https://github.com/python/cpython/commit/8c92d665ee9975e1d42b2bf70ff512dac55cabaf commit: 8c92d665ee9975e1d42b2bf70ff512dac55cabaf branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 <[email protected]> date: 2025-12-17T21:36:12+05:30 summary:
[3.14] gh-112127: Fix possible use-after-free in atexit.unregister() (GH-114092) (#142878) gh-112127: Fix possible use-after-free in atexit.unregister() (GH-114092) (cherry picked from commit 2b466c47c333106dc9522ab77898e6972e25a2c6) Co-authored-by: Benjamin Johnson <[email protected]> Co-authored-by: Serhiy Storchaka <[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 15957d68a574d1..5dde69f43052dd 100644 --- 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 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 4b068967a6ca6e..f777bb34e965b3 100644 --- a/Modules/atexitmodule.c +++ b/Modules/atexitmodule.c @@ -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; _______________________________________________ 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]
