-
-
Notifications
You must be signed in to change notification settings - Fork 34.2k
Description
Crash report
What happened?
Description
struct.Struct.__init__() can be called on an already-initialized Struct with a different format, which frees and replaces s_codes without any lock. Concurrent calls to unpack() or pack() read s_codes without any lock, producing a seg fault.
_struct.c declares Py_MOD_GIL_NOT_USED but has zero @critical_section annotations and no atomic operations on s_codes, s_size, or s_len.
Reproducer Code
import sys
import threading
import warnings
if sys._is_gil_enabled():
sys.exit("SKIP: requires --disable-gil build")
s = struct.Struct('I')
def reader():
for _ in range(5_000_000):
try:
s.unpack(b'\x00\x00\x00\x00')
except Exception:
pass
def reinit():
with warnings.catch_warnings():
warnings.simplefilter("ignore", FutureWarning)
for _ in range(1_000_000):
s.__init__('i') # different format -> frees s_codes
s.__init__('I') # back again -> frees s_codes again
readers = [threading.Thread(target=reader) for _ in range(4)]
writers = [threading.Thread(target=reinit) for _ in range(4)]
for t in writers + readers:
t.start()
for t in writers + readers:
t.join()
print("Completed without crash.")
Test Output
[1] 24871 segmentation fault ./python3 /tmp/test_struct_race.py
Note
Re-initialization of Struct is already deprecated (FutureWarning) and will be removed in a future version, which would close this race. However
I was able to trigger the crash today.
CPython versions tested on:
3.15
Operating systems tested on:
macOS
Output from running 'python -VV' on the command line:
Python 3.15.0a7+ free-threading build (heads/main:e167e06f8c6, Mar 15 2026, 09:14:39) [Clang 17.0.0 (clang-1700.6.4.2)]