python / python/cpython

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

Abierto
#152,107 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

interpreter-core type-crash
Lenguaje dominante
Python
Estrellas
77.2k
Forks
35.9k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

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

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Empieza en Objects/dictobject.c, en dictiter_new() y dictiter_dealloc(), y después ejecuta los reproducer proporcionados _strptime y _testcapi.set_nomemory en una compilación con --with-pydebug. Comprueba el PR enlazado para ver el trabajo que ya está en curso. Se considera terminado cuando la ruta OOM del iterador de elementos ya no provoca un aborto ni corrompe el GC, y hay una prueba de regresión que cubre el fallo.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
c, python
Área
backend
Tipo de issue
Error
Dificultad
3/5
Tiempo estimado
1-2 días
Estado de actividad
Estancado
Claridad
Bien especificado
Aptitud para principiantes
25/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.