Skip to content

dir(module) can call a freed __dir__ object after concurrent replacement #157115

Description

@Nievesjyl

Description

module_dir() retrieves a borrowed __dir__ value from the module dictionary and calls it without retaining the callable or holding the dictionary's critical section. A concurrent assignment can remove the dictionary's last reference to the selected callable before dispatch.

Observed Behavior

On a free-threaded ASan/assert build, the reproducer crashes in under one second during stale callable dispatch from _PyObject_CallNoArgs() at Objects/moduleobject.c:1196. The same executable completes normally 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 CONCURDEP_DURATION=20 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
import types


duration = float(os.environ.get("CONCURDEP_DURATION", "20"))
stop = threading.Event()
module = types.ModuleType("race_module")
counts = [0] * 9


class Callable:
    __slots__ = ("padding",)

    def __init__(self):
        self.padding = bytearray(4096)

    def __call__(self):
        return ["value"]


module.__dir__ = Callable()


def mutate():
    i = 0
    while not stop.is_set():
        module.__dir__ = Callable()
        i += 1
        counts[0] = i


def inspect(slot):
    while not stop.is_set():
        try:
            dir(module)
        except (TypeError, AttributeError):
            pass
        counts[slot] += 1


threads = [threading.Thread(target=mutate)]
threads += [threading.Thread(target=inspect, 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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions