summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/memory.rst4
-rw-r--r--Include/pymem.h33
-rw-r--r--Misc/NEWS7
-rw-r--r--Modules/almodule.c2
-rw-r--r--Modules/arraymodule.c5
-rw-r--r--Modules/selectmodule.c4
-rw-r--r--Objects/obmalloc.c18
7 files changed, 59 insertions, 14 deletions
diff --git a/Doc/c-api/memory.rst b/Doc/c-api/memory.rst
index 1dcb115..81d7cd9 100644
--- a/Doc/c-api/memory.rst
+++ b/Doc/c-api/memory.rst
@@ -136,7 +136,9 @@ The following type-oriented macros are provided for convenience. Note that
Same as :cfunc:`PyMem_Realloc`, but the memory block is resized to ``(n *
sizeof(TYPE))`` bytes. Returns a pointer cast to :ctype:`TYPE\*`. On return,
- *p* will be a pointer to the new memory area, or *NULL* in the event of failure.
+ *p* will be a pointer to the new memory area, or *NULL* in the event of
+ failure. This is a C preprocessor macro; p is always reassigned. Save
+ the original value of p to avoid losing memory when handling errors.
.. cfunction:: void PyMem_Del(void *p)
diff --git a/Include/pymem.h b/Include/pymem.h
index f9acb55..542acee 100644
--- a/Include/pymem.h
+++ b/Include/pymem.h
@@ -69,8 +69,12 @@ PyAPI_FUNC(void) PyMem_Free(void *);
for malloc(0), which would be treated as an error. Some platforms
would return a pointer with no memory behind it, which would break
pymalloc. To solve these problems, allocate an extra byte. */
-#define PyMem_MALLOC(n) malloc((n) ? (n) : 1)
-#define PyMem_REALLOC(p, n) realloc((p), (n) ? (n) : 1)
+/* Returns NULL to indicate error if a negative size or size larger than
+ Py_ssize_t can represent is supplied. Helps prevents security holes. */
+#define PyMem_MALLOC(n) (((n) < 0 || (n) > PY_SSIZE_T_MAX) ? NULL \
+ : malloc((n) ? (n) : 1))
+#define PyMem_REALLOC(p, n) (((n) < 0 || (n) > PY_SSIZE_T_MAX) ? NULL \
+ : realloc((p), (n) ? (n) : 1))
#define PyMem_FREE free
#endif /* PYMALLOC_DEBUG */
@@ -79,24 +83,31 @@ PyAPI_FUNC(void) PyMem_Free(void *);
* Type-oriented memory interface
* ==============================
*
- * These are carried along for historical reasons. There's rarely a good
- * reason to use them anymore (you can just as easily do the multiply and
- * cast yourself).
+ * Allocate memory for n objects of the given type. Returns a new pointer
+ * or NULL if the request was too large or memory allocation failed. Use
+ * these macros rather than doing the multiplication yourself so that proper
+ * overflow checking is always done.
*/
#define PyMem_New(type, n) \
- ( assert((n) <= PY_SIZE_MAX / sizeof(type)) , \
+ ( ((n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
( (type *) PyMem_Malloc((n) * sizeof(type)) ) )
#define PyMem_NEW(type, n) \
- ( assert((n) <= PY_SIZE_MAX / sizeof(type)) , \
+ ( ((n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
( (type *) PyMem_MALLOC((n) * sizeof(type)) ) )
+/*
+ * The value of (p) is always clobbered by this macro regardless of success.
+ * The caller MUST check if (p) is NULL afterwards and deal with the memory
+ * error if so. This means the original value of (p) MUST be saved for the
+ * caller's memory error handler to not lose track of it.
+ */
#define PyMem_Resize(p, type, n) \
- ( assert((n) <= PY_SIZE_MAX / sizeof(type)) , \
- ( (p) = (type *) PyMem_Realloc((p), (n) * sizeof(type)) ) )
+ ( (p) = ((n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
+ (type *) PyMem_Realloc((p), (n) * sizeof(type)) )
#define PyMem_RESIZE(p, type, n) \
- ( assert((n) <= PY_SIZE_MAX / sizeof(type)) , \
- ( (p) = (type *) PyMem_REALLOC((p), (n) * sizeof(type)) ) )
+ ( (p) = ((n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
+ (type *) PyMem_REALLOC((p), (n) * sizeof(type)) )
/* PyMem{Del,DEL} are left over from ancient days, and shouldn't be used
* anymore. They're just confusing aliases for PyMem_{Free,FREE} now.
diff --git a/Misc/NEWS b/Misc/NEWS
index 7946633..97852ef 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -16,6 +16,13 @@ Core and Builtins
the python debugger steps into a class statement: the free variables (local
variables defined in an outer scope) would be deleted from the outer scope.
+- Issue #2620: Overflow checking when allocating or reallocating memory
+ was not always being done properly in some python types and extension
+ modules. PyMem_MALLOC, PyMem_REALLOC, PyMem_NEW and PyMem_RESIZE have
+ all been updated to perform better checks and places in the code that
+ would previously leak memory on the error path when such an allocation
+ failed have been fixed.
+
Library
-------
diff --git a/Modules/almodule.c b/Modules/almodule.c
index 7f48fff..93f26bd 100644
--- a/Modules/almodule.c
+++ b/Modules/almodule.c
@@ -1633,9 +1633,11 @@ al_QueryValues(PyObject *self, PyObject *args)
if (nvals < 0)
goto cleanup;
if (nvals > setsize) {
+ ALvalue *old_return_set = return_set;
setsize = nvals;
PyMem_RESIZE(return_set, ALvalue, setsize);
if (return_set == NULL) {
+ return_set = old_return_set;
PyErr_NoMemory();
goto cleanup;
}
diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c
index 99d25d3..bcd82aa 100644
--- a/Modules/arraymodule.c
+++ b/Modules/arraymodule.c
@@ -815,6 +815,7 @@ static int
array_do_extend(arrayobject *self, PyObject *bb)
{
Py_ssize_t size;
+ char *old_item;
if (!array_Check(bb))
return array_iter_extend(self, bb);
@@ -830,8 +831,10 @@ array_do_extend(arrayobject *self, PyObject *bb)
return -1;
}
size = Py_SIZE(self) + Py_SIZE(b);
+ old_item = self->ob_item;
PyMem_RESIZE(self->ob_item, char, size*self->ob_descr->itemsize);
if (self->ob_item == NULL) {
+ self->ob_item = old_item;
PyErr_NoMemory();
return -1;
}
@@ -884,7 +887,7 @@ array_inplace_repeat(arrayobject *self, Py_ssize_t n)
if (size > PY_SSIZE_T_MAX / n) {
return PyErr_NoMemory();
}
- PyMem_Resize(items, char, n * size);
+ PyMem_RESIZE(items, char, n * size);
if (items == NULL)
return PyErr_NoMemory();
p = items;
diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c
index 83a6538..86ee3cc 100644
--- a/Modules/selectmodule.c
+++ b/Modules/selectmodule.c
@@ -349,10 +349,12 @@ update_ufd_array(pollObject *self)
{
Py_ssize_t i, pos;
PyObject *key, *value;
+ struct pollfd *old_ufds = self->ufds;
self->ufd_len = PyDict_Size(self->dict);
- PyMem_Resize(self->ufds, struct pollfd, self->ufd_len);
+ PyMem_RESIZE(self->ufds, struct pollfd, self->ufd_len);
if (self->ufds == NULL) {
+ self->ufds = old_ufds;
PyErr_NoMemory();
return 0;
}
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
index efbd566..da8f9c2 100644
--- a/Objects/obmalloc.c
+++ b/Objects/obmalloc.c
@@ -727,6 +727,15 @@ PyObject_Malloc(size_t nbytes)
uint size;
/*
+ * Limit ourselves to PY_SSIZE_T_MAX bytes to prevent security holes.
+ * Most python internals blindly use a signed Py_ssize_t to track
+ * things without checking for overflows or negatives.
+ * As size_t is unsigned, checking for nbytes < 0 is not required.
+ */
+ if (nbytes > PY_SSIZE_T_MAX)
+ return NULL;
+
+ /*
* This implicitly redirects malloc(0).
*/
if ((nbytes - 1) < SMALL_REQUEST_THRESHOLD) {
@@ -1130,6 +1139,15 @@ PyObject_Realloc(void *p, size_t nbytes)
if (p == NULL)
return PyObject_Malloc(nbytes);
+ /*
+ * Limit ourselves to PY_SSIZE_T_MAX bytes to prevent security holes.
+ * Most python internals blindly use a signed Py_ssize_t to track
+ * things without checking for overflows or negatives.
+ * As size_t is unsigned, checking for nbytes < 0 is not required.
+ */
+ if (nbytes > PY_SSIZE_T_MAX)
+ return NULL;
+
pool = POOL_ADDR(p);
if (Py_ADDRESS_IN_RANGE(p, pool)) {
/* We're in charge of this block */