https://github.com/python/cpython/commit/f674db18032be549773094ff8ae4225f7acfa29a
commit: f674db18032be549773094ff8ae4225f7acfa29a
branch: main
author: Abhishek Tiwari <[email protected]>
committer: nascheme <[email protected]>
date: 2026-10-12T11:33:36-07:00
summary:

gh-149816: Fix race in subtype_getweakref in free-threading build (gh-150247)

files:
A Lib/test/test_free_threading/test_weakref.py
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-05-22-17-10-29.gh-issue-149816.u1-iqT.rst
M Objects/typeobject.c

diff --git a/Lib/test/test_free_threading/test_weakref.py 
b/Lib/test/test_free_threading/test_weakref.py
new file mode 100644
index 000000000000000..7d047244828f6b9
--- /dev/null
+++ b/Lib/test/test_free_threading/test_weakref.py
@@ -0,0 +1,92 @@
+"""Regression tests for weakref races in subtype_getweakref() (gh-149816)."""
+
+import gc
+import sys
+import unittest
+import weakref
+
+from test.support import threading_helper
+
+
+@threading_helper.requires_working_threading()
+class TestWeakrefRaces(unittest.TestCase):
+
+    ITERATIONS = 5_000
+
+    def test_getweakref_no_crash(self):
+        """obj.__weakref__ racing with concurrent weakref 
creation/destruction."""
+        class Target:
+            pass
+
+        obj = Target()
+
+        def reader():
+            for _ in range(self.ITERATIONS):
+                ref = obj.__weakref__
+                self.assertIn(type(ref), (type(None), weakref.ref))
+                if ref is not None:
+                    self.assertIn(ref(), (None, obj))
+
+        def mutator():
+            for _ in range(self.ITERATIONS):
+                weakref.ref(obj, lambda _: None)
+
+        threading_helper.run_concurrently([reader, mutator])
+
+    def test_getweakref_return_type(self):
+        """__weakref__ must return None or a live weakref.ref."""
+        class Target:
+            pass
+
+        obj = Target()
+
+        self.assertIsNone(obj.__weakref__)
+
+        ref = weakref.ref(obj)
+        result = obj.__weakref__
+        self.assertIsInstance(result, weakref.ref)
+        self.assertIs(result(), obj)
+
+        del ref, result
+        gc.collect()
+        self.assertIsNone(obj.__weakref__)
+
+    def test_getweakref_no_refcount_leak(self):
+        """Each __weakref__ access must not inflate the weakref's refcount."""
+        class Target:
+            pass
+
+        obj = Target()
+        ref = weakref.ref(obj)
+        before = sys.getrefcount(ref)
+
+        for _ in range(1_000):
+            r = obj.__weakref__
+            del r
+
+        gc.collect()
+        self.assertEqual(sys.getrefcount(ref), before)
+
+    def test_getweakref_many_readers(self):
+        """Multiple concurrent readers and mutators must not crash."""
+        class Target:
+            pass
+
+        obj = Target()
+        n = 4
+        iters = self.ITERATIONS // n
+
+        def reader():
+            for _ in range(iters):
+                ref = obj.__weakref__
+                self.assertIn(type(ref), (type(None), weakref.ref))
+
+        def mutator():
+            for _ in range(iters):
+                weakref.ref(obj, lambda _: None)
+
+        threading_helper.run_concurrently([reader] * n + [mutator] * n)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-22-17-10-29.gh-issue-149816.u1-iqT.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-22-17-10-29.gh-issue-149816.u1-iqT.rst
new file mode 100644
index 000000000000000..0faa5f7b39a415c
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-22-17-10-29.gh-issue-149816.u1-iqT.rst
@@ -0,0 +1,2 @@
+Fix a Use-After-Free race condition in ``subtype_getweakref()`` during
+concurrent weakref creation and destruction in the free-threading build.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index b9e114260424072..15c3367013e4cac 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -4058,12 +4058,24 @@ subtype_getweakref(PyObject *obj, void *context)
     _PyObject_ASSERT((PyObject *)type,
                      ((type->tp_weaklistoffset + (Py_ssize_t)sizeof(PyObject 
*))
                       <= type->tp_basicsize));
+
+    /* Synchronize with weakref creation/destruction so the list head cannot
+     * be removed and freed between loading it and taking a reference. */
+    LOCK_WEAKREFS(obj);
     weaklistptr = (PyObject **)((char *)obj + type->tp_weaklistoffset);
-    if (*weaklistptr == NULL)
-        result = Py_None;
-    else
+    if (*weaklistptr == NULL) {
+        result = Py_NewRef(Py_None);
+    }
+    else if (_Py_TryIncref(*weaklistptr)) {
         result = *weaklistptr;
-    return Py_NewRef(result);
+    }
+    else {
+        result = Py_NewRef(Py_None);
+    }
+
+    UNLOCK_WEAKREFS(obj);
+
+    return result;
 }
 
 /* getset definitions for common descriptors */

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to