Skip to content

itertoolsmodule: free-threading use after free bug in counting slow mode #153981

Description

@johng

Bug report

Bug description:

https://github.com/python/cpython/blob/main/Modules/itertoolsmodule.c#L3663

During next we swap out the pointer for lz->long_cnt inside count_nextlong. This can cause a use after free bug as shown with a new test. Running this is TSAN mode exposes this reliably.

ThreadSanitizer can not provide additional info.
SUMMARY: ThreadSanitizer: SEGV object.c:766 in PyObject_Repr
==83915==ABORTING
[1] 83915 abort PYTHON_GIL=0 ./python.exe -m unittest -v

class TestCountConcurrent(unittest.TestCase):
    @staticmethod
    def _spin_next(it, n=2000):
        for _ in range(n):
            next(it)

    @staticmethod
    def _spin_repr(it, n=2000):
        for _ in range(n):
            repr(it)

    @threading_helper.reap_threads
    def test_repr_racing_next_fast_mode(self):
        for _ in range(10):
            it = count()
            workers = [self._spin_next] * 2 + [self._spin_repr] * 4
            threading_helper.run_concurrently(workers, args=(it,))

    @threading_helper.reap_threads
    def test_repr_racing_next_slow_mode(self):
        for _ in range(10):
            # Large count to trigger "slow mode"
            it = count(10**18, 2)
            workers = [self._spin_next] * 2 + [self._spin_repr] * 4
            threading_helper.run_concurrently(workers, args=(it,))

To fix I believe we need to hold a critical section reference in repr to fetching a reference to long_cnt

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Jul 18, 2026
  2. added a commit that references this issue on Jul 18, 2026
  3. devdanzin commented on Jul 19, 2026

    @devdanzin
    Member

    Confirming this independently. fusil --tsan (a ThreadSanitizer fuzzer) surfaced the same slow-mode use-after-free, which I'd reported as a follow-up on the fast-mode issue (gh-153908) just before being pointed at this issue and your PR.

    The mechanism matches: in slow (big-int) mode count_repr borrows lz->long_cnt and repr()s it (the if (lz->long_cnt == NULL) mode check + PyUnicode_FromFormat("%R", …, lz->long_cnt)) with no synchronization, while count_nextlong does lz->long_cnt = stepped_up under Py_BEGIN_CRITICAL_SECTION(lz) and returns the old value — which the next() caller then DECREFs, so count_repr can repr() a freed object. TSan flags it as a data race on lz->long_cnt (count_nextlong vs count_repr), and when the free wins it's a SEGV in PyObject_Repr (matching your SEGV object.c:766). Reproduced deterministically on a free-threaded --with-thread-sanitizer build of current main; fast-mode count() is clean.

    Repro:

    import itertools, threading
    NT = 8
    for _ in range(3000):
        it = itertools.count(10**18, 2)          # big-int -> slow (long_cnt) mode
        bar = threading.Barrier(NT)
        def work(advance, it=it, bar=bar):
            bar.wait()
            for _ in range(400):
                next(it) if advance else repr(it)
        ts = [threading.Thread(target=work, args=(i % 2 == 0,)) for i in range(NT)]
        for t in ts: t.start()
        for t in ts: t.join()

    PR #153983 looks like the correct fix: snapshotting long_cnt/long_step with Py_XNewRef inside Py_BEGIN_CRITICAL_SECTION(lz) and formatting from the locals addresses both halves — the data race (the reads are now under the same critical section count_nextlong writes under) and the use-after-free (the strong reference keeps long_cnt alive across the repr()). A bare atomic load of the pointer would have closed the race but not the UAF, so the strong-ref snapshot is the key part.

    (Found by fusil --tsan, a ThreadSanitizer fuzzer; comment + reproducer by Claude Code, reviewed by hand.)

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

    extension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions