Skip to content

Commit 46d85fb

Browse files
pablogsallazerg
andauthored
[3.13] gh-157639: Fix use-after-free when an external timer re-enters the profiler (GH-157648) (#158118)
(cherry picked from commit a6fa91c) Co-authored-by: Lazizbek Ergashev <lazerg2@gmail.com>
1 parent 7f5140b commit 46d85fb

3 files changed

Lines changed: 43 additions & 1 deletion

File tree

‎Lib/test/test_cprofile.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,32 @@ def __call__(self):
8383
profiler_with_evil_timer.clear()
8484
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
8585

86+
def test_enable_in_external_timer(self):
87+
# gh-157639: Enabling the profiler from an external timer should not crash
88+
import _lsprof
89+
90+
# the timer re-arms monitoring from inside disable(), so the tool
91+
# id stays claimed once the profiler is torn down
92+
self.addCleanup(sys.monitoring.free_tool_id, sys.monitoring.PROFILER_ID)
93+
94+
reenter = True
95+
96+
def timer():
97+
nonlocal reenter
98+
if reenter:
99+
reenter = False
100+
try:
101+
profiler.enable()
102+
except Exception:
103+
pass
104+
return 0
105+
106+
profiler = _lsprof.Profiler(timer=timer)
107+
profiler.enable()
108+
(lambda: None)()
109+
profiler.disable()
110+
profiler.clear()
111+
86112
def test_profile_enable_disable(self):
87113
prof = self.profilerclass()
88114
# Make sure we clean ourselves up if the test fails for some reason.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash in :mod:`!cProfile` when an external timer re-enters the
2+
profiler. Profiling events raised while the external timer runs are now
3+
ignored.

‎Modules/_lsprof.c‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,12 @@ ptrace_enter_call(PyObject *self, void *key, PyObject *userObj)
356356
ProfilerEntry *profEntry;
357357
ProfilerContext *pContext;
358358

359+
/* Events raised by the external timer must be ignored: it can run
360+
arbitrary code while a context is still being unwound. */
361+
if (pObj->flags & POF_EXT_TIMER) {
362+
return;
363+
}
364+
359365
/* In the case of entering a generator expression frame via a
360366
* throw (gen_send_ex(.., 1)), we may already have an
361367
* Exception set here. We must not mess around with this
@@ -398,6 +404,10 @@ ptrace_leave_call(PyObject *self, void *key)
398404
ProfilerEntry *profEntry;
399405
ProfilerContext *pContext;
400406

407+
if (pObj->flags & POF_EXT_TIMER) {
408+
return;
409+
}
410+
401411
pContext = pObj->currentProfilerContext;
402412
if (pContext == NULL)
403413
return;
@@ -908,9 +918,12 @@ profiler_dealloc(ProfilerObject *op)
908918
}
909919
}
910920

921+
/* Drop the external timer before flushing: it is Python code, and the
922+
profiler can be deallocated by the garbage collector. */
923+
Py_CLEAR(op->externalTimer);
924+
911925
flush_unmatched(op);
912926
clearEntries(op);
913-
Py_XDECREF(op->externalTimer);
914927
PyTypeObject *tp = Py_TYPE(op);
915928
tp->tp_free(op);
916929
Py_DECREF(tp);

0 commit comments

Comments
 (0)