[issue24802] PyFloat_FromString Buffer Over-read

John Leitch report at bugs.python.org
Thu Aug 6 05:15:21 CEST 2015


New submission from John Leitch:

Python suffers from a buffer over-read in PyFloat_FromString() that is caused by the incorrect assumption that buffers returned by PyObject_GetBuffer() are null-terminated. This could potentially result in the disclosure of adjacent memory.

PyObject *
PyFloat_FromString(PyObject *v)
{
    const char *s, *last, *end;
    double x;
    PyObject *s_buffer = NULL;
    Py_ssize_t len;
    Py_buffer view = {NULL, NULL};
    PyObject *result = NULL;

    if (PyUnicode_Check(v)) {
        s_buffer = _PyUnicode_TransformDecimalAndSpaceToASCII(v);
        if (s_buffer == NULL)
            return NULL;
        s = PyUnicode_AsUTF8AndSize(s_buffer, &len);
        if (s == NULL) {
            Py_DECREF(s_buffer);
            return NULL;
        }
    }
    else if (PyObject_GetBuffer(v, &view, PyBUF_SIMPLE) == 0) {
        s = (const char *)view.buf; <<<< The unterminated buffer is retrieved here.
        len = view.len;
    }
    else {
        PyErr_Format(PyExc_TypeError,
            "float() argument must be a string or a number, not '%.200s'",
            Py_TYPE(v)->tp_name);
        return NULL;
    }
    last = s + len;
    /* strip space */
    while (s < last && Py_ISSPACE(*s))
        s++;
    while (s < last - 1 && Py_ISSPACE(last[-1]))
        last--;
    /* We don't care about overflow or underflow.  If the platform
     * supports them, infinities and signed zeroes (on underflow) are
     * fine. */
    x = PyOS_string_to_double(s, (char **)&end, NULL); <<<< The buffer is then passed
                                                            along here.
    if (end != last) {
        PyErr_Format(PyExc_ValueError,
                     "could not convert string to float: "
                     "%R", v);
        result = NULL;
    }
    else if (x == -1.0 && PyErr_Occurred())
        result = NULL;
    else
        result = PyFloat_FromDouble(x);

    PyBuffer_Release(&view);
    Py_XDECREF(s_buffer);
    return result;
}

double
PyOS_string_to_double(const char *s,
                      char **endptr,
                      PyObject *overflow_exception)
{
    double x, result=-1.0;
    char *fail_pos;

    errno = 0;
    PyFPE_START_PROTECT("PyOS_string_to_double", return -1.0)
    x = _PyOS_ascii_strtod(s, &fail_pos);
    PyFPE_END_PROTECT(x)

    if (errno == ENOMEM) {
        PyErr_NoMemory();
        fail_pos = (char *)s;
    }
    else if (!endptr && (fail_pos == s || *fail_pos != '\0'))
        PyErr_Format(PyExc_ValueError, <<<< If any of these error paths are taken, the
                                            unterminated buffer is passed along without
                                            its length, ultimately resulting in a call
                                            to unicode_fromformat_write_cstr().
                      "could not convert string to float: "
                      "%.200s", s);
    else if (fail_pos == s)
        PyErr_Format(PyExc_ValueError,
                      "could not convert string to float: "
                      "%.200s", s);
    else if (errno == ERANGE && fabs(x) >= 1.0 && overflow_exception)
        PyErr_Format(overflow_exception,
                      "value too large to convert to float: "
                      "%.200s", s);
    else
        result = x;

    if (endptr != NULL)
        *endptr = fail_pos;
    return result;
}

static int
unicode_fromformat_write_cstr(_PyUnicodeWriter *writer, const char *str,
                              Py_ssize_t width, Py_ssize_t precision)
{
    /* UTF-8 */
    Py_ssize_t length;
    PyObject *unicode;
    int res;

    length = strlen(str); <<<< str points to the unterminated buffer, which means 
                               strlen() my read off the end, depending on the contents
                               of adjacent memory.
    if (precision != -1)
        length = Py_MIN(length, precision);
    unicode = PyUnicode_DecodeUTF8Stateful(str, length, "replace", NULL); <<<< The new,
                                                                    incorrect length is
                                                                    passed along.
																		 
    if (unicode == NULL)
        return -1;

    res = unicode_fromformat_write_str(writer, unicode, width, -1);
    Py_DECREF(unicode);
    return res;
}

