]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-112127: Fix possible use-after-free in atexit.unregister() (GH-114092)...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 17 Dec 2025 16:06:12 +0000 (17:06 +0100)
committerGitHub <noreply@github.com>
Wed, 17 Dec 2025 16:06:12 +0000 (21:36 +0530)
gh-112127: Fix possible use-after-free in atexit.unregister() (GH-114092)
(cherry picked from commit 2b466c47c333106dc9522ab77898e6972e25a2c6)

Co-authored-by: Benjamin Johnson <ben332004@gmail.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Lib/test/_test_atexit.py
Misc/ACKS
Misc/NEWS.d/next/Library/2025-12-17-14-41-09.gh-issue-112127.13OHQk.rst [new file with mode: 0644]
Modules/atexitmodule.c

index f618c1fcbca52b1edb30d8884123f355c013ce93..490b0686a0c1793240da3627d9817874ccb8f91b 100644 (file)
@@ -135,6 +135,19 @@ class GeneralTest(unittest.TestCase):
         finally:
             atexit.unregister(func)
 
+    def test_eq_unregister_clear(self):
+        # Issue #112127: callback's __eq__ may call unregister or _clear
+        class Evil:
+            def __eq__(self, other):
+                action(other)
+                return NotImplemented
+
+        for action in atexit.unregister, lambda o: atexit._clear():
+            with self.subTest(action=action):
+                atexit.register(lambda: None)
+                atexit.unregister(Evil())
+                atexit._clear()
+
 
 if __name__ == "__main__":
     unittest.main()
index 15957d68a574d165e36086aedd326d93d9a5bac2..5dde69f43052ddde0cbaead1f82898b81415ae2b 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -904,6 +904,7 @@ Jim Jewett
 Pedro Diaz Jimenez
 Orjan Johansen
 Fredrik Johansson
+Benjamin Johnson
 Benjamin K. Johnson
 Gregory K. Johnson
 Kent Johnson
diff --git a/Misc/NEWS.d/next/Library/2025-12-17-14-41-09.gh-issue-112127.13OHQk.rst b/Misc/NEWS.d/next/Library/2025-12-17-14-41-09.gh-issue-112127.13OHQk.rst
new file mode 100644 (file)
index 0000000..c983683
--- /dev/null
@@ -0,0 +1,2 @@
+Fix possible use-after-free in :func:`atexit.unregister` when the callback
+is unregistered during comparison.
index 4b068967a6ca6ea1408bf0f54408d638d61803b5..f777bb34e965b39fd8589e582414a22b488cafdc 100644 (file)
@@ -256,10 +256,11 @@ static int
 atexit_unregister_locked(PyObject *callbacks, PyObject *func)
 {
     for (Py_ssize_t i = 0; i < PyList_GET_SIZE(callbacks); ++i) {
-        PyObject *tuple = PyList_GET_ITEM(callbacks, i);
+        PyObject *tuple = Py_NewRef(PyList_GET_ITEM(callbacks, i));
         assert(PyTuple_CheckExact(tuple));
         PyObject *to_compare = PyTuple_GET_ITEM(tuple, 0);
         int cmp = PyObject_RichCompareBool(func, to_compare, Py_EQ);
+        Py_DECREF(tuple);
         if (cmp < 0)
         {
             return -1;