diff --git a/Include/objimpl.h b/Include/objimpl.h index 56472a72e42d34..54d5ba500ddb49 100644 --- a/Include/objimpl.h +++ b/Include/objimpl.h @@ -160,8 +160,6 @@ PyAPI_FUNC(PyVarObject *) _PyObject_GC_Resize(PyVarObject *, Py_ssize_t); #define PyObject_GC_Resize(type, op, n) \ ( (type *) _PyObject_GC_Resize(_PyVarObject_CAST(op), (n)) ) - - PyAPI_FUNC(PyObject *) _PyObject_GC_New(PyTypeObject *); PyAPI_FUNC(PyVarObject *) _PyObject_GC_NewVar(PyTypeObject *, Py_ssize_t); diff --git a/Misc/NEWS.d/next/C_API/2026-11-12-21-03-17.gh-issue-161694.WlTSJL.rst b/Misc/NEWS.d/next/C_API/2026-11-12-21-03-17.gh-issue-161694.WlTSJL.rst new file mode 100644 index 00000000000000..d48df462699b19 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-11-12-21-03-17.gh-issue-161694.WlTSJL.rst @@ -0,0 +1,2 @@ +:c:func:`PyObject_GC_NewVar` and :c:func:`PyObject_GC_Resize` now check for +integer overflow when computing the object size. Patch by Victor Stinner. diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 992699756ac770..91ffc190fa320d 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -50,11 +50,6 @@ tuple_alloc(Py_ssize_t size) return op; } } - /* Check for overflow */ - if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) - - sizeof(PyObject *))) / sizeof(PyObject *)) { - return (PyTupleObject *)PyErr_NoMemory(); - } PyTupleObject *result = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size); if (result != NULL) { _PyTuple_RESET_HASH_CACHE(result); @@ -1079,14 +1074,6 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) *pv = PyTuple_New(newsize); return *pv == NULL ? -1 : 0; } - /* Check for overflow */ - if ((size_t)newsize > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) - - sizeof(PyObject *))) / sizeof(PyObject *)) { - *pv = NULL; - Py_DECREF(v); - PyErr_NoMemory(); - return -1; - } if (_PyObject_GC_IS_TRACKED(v)) { _PyObject_GC_UNTRACK(v); diff --git a/Python/gc.c b/Python/gc.c index e363fa77f724f8..ea2dfa9dfc7fe1 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -2102,18 +2102,46 @@ _PyObject_GC_New(PyTypeObject *tp) return op; } +// Similar to _PyObject_VAR_SIZE(), but check for integer overflow. +// Set *result and return 0 on success, return -1 on integer overflow. +// +// The result can be greater than PY_SSIZE_T_MAX. The caller is responsible +// for checking result maximum. +static inline int +_PyObject_GC_VAR_SIZE(PyTypeObject *type, Py_ssize_t nitems_signed, + size_t *result) +{ + assert(type->tp_itemsize >= 1); + assert(nitems_signed >= 0); + + const size_t basicsize = _Py_STATIC_CAST(size_t, type->tp_basicsize); + const size_t nitems = _Py_STATIC_CAST(size_t, nitems_signed); + const size_t itemsize = _Py_STATIC_CAST(size_t, type->tp_itemsize); + if (nitems > ((size_t)PY_SSIZE_T_MAX - basicsize) / itemsize) { + return -1; + } + + size_t size = basicsize + nitems * itemsize; + size = _Py_SIZE_ROUND_UP(size, SIZEOF_VOID_P); + *result = size; + return 0; +} + PyVarObject * _PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems) { - PyVarObject *op; - if (nitems < 0) { PyErr_BadInternalCall(); return NULL; } + + size_t size; + if (_PyObject_GC_VAR_SIZE(tp, nitems, &size) < 0) { + return (PyVarObject *)PyErr_NoMemory(); + } + size_t presize = _PyType_PreHeaderSize(tp); - size_t size = _PyObject_VAR_SIZE(tp, nitems); - op = (PyVarObject *)gc_alloc(tp, size, presize); + PyVarObject *op = (PyVarObject *)gc_alloc(tp, size, presize); if (op == NULL) { return NULL; } @@ -2138,14 +2166,23 @@ PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *tp, size_t extra_size) PyVarObject * _PyObject_GC_Resize(PyVarObject *op, Py_ssize_t nitems) { - const size_t basicsize = _PyObject_VAR_SIZE(Py_TYPE(op), nitems); - const size_t presize = _PyType_PreHeaderSize(Py_TYPE(op)); _PyObject_ASSERT((PyObject *)op, !_PyObject_GC_IS_TRACKED(op)); - if (basicsize > (size_t)PY_SSIZE_T_MAX - presize) { + assert(nitems >= 0); + + PyTypeObject *type = Py_TYPE(op); + size_t size; + if (_PyObject_GC_VAR_SIZE(type, nitems, &size) < 0) { return (PyVarObject *)PyErr_NoMemory(); } + + const size_t presize = _PyType_PreHeaderSize(type); + if (size > (size_t)PY_SSIZE_T_MAX - presize) { + return (PyVarObject *)PyErr_NoMemory(); + } + size += presize; + char *mem = (char *)op - presize; - mem = (char *)_PyObject_ReallocWithType(Py_TYPE(op), mem, presize + basicsize); + mem = (char *)_PyObject_ReallocWithType(type, mem, size); if (mem == NULL) { return (PyVarObject *)PyErr_NoMemory(); } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index f4f1a7777f3522..5cfe61856ec2eb 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -2811,18 +2811,46 @@ _PyObject_GC_New(PyTypeObject *tp) return op; } +// Similar to _PyObject_VAR_SIZE(), but check for integer overflow. +// Set *result and return 0 on success, return -1 on integer overflow. +// +// The result can be greater than PY_SSIZE_T_MAX. The caller is responsible +// for checking result maximum. +static inline int +_PyObject_GC_VAR_SIZE(PyTypeObject *type, Py_ssize_t nitems_signed, + size_t *result) +{ + assert(type->tp_itemsize >= 1); + assert(nitems_signed >= 0); + + const size_t basicsize = _Py_STATIC_CAST(size_t, type->tp_basicsize); + const size_t nitems = _Py_STATIC_CAST(size_t, nitems_signed); + const size_t itemsize = _Py_STATIC_CAST(size_t, type->tp_itemsize); + if (nitems > ((size_t)PY_SSIZE_T_MAX - basicsize) / itemsize) { + return -1; + } + + size_t size = basicsize + nitems * itemsize; + size = _Py_SIZE_ROUND_UP(size, SIZEOF_VOID_P); + *result = size; + return 0; +} + PyVarObject * _PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems) { - PyVarObject *op; - if (nitems < 0) { PyErr_BadInternalCall(); return NULL; } + + size_t size; + if (_PyObject_GC_VAR_SIZE(tp, nitems, &size) < 0) { + return (PyVarObject *)PyErr_NoMemory(); + } + size_t presize = _PyType_PreHeaderSize(tp); - size_t size = _PyObject_VAR_SIZE(tp, nitems); - op = (PyVarObject *)gc_alloc(tp, size, presize); + PyVarObject *op = (PyVarObject *)gc_alloc(tp, size, presize); if (op == NULL) { return NULL; } @@ -2847,14 +2875,23 @@ PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *tp, size_t extra_size) PyVarObject * _PyObject_GC_Resize(PyVarObject *op, Py_ssize_t nitems) { - const size_t basicsize = _PyObject_VAR_SIZE(Py_TYPE(op), nitems); - const size_t presize = _PyType_PreHeaderSize(((PyObject *)op)->ob_type); _PyObject_ASSERT((PyObject *)op, !_PyObject_GC_IS_TRACKED(op)); - if (basicsize > (size_t)PY_SSIZE_T_MAX - presize) { + assert(nitems >= 0); + + PyTypeObject *type = Py_TYPE(op); + size_t size; + if (_PyObject_GC_VAR_SIZE(type, nitems, &size) < 0) { return (PyVarObject *)PyErr_NoMemory(); } + + const size_t presize = _PyType_PreHeaderSize(type); + if (size > (size_t)PY_SSIZE_T_MAX - presize) { + return (PyVarObject *)PyErr_NoMemory(); + } + size += presize; + char *mem = (char *)op - presize; - mem = (char *)_PyObject_ReallocWithType(Py_TYPE(op), mem, presize + basicsize); + mem = (char *)_PyObject_ReallocWithType(type, mem, size); if (mem == NULL) { return (PyVarObject *)PyErr_NoMemory(); }