python / python/cpython

CPython crashes on memory-allocation failure (OOM): 35 findings (4 dups → 31 distinct)

Đang mở
#151,763 20 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

extension-modules interpreter-core type-crash
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

Crash report

What happened?

Allocation-failure fuzzing of CPython main turned up 35 distinct ways the interpreter
crashes
— segfault, failed assert(...), or Py_FatalError — when a memory allocation (4 have since been confirmed as duplicates via rr → 31 distinct; see the dedup comment)
fails part-way through an operation. Each is a separate underlying bug with a minimal,
stdlib-only reproducer, a backtrace, a root-cause analysis, and a suggested fix, published
as a self-contained gist (linked below).

I'm filing them under one umbrella so they can be picked off individually without flooding
the tracker with 35 issues at once. To take one: open a normal CPython issue (or PR) for
it and drop a comment here with the link — I'll mark it in the table. If any turn out to be
duplicates or non-bugs, say so and I'll annotate them.

These were found with fusil's OOM-injection mode
(fusil originally by @vstinner). The reports and reduced reproducers were drafted with
AI assistance (Claude Code) and then reviewed and re-verified by hand — see Disclosure.

Reproducing

  • Found on main (3.16.0a0) at commit 15d7406. These are allocation-path bugs, so they
    are not tied to that exact revision.
  • Every gist ships a minimal OOM-ID-repro.py (standard library only) that drives the failing path
    while _testcapi.set_nomemory(...) forces allocations to fail. Just run python OOM-ID-repro.py — where the exact failing-allocation index is sensitive to the build's
    allocation count, the repro self-sweeps (a fresh subprocess per index) and stops at the
    first crash, so no manual tuning is needed.
  • _testcapi.set_nomemory requires a test/debug interpreter (--with-pydebug exposes it).
  • Build matrix (in each report): many of these are debug-only assertion / Py_FatalError
    failures — the assert(...) is compiled out under -DNDEBUG, so a release build doesn't
    abort. They are still real bugs: on release the same defect is latent undefined behaviour
    (a use-after-free, a Py_DECREF(NULL), or a silently-lost exception). A subset segfault on
    release builds directly; those are called out below.

Highest-confidence starting points

These crash a release build (not just a debug assertion) and have a clean minimal
reproducer — the lowest-effort to confirm and fix:

  • OOM-0034, OOM-0028 — unchecked PyUnicode_AsUTF8 / PyUnicode_EncodeFSDefault
    returning NULL is dereferenced (≈ one-line NULL checks).
  • OOM-0001, OOM-0002, OOM-0014Py_DECREF(NULL) on an unchecked-allocation
    error path.
  • OOM-0031_interpreters.capture_exception calls _PyXI_FreeExcInfo(NULL) with no
    NULL guard.
  • OOM-0012, OOM-0020 — instrumentation / thread-state corruption under OOM.

Findings

Status: blank = not yet filed · #N = open issue · #N (PR) = open pull request · #N FIXED = filed and fixed (issue closed-completed or PR merged) · 🔁 = confirmed duplicate, folded into another finding — please don't pursue.

