Description
OSError.__reduce__() reads self->args and its elements without taking the exception's critical section or creating an owned snapshot. Concurrent assignment through the public BaseException.args setter can replace and destroy the tuple or its elements between those reads.
Observed Behavior
On a free-threaded ASan/assert build, the reproducer aborts in under one second with a negative reference count on an object already marked freed while workers call error.__reduce__(). The same executable completes about 1.5 million reductions with the compatibility GIL enabled.
Affected Version
CPython 3.14.7 at commit 823f0323ee6ec1402088b73bce1a38473cac36dc, built with ASan, assertions, and free-threading support.
Reproduction
Run:
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0 PYTHON_GIL=0 python3.14 poc/reproduce.py
For comparison, run the same command with PYTHON_GIL=1.
PoC Source Code
poc/reproduce.py:
import os
import threading
import time
duration = float(os.environ.get("CONCURDEP_DURATION", "10"))
error = OSError(2, "message", "filename")
stop = threading.Event()
counts = [0] * 9
errors = []
class Token:
__slots__ = ("value",)
def __init__(self, value):
self.value = value
def mutate():
i = 0
while not stop.is_set():
error.args = (Token(i), Token(i + 1))
error.args = (i, "message")
i += 2
counts[0] = i
def reduce(slot):
try:
while not stop.is_set():
value = error.__reduce__()
if len(value) < 2:
raise AssertionError(value)
counts[slot] += 1
except BaseException as exc:
errors.append(repr(exc))
stop.set()
threads = [threading.Thread(target=mutate)]
threads += [threading.Thread(target=reduce, args=(i,)) for i in range(1, 9)]
for thread in threads:
thread.start()
time.sleep(duration)
stop.set()
for thread in threads:
thread.join()
print("counts", counts, "errors", errors)
Description
OSError.__reduce__()readsself->argsand its elements without taking the exception's critical section or creating an owned snapshot. Concurrent assignment through the publicBaseException.argssetter can replace and destroy the tuple or its elements between those reads.Observed Behavior
On a free-threaded ASan/assert build, the reproducer aborts in under one second with a negative reference count on an object already marked freed while workers call
error.__reduce__(). The same executable completes about 1.5 million reductions with the compatibility GIL enabled.Affected Version
CPython 3.14.7 at commit
823f0323ee6ec1402088b73bce1a38473cac36dc, built with ASan, assertions, and free-threading support.Reproduction
Run:
For comparison, run the same command with
PYTHON_GIL=1.PoC Source Code
poc/reproduce.py: