Bug report
Description
In Python/marshal.c, The TYPE_CODE reader accepts stacksize from untrusted marshal data with zero validation: stacksize = (int)r_long(p);
Objects/codeobject.c:550, When the code object is created, co_framesize is computed: co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE;
With stacksize = INT_MAX (2147483647) and nlocalsplus = 3, this overflows the signed 32-bit int and becomes -2147483642.
Objects/frameobject.c:2118, Frame allocation does the same overflow:
int slots = code->co_nlocalsplus + code->co_stacksize;
PyFrameObject *f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, slots);
Python/pystate.c:3136, The interpreter's frame push has a bug:
_PyInterpreterFrame *_PyThreadState_PushFrame(PyThreadState *tstate, size_t size) {
assert(size < INT_MAX/sizeof(PyObject *)); // Compiled out with -DNDEBUG
if (_PyThreadState_HasStackSpace(tstate, (int)size)) {
_PyInterpreterFrame *res = (_PyInterpreterFrame *)tstate->datastack_top;
tstate->datastack_top += size;
return res;
}
- This bug only occurs when using tracing, causing discrepancy in behavior.
PoC
# main.py
import marshal, struct, types
code = compile("def f(a, b, c): return a + b + c", "<t>", "exec")
ns = {};
exec(code, ns)
func_code = ns['f'].__code__
# Patch stacksize to INT_MAX in the marshal stream
data = bytearray(marshal.dumps(func_code))
data[13:17] = struct.pack('<i', 0x7fffffff) # stacksize field
# Load it: no error, valid code object
evil = marshal.loads(bytes(data))
func = types.FunctionType(evil, globals())
print("Running func without tracing...")
print(func(1, 2, 3))
import sys
sys.settrace(lambda f, e, a: None)
print("Running func with tracing...")
func(1, 2, 3)
Output
Running func without tracing...
6
Running func with tracing...
Traceback (most recent call last):
File "main.py", line 22, in <module>
func(1, 2, 3)
~~~~^^^^^^^^^
SystemError: Missing frame when calling trace function.
CPython versions tested on:
3.15
Operating systems tested on:
Linux
Bug report
Description
In
Python/marshal.c, TheTYPE_CODEreader acceptsstacksizefrom untrusted marshal data with zero validation:stacksize = (int)r_long(p);Objects/codeobject.c:550, When the code object is created,co_framesizeis computed:co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE;With
stacksize = INT_MAX (2147483647)andnlocalsplus = 3, this overflows the signed 32-bit int and becomes-2147483642.Objects/frameobject.c:2118, Frame allocation does the same overflow:Python/pystate.c:3136, The interpreter's frame push has a bug:PoC
Output
CPython versions tested on:
3.15
Operating systems tested on:
Linux