Segfaults (7)
Report Title Status
OOM-0001 Segfault: Py_DECREF of a NULL filename in do_warn (_warnings.c:1139) #151673
OOM-0002 Segfault: Py_DECREF(NULL) in PyContextVar_Set (context.c:367) #151773 FIXED
OOM-0024 Segfault: dealloc of uninitialized iterator in template_iter (templateobject.c:232) #151815
OOM-0028 Segfault: NULL deref in os__path_normpath_impl (posixmodule.c:6149) #152058 FIXED
OOM-0031 Segfault: NULL info deref in _excinfo_clear_type (crossinterp.c:1319) #151842 FIXED
OOM-0033 [SUPERSEDED — duplicate of OOM-0036 / #151818] Segfault / negative-refcount: over-decreffed sys.path entry in PyType_IsSubtype (typeobject.c:2931) 🔁 dup of OOM-0036 → #151818
OOM-0034 Segfault: unchecked PyUnicode_AsUTF8 NULL deref in pegen.c:33 #151798 (PR)
Assertion failures / aborts (23)
Report Title Status
OOM-0003 Abort: uninitialized _co_unique_id assert in code_dealloc (codeobject.c:2440) #152034 FIXED (PR)
OOM-0004 Abort/Segfault: corrupted object freelist in clear_freelist (object.c:909) #152125
OOM-0005 [SUPERSEDED — duplicate of OOM-0036 / #151818] Abort: negative-refcount over-decref in _PyFrame_ClearLocals (frame.c:101) 🔁 dup of OOM-0036 → #151818 (reported as #152147, closed as dup)
OOM-0006 Abort/Segfault: _PyObject_GC_UNTRACK assert on untracked iterator in dictiter_dealloc (dictobject.c:5532) #152107
OOM-0008 Abort: assert(!PyErr_Occurred()) in _PyType_LookupStackRefAndVersion (typeobject.c:6343) #151905 FIXED
OOM-0009 Abort: stale release1 flag trips an ownership assert in replace (unicodeobject.c:10783) #152228
OOM-0010 Abort: assert(_PyErr_Occurred(tstate)) in _PyEval_EvalFrameDefault (generated_cases.c.h:13817)
OOM-0011 [SUPERSEDED — duplicate of OOM-0008] Abort: assert(!PyErr_Occurred()) in specialize (specialize.c:364) 🔁 dup of OOM-0008 (above)
OOM-0012 Abort/Segfault: stale instrumentation in get_tools_for_instruction (instrumentation.c:1106)
OOM-0013 Abort: builtin breaks result/error contract in _Py_BuiltinCallFastWithKeywords_StackRef (ceval.c:843) #151968 FIXED (PR)
OOM-0014 Abort/Segfault: unchecked NULL in channelsmod__channel_id (_interpchannelsmodule.c:3487) #151902 (PR)
OOM-0015 Abort: stale exception in cfunction_check_kwargs (methodobject.c:409)
OOM-0016 Abort: assert(!queue->alive) in _queue_clear (_interpqueuesmodule.c:559) #152130
OOM-0017 Abort: negative gc_refs ("refcount too small") in validate_gc_objects (gc_free_threading.c:1116)
OOM-0018 Abort: ownership assert in set_keys (dictobject.c:205) #152254
OOM-0019 Abort: double-free in _PyPegen_raise_error_known_location (pegen_errors.c:363) #151931 (PR)
OOM-0025 Abort: assert(!PyErr_Occurred()) in unspecialize (specialize.c:378)
OOM-0026 Abort: err-code vs PyErr desync in handle_channel_error (_interpchannelsmodule.c:398 / :443) #152156 FIXED
OOM-0027 Abort: assert(PyStackRef_BoolCheck(cond)) in POP_JUMP_IF_FALSE (generated_cases.c.h:11120)
OOM-0029 [SUPERSEDED — duplicate of OOM-0036 / #151818] Abort: negative refcount on a MemoryError (tuple_dealloc, tupleobject.c:277) 🔁 dup of OOM-0036 → #151818
OOM-0030 Abort: Py_DECREF of NULL-data unicode in unicode_subtype_new (unicodeobject.c:13986)
OOM-0032 Abort: pending-exception assert from warn_explicit normalization (_warnings.c:799/806)
OOM-0035 Abort / malformed str: invalid maxchar in _PyUnicode_FromUCS4 (unicodeobject.c:2228)
Fatal Python error (5)
Report Title Status
OOM-0007 Fatal: context_tp_dealloc clears the pending exception (context.c:535) #152083
OOM-0020 Fatal: _PyMem_DebugRawFree: bad ID in free_threadstate (pystate.c:1527)
OOM-0021 Fatal: NULL returned without an exception set in _Py_CheckFunctionResult (call.c:43)
OOM-0022 Fatal: stale MemoryError trips _Py_CheckSlotResult in reload_singlephase_extension (import.c:2011)
OOM-0023 Fatal: dealloc clears the in-flight exception in subtype_dealloc (typeobject.c:2719)

Related groups (one fix may cover several)

  • Dealloc clears the in-flight exception (the gh-89373 _Py_Dealloc invariant): OOM-0007
    (context_tp_dealloc) and OOM-0023 (the generic subtype_dealloc, covering a family of
    pure-Python types) free an object while a MemoryError is pending and don't save/restore it.
  • A pending exception survives into code that asserts there is none (specializer / eval /
    call layers): OOM-0008, 0010, 0015, 0025, 0032 (OOM-0011 was a second detector face of
    OOM-0008 — same frame.f_back swallow — and is folded). Several share the theme that the
    adaptive specializer / call machinery is entered with a MemoryError already pending.
  • Py_DECREF/Py_CLEAR of a NULL or partially-initialized object on the OOM error path:
    OOM-0001, 0002, 0006, 0014, 0024, 0030, 0031 (an allocation fails after a slot is taken
    but before the object is valid, and the error path frees it anyway).
  • Over-decref → negative refcount under OOM (a real memory-safety bug; the assert is the
    debug-build detector): OOM-0019. (rr later showed OOM-0005, OOM-0029 and OOM-0033 are
    all the same _CALL_LIST_APPEND list.append()-under-MemoryError double-free —
    OOM-0036, filed #151818 — and are
    folded; OOM-0019 is a distinct pegen double-free.)
  • Free-threading-specific: OOM-0003 (_co_unique_id), OOM-0017 (cyclic GC),
    OOM-0018 (managed dict), OOM-0020 (thread-state reservation).

Disclosure & caveats

  • The reports and reduced reproducers were drafted with AI assistance (Claude Code); each
    gist carries an explicit disclaimer. The reproducers were re-run on the build matrix and the
    root causes audited against the CPython source before publishing.
  • A few root causes are explicitly marked partial — the trigger is minimal and verified,
    but the exact offending line wasn't pinned (noted in those reports): OOM-0010, 0027, 0035.
  • OOM-0001 is already filed as
    #151673; the other 34 had no matching
    python/cpython issue when checked and appear novel.

Found with fusil (OOM-injection mode; fusil originally
by Victor Stinner). Drafted with Claude Code; reproducers machine-generated and human-verified.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.16.0a0 free-threading build (heads/main:15d74068f3a, Jun 18 2026, 16:44:30) [Clang 22.1.2 (1ubuntu1)]

Linked PRs
  • gh-151779
  • gh-151798
  • gh-151902
  • gh-151931
  • gh-151968
  • gh-152013
  • gh-152034
  • gh-152069
  • gh-152070
  • gh-152094
  • gh-152095
  • gh-152096
  • gh-152131
  • gh-152155
  • gh-152158
  • gh-152159
  • gh-152160
  • gh-152164
  • gh-152165
  • gh-152836
  • gh-152837
  • gh-152840
  • gh-153548
  • gh-154019
  • gh-154717

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Chọn một phát hiện OOM chưa được xử lý trong bảng và mở gist được liên kết trước. Build một trình thông dịch kiểm thử/gỡ lỗi CPython với --with-pydebug, chạy OOM-ID-repro.py của gist, rồi kiểm tra vị trí mã nguồn và backtrace được nêu. Hoàn tất có nghĩa là tạo một issue hoặc PR tập trung cho phát hiện đó, sau đó thêm liên kết vào issue tổng hợp này.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
backend
Loại issue
Lỗi
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
35/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.