Skip to content

Segfault: NULL info deref in _excinfo_clear_type via _PyXI_FreeExcInfo(NULL) (Python/crossinterp.c:1319) #151842

Description

@amruthamodela06

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

importfaulthandler, _interpretersfaulthandler.enable()
from_testcapiimportset_nomemory, remove_mem_hooksforstartinrange(0, 40):
try:
set_nomemory(start, 0)
try:
_interpreters.capture_exception(Exception())
finally:
remove_mem_hooks()
exceptBaseException:
passfinally:
try: remove_mem_hooks()
exceptException: pass

Backtrace

#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

No one assigned

    Labels

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions