python / python/cpython

`dict` item-iterator: `_PyObject_GC_UNTRACK` on never-tracked iterator under OOM (`dictobject.c`)

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

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

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ả

Creating a dict item-iterator (iter(d.items()) or reversed(d.items())) while
allocations are failing aborts a debug build with:

Objects/dictobject.c:5532: _PyObject_GC_UNTRACK:
    Assertion "_PyObject_GC_IS_TRACKED(op)" failed:
    object not tracked by the garbage collector
object type name: dict_itemiterator
Fatal Python error: _PyObject_AssertFailed

dictiter_new() allocates the iterator with PyObject_GC_New() but only calls
_PyObject_GC_TRACK(di) at the very end. For item iterators it first allocates
di->di_result via _PyTuple_FromPairSteal(). If that allocation fails (OOM), the
error path runs Py_DECREF(di) on the still-untracked iterator, and
dictiter_dealloc() unconditionally calls _PyObject_GC_UNTRACK(di).

  • Debug build: the _PyObject_GC_IS_TRACKED assertion fires → Py_FatalError / abort.
  • Release build (NDEBUG): the assert is compiled out and untracking a never-tracked
    object corrupts the GC list, leading to a later segfault in _Py_Dealloc.

Only item iterators are affected (dict_itemiterator / dict_reverseitemiterator) —
they are the only dict iterators whose di_result requires an allocation between
PyObject_GC_New and _PyObject_GC_TRACK.

This was found via allocation-failure fuzzing as OOM-0006 in the umbrella issue
#151763. Filing a focused issue per that issue's process.

Reproducer

Minimal, stdlib-only, deterministic (requires a --with-pydebug interpreter so
_testcapi.set_nomemory is available):

import faulthandler, _strptime
faulthandler.enable()
from _testcapi import set_nomemory
for start in range(60):
    set_nomemory(start)
    try:
        _strptime._strptime("", "")
    except BaseException:
        pass
print("done, no crash")

_strptime._strptime("", "") performs an iter(d.items()) once the size-2 tuple
freelist is drained, so _PyTuple_FromPairSteal() reaches the failing allocator and
drives the dictiter_new() OOM error path. (Reducer by @devdanzin; gist:
https://gist.github.com/devdanzin/c809eb4072c0c787c0c890f54ba1c843)

Backtrace (abbreviated)
_PyObject_AssertFailed                       Objects/object.c
_PyObject_GC_UNTRACK                          Include/internal/pycore_gc.h:254
dictiter_dealloc (self=dict_itemiterator)     Objects/dictobject.c:5532
_Py_Dealloc                                   Objects/object.c
Py_DECREF                                     Include/refcount.h
dictiter_new (itertype=PyDictIterItem_Type)   Objects/dictobject.c   <- Py_DECREF(di) after di_result alloc fails
PyObject_GetIter                              Objects/abstract.c
Suggested fix

Either guard the untracked against the never-tracked error path:

if (_PyObject_GC_IS_TRACKED(di)) {
    _PyObject_GC_UNTRACK(di);
}

or move _PyObject_GC_TRACK(di) ahead of the fallible di_result allocation (after
initializing di->di_result = NULL so traverse/dealloc stay safe). A PR with the first
approach and a regression test is ready.

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

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

No response

Linked PRs
  • gh-152110

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

Bắt đầu trong Objects/dictobject.c tại dictiter_new() và dictiter_dealloc(), sau đó chạy các reproducer được cung cấp là _strptime và _testcapi.set_nomemory trên một bản build --with-pydebug. Kiểm tra PR được liên kết để xem phần công việc đang được thực hiện. Được coi là hoàn tất khi đường dẫn OOM của item-iterator không còn gây abort hoặc làm hỏng GC, đồng thời có một regression test bao phủ lỗi 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ệ
c, python
Lĩnh vực
backend
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
25/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.