Skip to content

gh-127681: add overflow checks when using PyMem_Malloc #127686

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 31 additions & 12 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -331,19 +331,30 @@ _ctypes_alloc_format_string(const char *prefix, const char *suffix)
return NULL;
}
len = strlen(suffix);
if (prefix)
len += strlen(prefix);
if (prefix) {
size_t prefix_len = strlen(prefix);
if (len > PY_SIZE_MAX - prefix_len) {
goto oom;
}
len += prefix_len;
}
if (len == PY_SIZE_MAX) {
goto oom;
}
Comment on lines +334 to +343
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an example of overflow? AFAIK, prefix is smaller than 1030 bytes, and suffix also is a short string.

result = PyMem_Malloc(len + 1);
if (result == NULL) {
PyErr_NoMemory();
return NULL;
goto oom;
}
if (prefix)
strcpy(result, prefix);
else
result[0] = '\0';
strcat(result, suffix);
return result;

oom:
PyErr_NoMemory();
return NULL;
}

/*
Expand All @@ -363,12 +374,16 @@ _ctypes_alloc_format_string_with_shape(int ndim, const Py_ssize_t *shape,
int k;

prefix_len = 32 * ndim + 3;
if (prefix)
prefix_len += strlen(prefix);
if (prefix) {
size_t l = strlen(prefix);
if ((size_t)prefix > (size_t)PY_SSIZE_T_MAX - l) {
goto oom;
}
prefix_len += l;
}
Comment on lines +377 to +383
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefix is always "&" here.

new_prefix = PyMem_Malloc(prefix_len);
if (new_prefix == NULL) {
PyErr_NoMemory();
return NULL;
goto oom;
}
new_prefix[0] = '\0';
if (prefix)
Expand All @@ -388,6 +403,10 @@ _ctypes_alloc_format_string_with_shape(int ndim, const Py_ssize_t *shape,
result = _ctypes_alloc_format_string(new_prefix, suffix);
PyMem_Free(new_prefix);
return result;

oom:
PyErr_NoMemory();
return NULL;
}

/* StructParamObject and StructParam_Type are used in _ctypes_callproc()
Expand Down Expand Up @@ -1666,7 +1685,7 @@ PyCArrayType_init(PyObject *self, PyObject *args, PyObject *kwds)
if (stginfo->format == NULL)
goto error;
stginfo->ndim = iteminfo->ndim + 1;
stginfo->shape = PyMem_Malloc(sizeof(Py_ssize_t) * stginfo->ndim);
stginfo->shape = PyMem_New(Py_ssize_t, stginfo->ndim);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This never overflows. ndim < 32.

if (stginfo->shape == NULL) {
PyErr_NoMemory();
goto error;
Expand Down Expand Up @@ -3094,7 +3113,7 @@ PyCData_MallocBuffer(CDataObject *obj, StgInfo *info)
/* In python 2.4, and ctypes 0.9.6, the malloc call took about
33% of the creation time for c_int().
*/
obj->b_ptr = (char *)PyMem_Malloc(info->size);
obj->b_ptr = PyMem_New(char, info->size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same.

if (obj->b_ptr == NULL) {
PyErr_NoMemory();
return -1;
Expand Down Expand Up @@ -4775,7 +4794,7 @@ Array_subscript(PyObject *myself, PyObject *item)
return PyBytes_FromStringAndSize(ptr + start,
slicelen);
}
dest = (char *)PyMem_Malloc(slicelen);
dest = PyMem_New(char, slicelen);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same.


if (dest == NULL)
return PyErr_NoMemory();
Expand Down Expand Up @@ -5461,7 +5480,7 @@ Pointer_subscript(PyObject *myself, PyObject *item)
return PyBytes_FromStringAndSize(ptr + start,
len);
}
dest = (char *)PyMem_Malloc(len);
dest = PyMem_New(char, len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same.

if (dest == NULL)
return PyErr_NoMemory();
for (cur = start, i = 0; i < len; cur += step, i++) {
Expand Down
43 changes: 30 additions & 13 deletions Modules/_ctypes/stgdict.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
int
PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info)
{
Py_ssize_t size;

ctype_clear_stginfo(dst_info);
PyMem_Free(dst_info->ffi_type_pointer.elements);
PyMem_Free(dst_info->format);
Expand All @@ -43,35 +41,49 @@ PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info)
Py_XINCREF(dst_info->module);

if (src_info->format) {
dst_info->format = PyMem_Malloc(strlen(src_info->format) + 1);
size_t s = strlen(src_info->format);
if (s >= (size_t)PY_SSIZE_T_MAX) {
goto oom;
}
dst_info->format = PyMem_Malloc(s + 1);
Comment on lines +44 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This never overflows. src_info->format points to a memory array at least strlen(src_info->format) + 1 bytes long.

if (dst_info->format == NULL) {
PyErr_NoMemory();
return -1;
goto oom;
}
strcpy(dst_info->format, src_info->format);
}
if (src_info->shape) {
dst_info->shape = PyMem_Malloc(sizeof(Py_ssize_t) * src_info->ndim);
dst_info->shape = PyMem_New(Py_ssize_t, src_info->ndim);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It never overflows. ndim < 32.

if (dst_info->shape == NULL) {
PyErr_NoMemory();
return -1;
goto oom;
}
memcpy(dst_info->shape, src_info->shape,
sizeof(Py_ssize_t) * src_info->ndim);
}

if (src_info->ffi_type_pointer.elements == NULL)
return 0;
size = sizeof(ffi_type *) * (src_info->length + 1);
if ((size_t)src_info->length > (size_t)PY_SSIZE_T_MAX / sizeof(ffi_type *) - 1) {
goto oom;
}
Py_ssize_t size = sizeof(ffi_type *) * (src_info->length + 1);
Comment on lines +65 to +68
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an example of overflow?

dst_info->ffi_type_pointer.elements = PyMem_Malloc(size);
if (dst_info->ffi_type_pointer.elements == NULL) {
PyErr_NoMemory();
return -1;
goto oom;
}
memcpy(dst_info->ffi_type_pointer.elements,
src_info->ffi_type_pointer.elements,
size);
return 0;

oom:
if (src_info->format) {
PyMem_Free(dst_info->format);
}
if (src_info->shape) {
PyMem_Free(dst_info->shape);
}
PyErr_NoMemory();
return -1;
}

