Repository navigation
Segfault: NULL info deref in _excinfo_clear_type via _PyXI_FreeExcInfo(NULL) (Python/crossinterp.c:1319) #151842
Description
Crash report
Segfault: NULL info deref in _excinfo_clear_type via _PyXI_FreeExcInfo(NULL)
_interpreters.capture_exception() builds a _PyXI_excinfo via _PyXI_NewExcInfo(). Under memory pressure that allocation can fail and return NULL. The cleanup path in _interpreters_capture_exception_impl then unconditionally calls _PyXI_FreeExcInfo(info) with info == NULL. _PyXI_FreeExcInfo has no NULL guard, so _PyXI_excinfo_clear → _excinfo_clear_type dereferences &NULL->type (offset 0) at Python/crossinterp.c:1319 → SIGSEGV.
This reproduces on all build configurations (a NULL dereference, not a debug-only assert).
Reproducer
import faulthandler, _interpreters
faulthandler.enable()
from _testcapi import set_nomemory, remove_mem_hooks
for start in range(0, 40):
try:
set_nomemory(start, 0)
try:
_interpreters.capture_exception(Exception())
finally:
remove_mem_hooks()
except BaseException:
pass
finally:
try: remove_mem_hooks()
except Exception: passBacktrace
#0 _excinfo_clear_type Python/crossinterp.c:1319 # info == 0x0 -> offset-0 deref
#1 _PyXI_excinfo_clear Python/crossinterp.c:1374
#2 _PyXI_FreeExcInfo Python/crossinterp.c:1712 # called with info == NULL, no guard
#3 _interpreters_capture_exception_impl Modules/_interpretersmodule.c:1544
#4 cfunction_vectorcall_FASTCALL_KEYWORDS Objects/methodobject.c:465
Root cause
Modules/_interpretersmodule.c, _interpreters_capture_exception_impl (≈L1522-1544):
_PyXI_excinfo *info = _PyXI_NewExcInfo(exc); /* returns NULL under OOM */
if (info == NULL) {
goto finally;
}
/* ... */
finally:
_PyXI_FreeExcInfo(info); /* L1544: runs even when info == NULL */_PyXI_NewExcInfo (crossinterp.c:1683) does PyMem_RawCalloc for its struct and, on any failure, PyMem_RawFrees it and returns NULL — so info here is a clean NULL, not a dangling half-built struct.
_PyXI_FreeExcInfo (crossinterp.c:1710) has no NULL guard:
void
_PyXI_FreeExcInfo(_PyXI_excinfo *info)
{
_PyXI_excinfo_clear(info); /* dereferences info */
PyMem_RawFree(info);
}Suggested fix
Make _PyXI_FreeExcInfo NULL-safe, matching the surrounding PyMem_RawFree(NULL) idiom (and Py_XDECREF, free(), etc.):
void
_PyXI_FreeExcInfo(_PyXI_excinfo *info)
{
if (info == NULL) {
return;
}
_PyXI_excinfo_clear(info);
PyMem_RawFree(info);
}This is more defensive than guarding only the one call site, since the function is part of the cross-interpreter exception ABI and any future caller would otherwise have the same hazard.
Notes
Part of #151763 (umbrella tracking 35 OOM-related crash findings); OOM-0031 in that table.
CPython versions tested on:
CPython main branch (3.16.0a0)
Operating systems tested on:
Linux, Windows
Linked PRs
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
gh-151842: Fix crash upon OOM in `_interpreters.capture_exception` (G…
[3.14] gh-151842: Fix crash upon OOM in `_interpreters.capture_except…
[3.15] gh-151842: Fix crash upon OOM in `_interpreters.capture_except…
pythongh-151842: Fix crash upon OOM in `_interpreters.capture_excepti…