A script that reproduces the issue is as follows:

import array
float(array.array("B",b"A"*0x10))

And it produces the following exception:

0:000> gu
eax=00000000 ebx=06116b00 ecx=00000000 edx=00000000 esi=06116b00 edi=00000000
eip=6516be1b esp=0080f440 ebp=0080f4f4 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
python35!PyFloat_FromString+0xab:
6516be1b 8b4c2454        mov     ecx,dword ptr [esp+54h] ss:002b:0080f494=090a2fe8
0:000> db @@(view.buf)
090a2fe8  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
090a2ff8  c0 c0 c0 c0 d0 d0 d0 d0-?? ?? ?? ?? ?? ?? ?? ??  ........????????
090a3008  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3018  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3028  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3038  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3048  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3058  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0:000> g
(828.bec): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0080f4d0 ebx=0080f3a0 ecx=0080f3a0 edx=090a2fe8 esi=090a3000 edi=090a2fe9
eip=651ac280 esp=0080f31c ebp=0080f328 iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010282
python35!unicode_fromformat_write_cstr+0x10:
651ac280 8a06            mov     al,byte ptr [esi]          ds:002b:090a3000=??
0:000> db esi-0x10
090a2ff0  41 41 41 41 41 41 41 41-c0 c0 c0 c0 d0 d0 d0 d0  AAAAAAAA........
090a3000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
090a3060  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0:000> !analyze -v -nodb
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************


FAULTING_IP: 
python35!unicode_fromformat_write_cstr+10 [c:\build\cpython\objects\unicodeobject.c @ 2368]
651ac280 8a06            mov     al,byte ptr [esi]

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 651ac280 (python35!unicode_fromformat_write_cstr+0x00000010)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000000
   Parameter[1]: 090a3000
Attempt to read from address 090a3000

CONTEXT:  00000000 -- (.cxr 0x0;r)
eax=0080f4d0 ebx=0080f3a0 ecx=0080f3a0 edx=090a2fe8 esi=090a3000 edi=090a2fe9
eip=651ac280 esp=0080f31c ebp=0080f328 iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010282
python35!unicode_fromformat_write_cstr+0x10:
651ac280 8a06            mov     al,byte ptr [esi]          ds:002b:090a3000=??

FAULTING_THREAD:  00000bec

DEFAULT_BUCKET_ID:  INVALID_POINTER_READ

PROCESS_NAME:  python.exe

ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

EXCEPTION_PARAMETER1:  00000000

EXCEPTION_PARAMETER2:  090a3000

READ_ADDRESS:  090a3000 

FOLLOWUP_IP: 
python35!unicode_fromformat_write_cstr+10 [c:\build\cpython\objects\unicodeobject.c @ 2368]
651ac280 8a06            mov     al,byte ptr [esi]

NTGLOBALFLAG:  2000000

APPLICATION_VERIFIER_FLAGS:  0

FAULTING_LOCAL_VARIABLE_NAME:  length

APP:  python.exe

ANALYSIS_VERSION: 6.3.9600.17029 (debuggers(dbg).140219-1702) x86fre

PRIMARY_PROBLEM_CLASS:  INVALID_POINTER_READ

BUGCHECK_STR:  APPLICATION_FAULT_INVALID_POINTER_READ

LAST_CONTROL_TRANSFER:  from 651ac6e9 to 651ac280