/* descr is the descriptor for a field marked as anonymous. Get all the
Expand Down Expand Up @@ -331,6 +343,10 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
PyMem_Free(stginfo->format);
stginfo->format = NULL;
}
if (format_spec_size >= PY_SSIZE_T_MAX) {
PyErr_NoMemory();
goto error;
}
Comment on lines +346 to +349
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It never overflows. There is a memory array at least format_spec_size+1 bytes long.

stginfo->format = PyMem_Malloc(format_spec_size + 1);
if (!stginfo->format) {
PyErr_NoMemory();
Expand Down Expand Up @@ -564,8 +580,9 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
* dummy fields standing in for array elements, and the
* ffi_types representing the dummy structures.
*/
alloc_size = (ffi_ofs + 1 + len + num_ffi_type_pointers) * sizeof(ffi_type *) +
num_ffi_types * sizeof(ffi_type);
// TODO: check if this can overflow
alloc_size = (ffi_ofs + 1 + len + num_ffi_type_pointers) * sizeof(ffi_type *)
+ num_ffi_types * sizeof(ffi_type);
type_block = PyMem_Malloc(alloc_size);

if (type_block == NULL) {
Expand Down
1 change: 1 addition & 0 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5186,6 +5186,7 @@ cursesmodule_exec(PyObject *module)
continue;
}
if (strncmp(key_name, "KEY_F(", 6) == 0) {
// We may assume that the name has a small enough length.
char *fn_key_name = PyMem_Malloc(strlen(key_name) + 1);
if (!fn_key_name) {
PyErr_NoMemory();
Expand Down
10 changes: 8 additions & 2 deletions Modules/_decimal/_decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -2116,10 +2116,10 @@ numeric_as_ascii(PyObject *u, int strip_ws, int ignore_underscores)
data = PyUnicode_DATA(u);
len = PyUnicode_GET_LENGTH(u);

// Note: this should not overflow since the number of digits is << 2^32.
cp = res = PyMem_Malloc(len+1);
if (res == NULL) {
PyErr_NoMemory();
return NULL;
goto oom;
}

j = 0;
Expand Down Expand Up @@ -2155,6 +2155,10 @@ numeric_as_ascii(PyObject *u, int strip_ws, int ignore_underscores)
}
*cp = '\0';
return res;

oom:
PyErr_NoMemory();
return NULL;
}

