https://github.com/python/cpython/commit/08d665d607aded4be5461319b61023debdaacbd0
commit: 08d665d607aded4be5461319b61023debdaacbd0
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-12-17T17:15:21Z
summary:

[3.13] gh-112127: Fix possible use-after-free in atexit.unregister() 
(GH-114092) (GH-142880)

(cherry picked from commit 2b466c47c333106dc9522ab77898e6972e25a2c6)

Co-authored-by: Benjamin Johnson <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-12-17-14-41-09.gh-issue-112127.13OHQk.rst
M Lib/test/_test_atexit.py
M Misc/ACKS
M Modules/atexitmodule.c

diff --git a/Lib/test/_test_atexit.py b/Lib/test/_test_atexit.py
index f618c1fcbca52b..490b0686a0c179 100644
--- a/Lib/test/_test_atexit.py
+++ b/Lib/test/_test_atexit.py
@@ -135,6 +135,19 @@ def func():
         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()
diff --git a/Misc/ACKS b/Misc/ACKS
index 55713d3b286835..85ac1d5feefb46 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -892,6 +892,7 @@ Jim Jewett
 Pedro Diaz Jimenez
 Orjan Johansen
 Fredrik Johansson
+Benjamin Johnson
 Gregory K. Johnson
 Kent Johnson
 Michael 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
index 00000000000000..c983683ebd5589
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-17-14-41-09.gh-issue-112127.13OHQk.rst
@@ -0,0 +1,2 @@
+Fix possible use-after-free in :func:`atexit.unregister` when the callback
+is unregistered during comparison.
diff --git a/Modules/atexitmodule.c b/Modules/atexitmodule.c
index c009235b7a36c2..93d4c4ede32513 100644
--- a/Modules/atexitmodule.c
+++ b/Modules/atexitmodule.c
@@ -287,7 +287,9 @@ atexit_unregister(PyObject *module, PyObject *func)
             continue;
         }
 
-        int eq = PyObject_RichCompareBool(cb->func, func, Py_EQ);
+        PyObject *to_compare = Py_NewRef(cb->func);
+        int eq = PyObject_RichCompareBool(to_compare, func, Py_EQ);
+        Py_DECREF(to_compare);
         if (eq < 0) {
             return NULL;
         }

_______________________________________________
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