Skip to content

Commit 6c8db8a

Browse files
Fix use-after-free in atexit.unregister()
1 parent d457345 commit 6c8db8a

File tree

3 files changed

+45
-1
lines changed

3 files changed

+45
-1
lines changed

Lib/test/_test_atexit.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,40 @@ def func():
135135
finally:
136136
atexit.unregister(func)
137137

138+
def test_eq_unregister_clear(self):
139+
# Issue #112127: callback's __eq__ may call unregister or _clear
140+
global cnt
141+
cnt = 0
142+
class Func:
143+
def __init__(self, action, eq_ret_val):
144+
self.action = action
145+
self.eq_ret_val = eq_ret_val
146+
147+
def __call__(self):
148+
return
149+
150+
def __eq__(self, o):
151+
global cnt
152+
cnt += 1
153+
if cnt == 1:
154+
self.action(o)
155+
return self.eq_ret_val(o)
156+
157+
for action, eq_ret_val in (
158+
(lambda o: atexit.unregister(self), lambda o: NotImplemented),
159+
(lambda o: atexit.unregister(self), lambda o: True),
160+
(lambda o: atexit.unregister(o), lambda o: NotImplemented),
161+
(lambda o: atexit.unregister(o), lambda o: True),
162+
(lambda o: atexit._clear(), lambda o: NotImplemented),
163+
(lambda o: atexit._clear(), lambda o: True),
164+
):
165+
cnt = 0
166+
f1 = Func(action, eq_ret_val)
167+
f2 = Func(action, eq_ret_val)
168+
atexit.register(f1)
169+
atexit.register(f2)
170+
atexit._run_exitfuncs()
171+
138172

139173
if __name__ == "__main__":
140174
unittest.main()

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,7 @@ Jim Jewett
881881
Pedro Diaz Jimenez
882882
Orjan Johansen
883883
Fredrik Johansson
884+
Benjamin Johnson
884885
Gregory K. Johnson
885886
Kent Johnson
886887
Michael Johnson

Modules/atexitmodule.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,20 @@ atexit_unregister(PyObject *module, PyObject *func)
278278
continue;
279279
}
280280

281+
/*
282+
* Increment refcounts of func and cb->func because equality check may
283+
* unregister one or both
284+
*/
285+
PyObject *cb_func = Py_NewRef(cb->func);
286+
PyObject *cur_func = Py_NewRef(func);
281287
int eq = PyObject_RichCompareBool(cb->func, func, Py_EQ);
288+
Py_DECREF(cb_func);
289+
Py_DECREF(cur_func);
282290
if (eq < 0) {
283291
return NULL;
284292
}
285-
if (eq) {
293+
// Equality comparison might have already deleted this callback
294+
if (eq && state->callbacks[i] != NULL) {
286295
atexit_delete_cb(state, i);
287296
}
288297
}

0 commit comments

Comments
 (0)