Skip to content

Commit 044d1a5

Browse files
committed
fix logic
1 parent 6b34c22 commit 044d1a5

File tree

7 files changed

+81
-45
lines changed

7 files changed

+81
-45
lines changed

Lib/test/support/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,21 @@ def check_sizeof(test, o, size):
901901
% (type(o), result, size)
902902
test.assertEqual(result, size, msg)
903903

904+
905+
def get_frame_specials_size():
906+
"""Compute the C defined constant FRAME_SPECIALS_SIZE in codeobject.c."""
907+
try:
908+
import _testinternalcapi
909+
except ImportError:
910+
raise unittest.SkipTest("_testinternalcapi required")
911+
912+
c = (lambda: ...).__code__
913+
# co_framesize = co_stacksize + co_nlocalsplus + FRAME_SPECIALS_SIZE
914+
co_framesize = _testinternalcapi.get_co_framesize(c)
915+
co_nlocalsplus = len({*c.co_varnames, *c.co_cellvars, *c.co_freevars})
916+
return co_framesize - c.co_stacksize - co_nlocalsplus
917+
918+
904919
#=======================================================================
905920
# Decorator/context manager for running a code in a different locale,
906921
# correctly resetting it afterwards.

Lib/test/test_code.py

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ def test_code_equal_with_instrumentation(self):
535535
self.assertNotEqual(code1, code2)
536536
sys.settrace(None)
537537

538+
@unittest.skipUnless(ctypes, "requires ctypes")
538539
@unittest.skipUnless(_testcapi, "requires _testcapi")
539540
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
540541
def test_co_framesize_overflow(self):
@@ -545,28 +546,17 @@ def foo(a, b):
545546
return x
546547

547548
c = foo.__code__
548-
co_nlocalsplus = len({*c.co_varnames, *c.co_cellvars, *c.co_freevars})
549-
# co_framesize = co_stacksize + co_nlocalsplus + FRAME_SPECIALS_SIZE
550-
co_framesize = _testinternalcapi.get_co_framesize(c)
551-
FRAME_SPECIALS_SIZE = co_framesize - c.co_stacksize - co_nlocalsplus
552549

