diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 655f5a9be7fa31..3386b88ebe7eea 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -208,6 +208,12 @@ import ctypes except ImportError: ctypes = None + +try: + import _testcapi +except ImportError: + _testcapi = None + from test.support import (cpython_only, check_impl_detail, requires_debug_ranges, gc_collect, Py_GIL_DISABLED) @@ -1164,6 +1170,25 @@ def test_stateless(self): with self.assertRaises(Exception): _testinternalcapi.verify_stateless_code(func) + @unittest.skipUnless(ctypes, "requires ctypes") + @unittest.skipUnless(_testcapi, "requires _testcapi") + def test_co_framesize_overflow(self): + # See: https://github.com/python/cpython/issues/126119. + + def foo(a, b): + x = a * b + return x + + c = foo.__code__ + + # The exact limit depends on co_nlocalsplus, so we do not hardcode it. + too_large_stacksize = _testcapi.INT_MAX // 16 + ok_stacksize = too_large_stacksize // 2 + + with self.assertRaisesRegex(OverflowError, "stack size is too large"): + c.__replace__(co_stacksize=too_large_stacksize) + c.__replace__(co_stacksize=ok_stacksize) + def isinterned(s): return s is sys.intern(('_' + s + '_')[1:-1]) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-11-47-19.gh-issue-126119.xbAvxt.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-11-47-19.gh-issue-126119.xbAvxt.rst new file mode 100644 index 00000000000000..800a768e66c4b9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-11-47-19.gh-issue-126119.xbAvxt.rst @@ -0,0 +1,4 @@ +Fix a crash in DEBUG builds due to a lack of overflow checks when setting +the :attr:`co_stacksize ` field of a :ref:`code +object ` via :meth:`~object.__replace__`. +Reported by Valery Fedorenko. Patch by Bénédikt Tran. diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 0d264a6e346f95..6f55ccdeb75ed7 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -496,7 +496,22 @@ _PyCode_Validate(struct _PyCodeConstructor *con) PyErr_SetString(PyExc_ValueError, "code: co_varnames is too small"); return -1; } - + /* + * Since framesize = stacksize + nlocalsplus + FRAME_SPECIALS_SIZE is used + * as framesize * sizeof(PyObject *) and assumed to be < INT_MAX in many + * other places, we need to limit stacksize + nlocalsplus in order to + * avoid overflows. + * + * See https://github.com/python/cpython/issues/126119 for details + * and corresponding PR for the rationale on the upper limit value. + */ + Py_ssize_t limit = (Py_ssize_t)(INT_MAX / 16); + Py_ssize_t nlocalsplus = PyTuple_GET_SIZE(con->localsplusnames); + if (nlocalsplus >= limit || con->stacksize >= limit - nlocalsplus) { + PyErr_SetString(PyExc_OverflowError, + "code: locals + stack size is too large"); + return -1; + } return 0; }