Skip to content

Commit a6fa91c

Browse files
authored
gh-157639: Fix use-after-free when an external timer re-enters the profiler (#157648)
* gh-157639: Fix use-after-free when an external timer re-enters the profiler * Suppress the cProfile link in the NEWS entry * Add braces around the external timer guards, per PEP 7 * Simplify the external timer test setup * Do not call the external timer while deallocating the profiler
1 parent 46ee358 commit a6fa91c

3 files changed

Lines changed: 38 additions & 1 deletion

File tree

‎Lib/test/test_profiling/test_tracing_profiler.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,27 @@ def __call__(self):
8585
profiler_with_evil_timer.clear()
8686
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
8787

88+
def test_enable_in_external_timer(self):
89+
# gh-157639: Enabling the profiler from an external timer should not crash
90+
import _lsprof
91+
92+
# the timer re-arms monitoring from inside disable(), so the tool
93+
# id stays claimed once the profiler is torn down
94+
self.addCleanup(sys.monitoring.free_tool_id, sys.monitoring.PROFILER_ID)
95+
96+
def timer():
97+
try:
98+
profiler.enable()
99+
except Exception:
100+
pass
101+
return 0
102+
103+
profiler = _lsprof.Profiler(timer=timer)
104+
profiler.enable()
105+
(lambda: None)()
106+
profiler.disable()
107+
profiler.clear()
108+
88109
def test_profile_enable_disable(self):
89110
prof = self.profilerclass()
90111
# 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
@@ -363,6 +363,12 @@ ptrace_enter_call(PyObject *self, void *key, PyObject *userObj)
363363
ProfilerEntry *profEntry;
364364
ProfilerContext *pContext;
365365

366+
/* Events raised by the external timer must be ignored: it can run
367+
arbitrary code while a context is still being unwound. */
368+
if (pObj->flags & POF_EXT_TIMER) {
369+
return;
370+
}
371+
366372
/* In the case of entering a generator expression frame via a
367373
* throw (gen_send_ex(.., 1)), we may already have an
368374
* Exception set here. We must not mess around with this
@@ -405,6 +411,10 @@ ptrace_leave_call(PyObject *self, void *key)
405411
ProfilerEntry *profEntry;
406412
ProfilerContext *pContext;
407413

414+
if (pObj->flags & POF_EXT_TIMER) {
415+
return;
416+
}
417+
408418
pContext = pObj->currentProfilerContext;
409419
if (pContext == NULL)
410420
return;
@@ -980,10 +990,13 @@ profiler_dealloc(PyObject *op)
980990
}
981991
}
982992

993+
/* Drop the external timer before flushing: it is Python code, and the
994+
profiler can be deallocated by the garbage collector. */
995+
Py_CLEAR(self->externalTimer);
996+
983997
flush_unmatched(self);
984998
clearEntries(self);
985999
Py_XDECREF(self->missing);
986-
Py_XDECREF(self->externalTimer);
9871000
PyTypeObject *tp = Py_TYPE(self);
9881001
tp->tp_free(self);
9891002
Py_DECREF(tp);

0 commit comments

Comments
 (0)