From 3a92ca5edc67946cd7ecacc3c8f89d5c587c9ae6 Mon Sep 17 00:00:00 2001 From: Benjamin Johnson Date: Wed, 17 Dec 2025 07:09:57 -0800 Subject: [PATCH] [3.13] gh-112127: Fix possible use-after-free in atexit.unregister() (GH-114092) (cherry picked from commit 2b466c47c333106dc9522ab77898e6972e25a2c6) Co-authored-by: Benjamin Johnson Co-authored-by: Serhiy Storchaka --- Lib/test/_test_atexit.py | 13 +++++++++++++ Misc/ACKS | 1 + .../2025-12-17-14-41-09.gh-issue-112127.13OHQk.rst | 2 ++ Modules/atexitmodule.c | 4 +++- 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-17-14-41-09.gh-issue-112127.13OHQk.rst 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; }