From 3c8147563634f921ece11e450a680f5518ff0302 Mon Sep 17 00:00:00 2001 From: ZeroIntensity Date: Thu, 8 Aug 2024 15:06:18 -0400 Subject: [PATCH 1/5] Backport #122834 for 3.13 --- .../2024-08-08-15-05-58.gh-issue-122695.f7pwBv.rst | 2 ++ Modules/_asynciomodule.c | 12 ++---------- 2 files changed, 4 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-08-08-15-05-58.gh-issue-122695.f7pwBv.rst diff --git a/Misc/NEWS.d/next/Library/2024-08-08-15-05-58.gh-issue-122695.f7pwBv.rst b/Misc/NEWS.d/next/Library/2024-08-08-15-05-58.gh-issue-122695.f7pwBv.rst new file mode 100644 index 00000000000000..cc6bc38f419462 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-08-08-15-05-58.gh-issue-122695.f7pwBv.rst @@ -0,0 +1,2 @@ +Fixed double-free when using :func:`gc.get_referents` with a freed +:class:`asyncio.Future` iterator. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 6e87de5e954826..94895f7122d715 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3530,16 +3530,8 @@ module_traverse(PyObject *mod, visitproc visit, void *arg) Py_VISIT(state->context_kwname); -#ifndef Py_GIL_DISABLED - // Visit freelist. - PyObject *next = (PyObject*) state->fi_freelist; - while (next != NULL) { - PyObject *current = next; - Py_VISIT(current); - next = (PyObject*) ((futureiterobject*) current)->future; - } -#endif - + // GH-122695: Do not traverse the freelist here, as that can cause problems + // with gc.get_referents() return 0; } From 7fb1041af8a59e77e9ed699c6aebf3edbc3bc8ae Mon Sep 17 00:00:00 2001 From: ZeroIntensity Date: Thu, 8 Aug 2024 15:12:40 -0400 Subject: [PATCH 2/5] Add test. --- Lib/test/test_asyncio/test_futures.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 458b70451a306a..8601dc05c0ba5c 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -675,6 +675,13 @@ def test_future_del_segfault(self): with self.assertRaises(AttributeError): del fut._log_traceback + def test_future_iter_get_referents_segfault(self): + # See https://github.com/python/cpython/issues/122695 + import _asyncio + it = iter(self._new_future(loop=self.loop)) + del it + evil = gc.get_referents(_asyncio) + @unittest.skipUnless(hasattr(futures, '_CFuture'), 'requires the C _asyncio module') From 76857f522157552690de3d0ca495944766c40ca3 Mon Sep 17 00:00:00 2001 From: ZeroIntensity Date: Fri, 9 Aug 2024 06:06:18 -0400 Subject: [PATCH 3/5] Force garbage collection in unit test. --- Lib/test/test_asyncio/test_futures.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 8601dc05c0ba5c..dec703891c230a 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -680,6 +680,7 @@ def test_future_iter_get_referents_segfault(self): import _asyncio it = iter(self._new_future(loop=self.loop)) del it + gc.collect() evil = gc.get_referents(_asyncio) From 044e4ff11727c7bdc958e6cc669ca441b6a47cc0 Mon Sep 17 00:00:00 2001 From: ZeroIntensity Date: Fri, 9 Aug 2024 06:06:46 -0400 Subject: [PATCH 4/5] Remove redundant comment. --- Modules/_asynciomodule.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 94895f7122d715..3babd33eaabf45 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3529,9 +3529,6 @@ module_traverse(PyObject *mod, visitproc visit, void *arg) Py_VISIT(state->iscoroutine_typecache); Py_VISIT(state->context_kwname); - - // GH-122695: Do not traverse the freelist here, as that can cause problems - // with gc.get_referents() return 0; } From 3cd6b1ab64f22e3dc668487f81c72a4729f343fd Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 9 Aug 2024 06:51:04 -0400 Subject: [PATCH 5/5] Move collection call. --- Lib/test/test_asyncio/test_futures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index dec703891c230a..2417712a9c9b64 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -680,8 +680,8 @@ def test_future_iter_get_referents_segfault(self): import _asyncio it = iter(self._new_future(loop=self.loop)) del it - gc.collect() evil = gc.get_referents(_asyncio) + gc.collect() @unittest.skipUnless(hasattr(futures, '_CFuture'),