STACK_TEXT:  
0080f328 651ac6e9 ffffffff 000000c8 00000000 python35!unicode_fromformat_write_cstr+0x10
0080f384 651ac955 0080f39c 090a2fe8 65321778 python35!unicode_fromformat_arg+0x409
0080f3d8 651f1a1a 65321778 0080f404 090a2fe8 python35!PyUnicode_FromFormatV+0x65
0080f3f4 652070a9 6536bd38 65321778 090a2fe8 python35!PyErr_Format+0x1a
0080f42c 6516be70 090a2fe8 0080f484 00000000 python35!PyOS_string_to_double+0xa9
0080f4f4 6514808b 06116b00 6536d658 6536d658 python35!PyFloat_FromString+0x100
0080f554 6516e6e2 06116b00 06116b00 06116b00 python35!PyNumber_Float+0xcb
0080f568 65194e08 6536d658 0610e630 00000000 python35!float_new+0x72
0080f588 6514947d 6536d658 0610e630 00000000 python35!type_call+0x38
0080f5a4 651e49cc 6536d658 0610e630 00000000 python35!PyObject_Call+0x6d
0080f5d0 651e449c 00000001 0610e630 00000083 python35!do_call+0x11c
0080f600 651e18d8 060ceab0 00000000 00000040 python35!call_function+0x36c
0080f678 651e339f 060ceab0 00000000 08c73ff0 python35!PyEval_EvalFrameEx+0x2318
0080f6c4 6521a142 060eff58 00000000 00000000 python35!_PyEval_EvalCodeWithName+0x82f
0080f700 65219fd5 060eff58 060eff58 0080f7cc python35!run_mod+0x42
0080f72c 6521904a 06d40fc8 061571d0 00000101 python35!PyRun_FileExFlags+0x85
0080f770 650ef037 06d40fc8 061571d0 00000001 python35!PyRun_SimpleFileExFlags+0x20a
0080f79c 650ef973 0080f7cc 65492100 65492108 python35!run_file+0xe7
0080f840 1c4b143f 00000002 05e06f10 05e0cf48 python35!Py_Main+0x913
0080f88c 76323744 7ee8e000 76323720 ddf3f75b python!__scrt_common_main_seh+0xff
0080f8a0 7789a064 7ee8e000 16227053 00000000 KERNEL32!BaseThreadInitThunk+0x24
0080f8e8 7789a02f ffffffff 778bd7c3 00000000 ntdll!__RtlUserThreadStart+0x2f
0080f8f8 00000000 1c4b14f7 7ee8e000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  .cxr 0x0 ; kb

FAULTING_SOURCE_LINE:  c:\build\cpython\objects\unicodeobject.c

FAULTING_SOURCE_FILE:  c:\build\cpython\objects\unicodeobject.c

FAULTING_SOURCE_LINE_NUMBER:  2368

FAULTING_SOURCE_CODE:  
  2364:     Py_ssize_t length;
  2365:     PyObject *unicode;
  2366:     int res;
  2367: 
> 2368:     length = strlen(str);
  2369:     if (precision != -1)
  2370:         length = Py_MIN(length, precision);
  2371:     unicode = PyUnicode_DecodeUTF8Stateful(str, length, "replace", NULL);
  2372:     if (unicode == NULL)
  2373:         return -1;


SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  python35!unicode_fromformat_write_cstr+10

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: python35

IMAGE_NAME:  python35.dll

DEBUG_FLR_IMAGE_TIMESTAMP:  5598ccc2

FAILURE_BUCKET_ID:  INVALID_POINTER_READ_c0000005_python35.dll!unicode_fromformat_write_cstr

BUCKET_ID:  APPLICATION_FAULT_INVALID_POINTER_READ_python35!unicode_fromformat_write_cstr+10

ANALYSIS_SOURCE:  UM

FAILURE_ID_HASH_STRING:  um:invalid_pointer_read_c0000005_python35.dll!unicode_fromformat_write_cstr

FAILURE_ID_HASH:  {1d85cbc9-3259-9a7e-3da2-8540573292b2}

Followup: MachineOwner
---------

To fix the issue, it is recommended that PyFloat_FromString() check the type of argument v after a successful PyObject_GetBuffer() call to determine if the buffer is null-terminated or otherwise. A proposed patch is attached.

----------
files: PyFloat_FromString_Buffer_Over-read.patch
keywords: patch
messages: 248099
nosy: JohnLeitch
priority: normal
severity: normal
status: open
title: PyFloat_FromString Buffer Over-read
type: security
versions: Python 3.5
Added file: http://bugs.python.org/file40132/PyFloat_FromString_Buffer_Over-read.patch

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue24802>
_______________________________________


More information about the Python-bugs-list mailing list