From 8580b6a1e893fec3f55dc0e4f0a3a2e5a20e1560 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 18 Jul 2026 16:36:49 +0100 Subject: [PATCH 1/2] Fix use after free bug itertools.count() Obtain a reference to lz->long_cnt, lz->long_step such that we don't hit a use after free --- .../test_free_threading/test_itertools.py | 28 +++++++++++ Modules/itertoolsmodule.c | 50 +++++++++++++------ 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_free_threading/test_itertools.py b/Lib/test/test_free_threading/test_itertools.py index 7392bd739acd709..afcf77c8d3bad3f 100644 --- a/Lib/test/test_free_threading/test_itertools.py +++ b/Lib/test/test_free_threading/test_itertools.py @@ -4,6 +4,7 @@ batched, chain, combinations_with_replacement, + count, cycle, permutations, tee, @@ -142,5 +143,32 @@ def consume(): self.assertEqual(errors, [], msg=f"unexpected errors: {errors}") +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,)) + + if __name__ == "__main__": unittest.main() diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index c0023c839ca7fe3..5092ea3d90268ca 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -3675,27 +3675,45 @@ static PyObject * count_repr(PyObject *op) { countobject *lz = countobject_CAST(op); - if (lz->long_cnt == NULL) { + PyObject *long_cnt, *long_step, *result; + + Py_BEGIN_CRITICAL_SECTION(lz); + long_cnt = Py_XNewRef(lz->long_cnt); + long_step = Py_XNewRef(lz->long_step); + Py_END_CRITICAL_SECTION(); + + if (long_cnt == NULL) { + /* Fast mode: cnt is advanced by count_next()'s lock-free atomic CAS, + which never takes this critical section, so read it atomically. */ Py_ssize_t cnt = FT_ATOMIC_LOAD_SSIZE_RELAXED(lz->cnt); - return PyUnicode_FromFormat("%s(%zd)", - _PyType_Name(Py_TYPE(lz)), cnt); + result = PyUnicode_FromFormat("%s(%zd)", + _PyType_Name(Py_TYPE(lz)), cnt); } - - if (PyLong_Check(lz->long_step)) { - long step = PyLong_AsLong(lz->long_step); - if (step == -1 && PyErr_Occurred()) { - PyErr_Clear(); - } - if (step == 1) { + else { + int hide_step = 0; + if (PyLong_Check(long_step)) { + long step = PyLong_AsLong(long_step); + if (step == -1 && PyErr_Occurred()) { + PyErr_Clear(); + } /* Don't display step when it is an integer equal to 1 */ - return PyUnicode_FromFormat("%s(%R)", - _PyType_Name(Py_TYPE(lz)), - lz->long_cnt); + hide_step = (step == 1); + } + if (hide_step) { + result = PyUnicode_FromFormat("%s(%R)", + _PyType_Name(Py_TYPE(lz)), + long_cnt); + } + else { + result = PyUnicode_FromFormat("%s(%R, %R)", + _PyType_Name(Py_TYPE(lz)), + long_cnt, long_step); } } - return PyUnicode_FromFormat("%s(%R, %R)", - _PyType_Name(Py_TYPE(lz)), - lz->long_cnt, lz->long_step); + + Py_XDECREF(long_cnt); + Py_XDECREF(long_step); + return result; } static PyType_Slot count_slots[] = { From 31f322fc8237ac6b50d9d6204d4580c0af813bb0 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:47:53 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-07-18-14-47-52.gh-issue-153981.yZcmKQ.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-18-14-47-52.gh-issue-153981.yZcmKQ.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-18-14-47-52.gh-issue-153981.yZcmKQ.rst b/Misc/NEWS.d/next/Library/2026-07-18-14-47-52.gh-issue-153981.yZcmKQ.rst new file mode 100644 index 000000000000000..6b82ceb605fd1d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-18-14-47-52.gh-issue-153981.yZcmKQ.rst @@ -0,0 +1 @@ +Fix use after free race condition when calling :func:`repr` on :class:`itertools.count` for large integers (slow mode) under the :term:`free-threaded build`.