550+
fss = support.get_frame_specials_size()
553551
ps = ctypes.sizeof(ctypes.c_void_p) # sizeof(PyObject *)
554-
smallest_evil_co_stacksize = (
555-
(_testcapi.INT_MAX - co_nlocalsplus - FRAME_SPECIALS_SIZE) // ps
556-
)
552+
co_nlocalsplus = len({*c.co_varnames, *c.co_cellvars, *c.co_freevars})
553+
# anything below that limit is a valid co_stacksize
554+
evil_stacksize = int(_testcapi.INT_MAX / ps - fss - co_nlocalsplus)
555+
self.assertLessEqual(evil_stacksize, _testcapi.INT_MAX // ps)
557556

558-
for evil_co_stacksize in [
559-
_testcapi.INT_MAX,
560-
_testcapi.INT_MAX // ps,
561-
smallest_evil_co_stacksize,
562-
]:
563-
with (
564-
self.subTest(evil_co_stacksize),
565-
self.assertRaisesRegex(OverflowError, "co_stacksize")
566-
):
567-
c.__replace__(co_stacksize=evil_co_stacksize)
568-
569-
c.__replace__(co_stacksize=smallest_evil_co_stacksize - 1)
557+
with self.assertRaisesRegex(OverflowError, "co_stacksize"):
558+
c.__replace__(co_stacksize=evil_stacksize)
559+
c.__replace__(co_stacksize=evil_stacksize - 1)
570560

571561

572562
def isinterned(s):

Lib/test/test_frame.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,20 @@ def test_f_lineno_del_segfault(self):
225225
@unittest.skipUnless(_testcapi, "requires _testcapi")
226226
def test_sizeof_overflow(self):
227227
# See: https://github.com/python/cpython/issues/126119
228-
evil_co_stacksize = _testcapi.INT_MAX // support.calcobjsize('P')
228+
ctypes = import_helper.import_module('ctypes')
229+
229230
f, _, _ = self.make_frames()
230-
evil_code = f.f_code.replace(co_stacksize=evil_co_stacksize)
231+
c = f.f_code
232+
co_nlocalsplus = len({*c.co_varnames, *c.co_cellvars, *c.co_freevars})
233+
234+
fss = support.get_frame_specials_size()
235+
ps = ctypes.sizeof(ctypes.c_void_p) # sizeof(PyObject *)
236+
evil_stacksize = int(_testcapi.INT_MAX / ps - fss - co_nlocalsplus)
237+
# an evil code with a valid (but very large) stack size
238+
evil_code = f.f_code.replace(co_stacksize=evil_stacksize - 1)
231239
frame = _testcapi.frame_new(evil_code, globals(), locals())
232-
self.assertGreaterEqual(frame.__sizeof__(), evil_co_stacksize)
240+
message = re.escape("size exceeds INT_MAX")
241+
self.assertRaisesRegex(OverflowError, message, frame.__sizeof__)
233242

234243

235244
class ReprTest(unittest.TestCase):

Lib/test/test_generators.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import types
1212

1313
from test import support
14+
from test.support import import_helper
1415

1516
try:
1617
import _testcapi
@@ -269,16 +270,24 @@ def loop():
269270
#This should not raise
270271
loop()
271272

272-
@unittest.skipUnless(_testcapi, "requires _testcapi.INT_MAX")
273+
@unittest.skipUnless(_testcapi, "requires _testcapi")
273274
def test_gi_frame_f_code_overflow(self):
274275
# See: https://github.com/python/cpython/issues/126119
276+
ctypes = import_helper.import_module('ctypes')
275277

276278
def f(): yield
279+
c = f().gi_frame.f_code
280+
co_nlocalsplus = len({*c.co_varnames, *c.co_cellvars, *c.co_freevars})
277281

278-
evil_co_stacksize = _testcapi.INT_MAX // support.calcobjsize("P")
279-
evil = f().gi_frame.f_code.__replace__(co_stacksize=evil_co_stacksize)
282+
ps = ctypes.sizeof(ctypes.c_void_p) # sizeof(PyObject *)
283+
fss = support.get_frame_specials_size()
284+
# anything below that limit is a valid co_stacksize
285+
evil_stacksize = int(_testcapi.INT_MAX / ps - fss - co_nlocalsplus)
286+
287+
evil = c.__replace__(co_stacksize=evil_stacksize - 1)
280288
evil_gi = types.FunctionType(evil, {})()
281-
self.assertGreaterEqual(evil_gi.__sizeof__(), evil_co_stacksize)
289+
message = re.escape("size exceeds INT_MAX")
290+
self.assertRaisesRegex(OverflowError, message, evil_gi.__sizeof__)
282291

283292

284293
class ModifyUnderlyingIterableTest(unittest.TestCase):

Objects/codeobject.c

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -437,17 +437,16 @@ _PyCode_Validate(struct _PyCodeConstructor *con)
437437
return -1;
438438
}
439439
/*
440-
* Ensure that the framesize will not overflow.
440+
* The framesize = stacksize + nlocalsplus + FRAME_SPECIALS_SIZE is used
441+
* as framesize * sizeof(PyObject *) and assumed to be < INT_MAX. Thus,
442+
* we need to dynamically limit the value of stacksize.
441443
*
442-
* There are various places in the code where the size of the object
443-
* is assumed to be at most INT_MAX / sizeof(PyObject *). Since this
444-
* size is the framesize, we need to guarantee that there will not
445-
* be an overflow on "framesize * sizeof(PyObject *) + CONSTANT".
444+
* See https://github.com/python/cpython/issues/126119 for details.
446445
*/
447-
int nlocalsplus = PyTuple_GET_SIZE(con->localsplusnames);
448-
if ((size_t)con->stacksize
449-
>= (INT_MAX - FRAME_SPECIALS_SIZE - nlocalsplus) / sizeof(PyObject *))
450-
{
446+
int max_stacksize = (int)(INT_MAX / sizeof(PyObject *))
447+
- FRAME_SPECIALS_SIZE
448+
- PyTuple_GET_SIZE(con->localsplusnames);
449+
if (con->stacksize >= max_stacksize) {
451450
PyErr_SetString(PyExc_OverflowError, "code: co_stacksize is too large");
452451
return -1;
453452
}

Objects/frameobject.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,17 +1804,24 @@ PyDoc_STRVAR(clear__doc__,
18041804
static PyObject *
18051805
frame_sizeof(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
18061806
{
1807-
Py_ssize_t res = offsetof(PyFrameObject, _f_frame_data)
1808-
+ offsetof(_PyInterpreterFrame, localsplus);
1807+
Py_ssize_t base = offsetof(PyFrameObject, _f_frame_data)
1808+
+ offsetof(_PyInterpreterFrame, localsplus);
18091809
PyCodeObject *code = _PyFrame_GetCode(f->f_frame);
18101810
int nslots = _PyFrame_NumSlotsForCodeObject(code);
18111811
assert(nslots >= 0);
18121812
// By construction, 0 <= nslots < code->co_framesize <= INT_MAX.
18131813
// It should not be possible to have nslots >= PY_SSIZE_T_MAX
18141814
// even if PY_SSIZE_T_MAX < INT_MAX because code->co_framesize
1815-
// is checked in _PyCode_Validate().
1816-
assert((size_t)nslots < (INT_MAX - res) / sizeof(PyObject *));
1817-
return PyLong_FromSsize_t(res + nslots * sizeof(PyObject *));
1815+
// is checked in _PyCode_Validate(). However, it is possible
1816+
// to make base + nslots * sizeof(PyObject *) >= INT_MAX since
1817+
// 'base' is not yet known when creating code objects.
1818+
//
1819+
// See https://github.com/python/cpython/issues/126119 for details.
1820+
if (nslots >= (int)((INT_MAX - base) / sizeof(PyObject *))) {
1821+
PyErr_SetString(PyExc_OverflowError, "size exceeds INT_MAX");
1822+
return NULL;
1823+
}
1824+
return PyLong_FromSsize_t(base + nslots * sizeof(PyObject *));
18181825
}
18191826

18201827
PyDoc_STRVAR(sizeof__doc__,

Objects/genobject.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -817,17 +817,24 @@ static PyMemberDef gen_memberlist[] = {
817817
static PyObject *
818818
gen_sizeof(PyGenObject *gen, PyObject *Py_UNUSED(ignored))
819819
{
820-
Py_ssize_t res = offsetof(PyGenObject, gi_iframe)
821-
+ offsetof(_PyInterpreterFrame, localsplus);
820+
Py_ssize_t base = offsetof(PyGenObject, gi_iframe)
821+
+ offsetof(_PyInterpreterFrame, localsplus);
822822
PyCodeObject *code = _PyGen_GetCode(gen);
823823
int nslots = _PyFrame_NumSlotsForCodeObject(code);
824824
assert(nslots >= 0);
825825
// By construction, 0 <= nslots < code->co_framesize <= INT_MAX.
826826
// It should not be possible to have nslots >= PY_SSIZE_T_MAX
827827
// even if PY_SSIZE_T_MAX < INT_MAX because code->co_framesize
828-
// is checked in _PyCode_Validate().
829-
assert((size_t)nslots < (INT_MAX - res) / sizeof(PyObject *));
830-
return PyLong_FromSsize_t(res + nslots * sizeof(PyObject *));
828+
// is checked in _PyCode_Validate(). However, it is possible
829+
// to make base + nslots * sizeof(PyObject *) >= INT_MAX since
830+
// 'base' is not yet known when creating code objects.
831+
//
832+
// See https://github.com/python/cpython/issues/126119 for details.
833+
if (nslots >= (int)((INT_MAX - base) / sizeof(PyObject *))) {
834+
PyErr_SetString(PyExc_OverflowError, "size exceeds INT_MAX");
835+
return NULL;
836+
}
837+
return PyLong_FromSsize_t(base + nslots * sizeof(PyObject *));
831838
}
832839

833840
PyDoc_STRVAR(sizeof__doc__,

0 commit comments

Comments
 (0)