/* Return a new PyDecObject or a subtype from a C string. Use the context
Expand Down Expand Up @@ -2689,6 +2693,7 @@ dectuple_as_str(PyObject *dectuple)

tsize = PyTuple_Size(digits);
/* [sign][coeffdigits+1][E][-][expdigits+1]['\0'] */
// TODO: this should not overflow since we would be below the digits limit
mem = 1 + tsize + 3 + MPD_EXPDIGITS + 2;
cp = decstring = PyMem_Malloc(mem);
if (decstring == NULL) {
Expand Down Expand Up @@ -3377,6 +3382,7 @@ dec_repr(PyObject *dec)
static char *
dec_strdup(const char *src, Py_ssize_t size)
{
// Note: this should not overflow since the number of digits is << 2^32.
char *dest = PyMem_Malloc(size+1);
if (dest == NULL) {
PyErr_NoMemory();
Expand Down
4 changes: 4 additions & 0 deletions Modules/_io/textio.c
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ _PyIncrementalNewlineDecoder_decode(PyObject *myself,
when there is something to translate. On the other hand,
we already know there is a \r byte, so chances are high
that something needs to be done. */
if (len > PY_SSIZE_T_MAX / kind) {
PyErr_NoMemory();
goto error;
}
Comment on lines +468 to +471
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It never overflows. The size of output is larger than kind * len.

translated = PyMem_Malloc(kind * len);
if (translated == NULL) {
PyErr_NoMemory();
Expand Down
8 changes: 5 additions & 3 deletions Modules/_io/winconsoleio.c
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ static wchar_t *
read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen) {
int err = 0, sig = 0;

wchar_t *buf = (wchar_t*)PyMem_Malloc(maxlen * sizeof(wchar_t));
wchar_t *buf = PyMem_New(wchar_t, maxlen);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It never overflows. maxlen is at most DWORD_MAX / 4, and sizeof(wchar_t) is 2.

if (!buf) {
PyErr_NoMemory();
goto error;
Expand Down Expand Up @@ -875,8 +875,10 @@ _io__WindowsConsoleIO_readall_impl(winconsoleio *self)
return NULL;

bufsize = BUFSIZ;

buf = (wchar_t*)PyMem_Malloc((bufsize + 1) * sizeof(wchar_t));
// Note: BUFSIZ is small enough not to overflow, so no need
// to check that 'bufsize + 1' overflows. However, we
// still need to check (bufsize + 1) * sizeof(wchar_t).
buf = PyMem_New(wchar_t, bufsize);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It never overflows.

if (buf == NULL) {
PyErr_NoMemory();
return NULL;
Expand Down
6 changes: 5 additions & 1 deletion Modules/_localemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,11 @@ static PyObject *
decode_strings(const char *result, size_t max_count)
{
/* Convert a sequence of NUL-separated C strings to a Python string
* containing semicolon separated items. */
* containing semicolon separated items.
*
* For efficiency purposes, we assume that messages to be translated
* are not absurdly large so that 'i' never overflows.
*/
size_t i = 0;
size_t count = 0;
for (; count < max_count && result[i]; count++) {
Expand Down
2 changes: 2 additions & 0 deletions Modules/_multiprocessing/semaphore.c
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ _multiprocessing_SemLock_impl(PyTypeObject *type, int kind, int value,
}

if (!unlink) {
// We may assume that the name has a small enough length.
name_copy = PyMem_Malloc(strlen(name) + 1);
if (name_copy == NULL) {
return PyErr_NoMemory();
Expand Down Expand Up @@ -555,6 +556,7 @@ _multiprocessing_SemLock__rebuild_impl(PyTypeObject *type, SEM_HANDLE handle,
char *name_copy = NULL;

if (name != NULL) {
// We may assume that the name has a small enough length.
name_copy = PyMem_Malloc(strlen(name) + 1);
if (name_copy == NULL)
return PyErr_NoMemory();
Expand Down
7 changes: 5 additions & 2 deletions Modules/_posixsubprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -1125,13 +1125,16 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
if (extra_group_size < 0)
goto cleanup;

if (extra_group_size > MAX_GROUPS) {
if (extra_group_size > MAX_GROUPS
|| (size_t)extra_group_size > (size_t)PY_SSIZE_T_MAX / sizeof(gid_t))
Comment on lines +1128 to +1129
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can be sure that MAX_GROUPS is small enough.

{
PyErr_SetString(PyExc_ValueError, "too many extra_groups");
goto cleanup;
}

/* Deliberately keep extra_groups == NULL for extra_group_size == 0 */
if (extra_group_size > 0) {
/* extra_group_size < MAX_GROUPS */
extra_groups = PyMem_RawMalloc(extra_group_size * sizeof(gid_t));
if (extra_groups == NULL) {
PyErr_SetString(PyExc_MemoryError,
Expand Down Expand Up @@ -1192,7 +1195,7 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
#endif /* HAVE_SETREUID */
}

c_fds_to_keep = PyMem_Malloc(fds_to_keep_len * sizeof(int));
c_fds_to_keep = PyMem_New(int, fds_to_keep_len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It never overflows. fds_to_keep_len is the size of a tuple. sizeof(int) <= sizeof(PyObject *).

if (c_fds_to_keep == NULL) {
PyErr_SetString(PyExc_MemoryError, "failed to malloc c_fds_to_keep");
goto cleanup;
Expand Down
8 changes: 6 additions & 2 deletions Modules/_randommodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -524,10 +524,10 @@ _random_Random_getrandbits_impl(RandomObject *self, int k)
return PyLong_FromUnsignedLong(genrand_uint32(self) >> (32 - k));

words = (k - 1) / 32 + 1;
// words * 4 always fits on int if k fits on ints (and thus fits on size_t)
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
if (wordarray == NULL) {
PyErr_NoMemory();
return NULL;
goto oom;
}

/* Fill-out bits of long integer, by 32-bit words, from least significant
Expand All @@ -548,6 +548,10 @@ _random_Random_getrandbits_impl(RandomObject *self, int k)
PY_LITTLE_ENDIAN, 0 /* unsigned */);
PyMem_Free(wordarray);
return result;

oom:
PyErr_NoMemory();
return NULL;
}

static int
Expand Down
1 change: 1 addition & 0 deletions Modules/_scproxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cfstring_to_pystring(CFStringRef ref)
PyObject* result;
char* buf;

// TODO: can this overflow?
buf = PyMem_Malloc(len*4);
if (buf == NULL) {
PyErr_NoMemory();
Expand Down
2 changes: 1 addition & 1 deletion Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -1725,7 +1725,7 @@ prepare_s(PyStructObject *self)
}

/* check for overflow */
if ((ncodes + 1) > ((size_t)PY_SSIZE_T_MAX / sizeof(formatcode))) {
if (ncodes > ((size_t)PY_SSIZE_T_MAX / sizeof(formatcode)) - 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ncodes + 1 does not overflow. ncodes is not longer then the length of str/bytes.

PyErr_NoMemory();
return -1;
}
Expand Down
1 change: 1 addition & 0 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,7 @@ _winapi_GetShortPathName_impl(PyObject *module, LPCWSTR path)
cchBuffer = GetShortPathNameW(path, NULL, 0);
Py_END_ALLOW_THREADS
if (cchBuffer) {
// Note: the path length is way smaller than the max alloc. size.
WCHAR *buffer = (WCHAR *)PyMem_Malloc(cchBuffer * sizeof(WCHAR));
if (buffer) {
Py_BEGIN_ALLOW_THREADS
Expand Down
Loading
Loading