Repository navigation
Segfaults when accessing module state in tp_dealloc (itertools teedataobject clear) #115874
Copy link
Copy link
Closed
Closed
Copy link
Labels
3.12only security fixesonly security fixes3.13only security fixesonly security fixestype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
Crash report
What happened?
from dataclasses import dataclass
from itertools import tee
from typing import Optional
# if we remove @dataclass, then no segfault
@dataclass
class SomeDataClass:
pass
class SomeClass:
# if we remove Optional, then no segfault
_value: Optional[SomeDataClass]
def __init__(self, it):
self._it = it
def prepare_segfault(self):
(lhs, _) = tee(self._it)
# if we don't assign lhs to self._it, then no segfault
self._it = lhs
# if some_object isn't bound at the top-level scope, then no segfault
some_object = SomeClass(iter("testing"))
some_object.prepare_segfault()Running the file from the terminal with python3.12 minimal.py is sufficient. When the interpreter exits, it segfaults.
Crash does not occur in python 3.8-3.11, but does occur in 3.12 and 3.13. Crash not observed on Windows with 3.12.
Backtrace:
#0 0x00007fc2c1a72e01 in teedataobject_clear (tdo=tdo@entry=0x7fc2b3ed2040) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/itertoolsmodule.c:836
#1 0x00007fc2c1a72d49 in teedataobject_dealloc (tdo=0x7fc2b3ed2040) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/itertoolsmodule.c:845
#2 0x00007fc2c1b06a0e in Py_DECREF (op=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Include/object.h:706
#3 tee_clear (to=to@entry=0x7fc2b3c13b00) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/itertoolsmodule.c:1050
#4 0x00007fc2c1b069ac in tee_dealloc (to=0x7fc2b3c13b00) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/itertoolsmodule.c:1059
#5 0x00007fc2c1a390d8 in _Py_Dealloc (op=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Objects/object.c:2608
#6 Py_DECREF (op=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Include/object.h:706
#7 Py_XDECREF (op=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Include/object.h:799
#8 _PyObject_FreeInstanceAttributes (self=0x7fc2b3daf1d0) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Objects/dictobject.c:5571
#9 subtype_dealloc (self=0x7fc2b3daf1d0) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Objects/typeobject.c:2017
#10 0x00007fc2c19f5440 in _Py_Dealloc (op=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Objects/object.c:2625
#11 Py_DECREF (op=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Include/object.h:706
#12 Py_XDECREF (op=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Include/object.h:799
#13 free_keys_object (interp=0x7fc2c1df0d48 <_PyRuntime+76392>, keys=0x7fc2b3d7a100) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Objects/dictobject.c:673
#14 0x00007fc2c1aa72bd in dict_tp_clear (op=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Objects/dictobject.c:3564
#15 0x00007fc2c1a02cfa in delete_garbage (old=0x7fc2c1df0e00 <_PyRuntime+76576>, collectable=0x7ffcdfc26270, gcstate=0x7fc2c1df0db8 <_PyRuntime+76504>, tstate=0x7fc2c1e4e668 <_PyRuntime+459656>)
at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/gcmodule.c:1029
#16 gc_collect_main (tstate=0x7fc2c1e4e668 <_PyRuntime+459656>, generation=generation@entry=2, n_collected=n_collected@entry=0x0, n_uncollectable=n_uncollectable@entry=0x0, nofail=nofail@entry=1)
at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/gcmodule.c:1303
#17 0x00007fc2c1abe201 in _PyGC_CollectNoFail (tstate=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/gcmodule.c:2135
#18 0x00007fc2c1aaacda in Py_FinalizeEx () at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Python/pylifecycle.c:1889
#19 0x00007fc2c1ab96c9 in Py_RunMain () at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/main.c:711
#20 0x00007fc2c1a74f5c in Py_BytesMain (argc=<optimized out>, argv=<optimized out>) at /usr/src/debug/python3.12-3.12.1-2.fc39.x86_64/Modules/main.c:763
#21 0x00007fc2c164614a in __libc_start_call_main (main=main@entry=0x56033de41160 <main>, argc=argc@entry=2, argv=argv@entry=0x7ffcdfc26698) at ../sysdeps/nptl/libc_start_call_main.h:58
#22 0x00007fc2c164620b in __libc_start_main_impl (main=0x56033de41160 <main>, argc=2, argv=0x7ffcdfc26698, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffcdfc26688)
at ../csu/libc-start.c:360
#23 0x000056033de41095 in _start ()
CPython versions tested on:
3.12
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.12.1 (main, Dec 18 2023, 00:00:00) [GCC 13.2.1 20231205 (Red Hat 13.2.1-6)]
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.12only security fixesonly security fixes3.13only security fixesonly security fixestype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Activity
JelleZijlstra commentedon Feb 24, 2024
Slightly different crash on current main on macOS. It fails an assertion
Assertion failed: (et->ht_module), function _PyType_GetModuleState, file pycore_typeobject.h, line 110.with the following stack trace:The crash happens during finalization. Apparently the module has already been finalized but the
teetype still exists.cc @erlend-aasland who worked on itertools module state (e.g., #101477).
JelleZijlstra commentedon Feb 24, 2024
The crash goes away if I remove both of the
copyreg.picklecalls here:cpython/Lib/typing.py
Line 1797 in 200271c
erlend-aasland commentedon Feb 24, 2024
That sounds really strange. The (heap) type keeps a strong reference to the module.
brandtbucher commentedon Feb 27, 2024
Tiny reproducer:
Here's essentially the same thing, but also clearing every reference I can think of.
brandtbucher commentedon Feb 27, 2024
One thing I'm curious about (maybe others with GC or multi-phase init expertise can shed some light): how is the "type -> module -> type" cycle handled for these types?
More concretely, it seems like there could be a few cycles here at shutdown:
Faced with these three cycles, it seems like it's only "correct" to clear the third, before moving on of the other two. But there's nothing stopping the GC from "picking" one of the other two to clear instead, which leads to this nasty situation. So it seems like relying on module state in
tp_clear, as the teedataobject type does, is incorrect.Am I missing something?
brandtbucher commentedon Feb 27, 2024
Confirming my hunch, the following patch fixes this for me:
I also think I found an unrelated refleak while chasing this down:
brandtbucher commentedon Feb 27, 2024
I can open a PR for this tomorrow if others confirm that my understanding is correct.
em9797b commentedon Feb 27, 2024
I don't know enough to comment on the GC, but I can confirm that, for me:
cjwatson commentedon Feb 27, 2024
I ran into this while trying to track down https://bugs.debian.org/1063345 (a test failure in Debian's
celerypackage) and confirmed that applying @brandtbucher's first patch from #115874 (comment) to Debian'spython3.12package is enough to fix the crash. Thanks!brandtbucher commentedon Feb 27, 2024
I scanned through the other modules.
FutureIter_deallochas a similar issue, where the module state is used to add the currentfutureiterobjectto a free list. That one seems less easy to fix:https://github.com/python/cpython/blob/main/Modules/_asynciomodule.c#L1600-L1617
brandtbucher commentedon Feb 27, 2024
Here's a tiny reproducer for the
FutureItercase:Commenting out the
statestuff inFutureIter_deallocfixes the crash.10 remaining items
[-]Segfaults when accessing module state in `tp_dealloc`[/-][+]Segfaults when accessing module state in `tp_dealloc` (itertools teedataobject clear)[/+]vstinner commentedon Mar 27, 2024
I found a very similar bug in the tmt project: teemtee/tmt#2501 (comment). It's good to know that it has already been fixed :-)
tmt crash: the Python crash occurs in 3 steps:
itertoolsmodule_clear()is called: itertools_state is cleared, it stores strong references to itertools typestype_clear()is called onitertools._tee_dataobjecttype => setht_moduletoNULL.teedataobject_clear()is called on anitertools._tee_dataobjectinstance => assertion fails sinceht_moduleisNULL.erlend-aasland commentedon Mar 27, 2024
Do you think it is possible to get the fix merged in time for the next 3.12 release?
savannahostrowski commentedon Apr 8, 2024
AFACT, this no longer repros on main but I'm not aware of any fixes that landed addressing the segfault.
erlend-aasland commentedon Apr 9, 2024
I bisected it down to commit 8bef34f, which modified the garbage collector. Also,
3.12still crashes, so I still think the bug should be fixed both inmainand3.12.pythongh-115874: Don't use module state in teedataobject tp_dealloc (p…
GH-115874: Fix segfault in FutureIter_dealloc (GH-117741)
pythonGH-115874: Fix segfault in FutureIter_dealloc (pythonGH-117741)
[3.12] GH-115874: Fix segfault in FutureIter_dealloc (GH-118114)
vstinner commentedon Apr 29, 2024
I suggest to open a new issue since this one is closed.
erlend-aasland commentedon Apr 29, 2024
It was fixed by #117741 and #118114. No need to open a new issue.
Don't rely on module state in teedataobject_clear (from Brandt Bucher…
vstinner commentedon Aug 6, 2024
See also the similar issue gh-122695.
Import Debian changes 3.13.2-3