Bug report
Script bug.py (from issue gh-149146, but modified):
importresourceimportsignalimportosdefsegfault_handler(signum, frame):
os.abort()
raiseMemoryError#signal.signal(signal.SIGSEGV, segfault_handler)# deallocation the chain uses a lot of C stack memoryCHAIN_LENGTH=1024*1024# limit process memory to not crash the whole machinemax_memory=1024*1024*1024resource.setrlimit(resource.RLIMIT_AS, (max_memory, max_memory))
print("create long chain...")
long_chain=Nonefor_inrange(CHAIN_LENGTH):
long_chain= (long_chain, None)
print("fill the process memory...")
memory= []
try:
large=1024*1024whileTrue:
memory.append(b'x'*large)
exceptMemoryError:
passtry:
small=1024os.uname()
whileTrue:
memory.append(b'x'*small)
exceptMemoryError:
passprint("delete the long chain...")
long_chain=None# at exit (on error), deallocating long_chain will use a lot of C stack memory# ... and does crash!Sometimes, running this script triggers a bug when memory.append(b'x' * small) raises a MemoryError. Sadly, the reproducer is very fragile. So far, I failed to write a more reliable reproducer.
The fix is to set stack_pointer[-1] to PyStackRef_NULL in _CALL_LIST_APPEND before calling JUMP_TO_LABEL(error);.
Steps:
- PyErr_NoMemory() is called by _PyList_AppendTakeRefListResize() in
_CALL_LIST_APPEND opcode - At this point,
stack_pointer[-1] (arg) points to freed memory (all bytes are set to 0xdd) _PyList_AppendTakeRef() returns non-zero result, so _CALL_LIST_APPEND opcode goes to JUMP_TO_LABEL(error);- The
error label calls JUMP_TO_LABEL(exception_unwind); exception_unwind label clears the stack: call ref = _PyFrame_StackPop() and PyStackRef_XCLOSE(ref) until the stack is emptyexception_unwind: PyStackRef_XCLOSE(ref) fails with an assertion error (Py_DECREF_MORTAL: Assertion !_Py_IsStaticImmortal(op)') because stack_pointer[-1]` points to freed memory
$ gdb -args ./python bug.py (gdb) b os_uname
(gdb) run
Breakpoint 2, os_uname (...)
(gdb) b PyErr_NoMemory
(gdb) c
Continuing.
Breakpoint 3, PyErr_NoMemory () at Python/errors.c:803
803 {
(gdb) n
(...)
(gdb) _PyList_AppendTakeRefListResize (...)
531 Py_DECREF(newitem);
(gdb) 532 return -1;
(gdb) _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=0x7ffff7fb0020, throwflag=0) at Python/generated_cases.c.h:3983
3983 if (err) {
(gdb) p stack_pointer[-1]
$2 = <unknown at remote 0xcc08e0>
(gdb) p /x *(PyObject*)stack_pointer[-1]
$3 = {
{
ob_refcnt_full = 0xdddddddddddddddd,
{
ob_refcnt = 0xdddddddd,
ob_overflow = 0xdddd,
ob_flags = 0xdddd
},
_aligner = 0xdd
},
ob_type = 0xdddddddddddddddd
}
(gdb) n
3984 JUMP_TO_LABEL(error);
(gdb) n
(...)
(gdb) 13843 JUMP_TO_LABEL(exception_unwind);
(gdb) 13849 int offset = INSTR_OFFSET()-1;
(...)
(gdb) 13865 while (frame->stackpointer > new_top) {
(gdb) 13866 _PyStackRef ref = _PyFrame_StackPop(frame);
(gdb) 13867 PyStackRef_XCLOSE(ref);
(gdb) p ref
$5 = <unknown at remote 0xcc08e0>
(gdb) p /x *(PyObject*)ref
$6 = {
{
ob_refcnt_full = 0xdddddddddddddddd,
{
ob_refcnt = 0xdddddddd,
ob_overflow = 0xdddd,
ob_flags = 0xdddd
},
_aligner = 0xdd
},
ob_type = 0xdddddddddddddddd
}
(gdb) n
python: ./Include/internal/pycore_object.h:414: Py_DECREF_MORTAL: Assertion `!_Py_IsStaticImmortal(op)' failed.
Program received signal SIGABRT, Aborted.
0x00007ffff7cfedcc in __pthread_kill_implementation () from /lib64/libc.so.6
Linked PRs
Bug report
Script
bug.py(from issue gh-149146, but modified):Sometimes, running this script triggers a bug when
memory.append(b'x' * small)raises aMemoryError. Sadly, the reproducer is very fragile. So far, I failed to write a more reliable reproducer.The fix is to set
stack_pointer[-1]toPyStackRef_NULLin_CALL_LIST_APPENDbefore callingJUMP_TO_LABEL(error);.Steps:
_CALL_LIST_APPENDopcodestack_pointer[-1](arg) points to freed memory (all bytes are set to0xdd)_PyList_AppendTakeRef()returns non-zero result, so_CALL_LIST_APPENDopcode goes toJUMP_TO_LABEL(error);errorlabel callsJUMP_TO_LABEL(exception_unwind);exception_unwindlabel clears the stack: callref = _PyFrame_StackPop()andPyStackRef_XCLOSE(ref)until the stack is emptyexception_unwind:PyStackRef_XCLOSE(ref)fails with an assertion error (Py_DECREF_MORTAL: Assertion!_Py_IsStaticImmortal(op)') becausestack_pointer[-1]` points to freed memoryLinked PRs