Skip to content

Commit 23052e5

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

3 files changed

Lines changed: 38 additions & 1 deletion

File tree

‎Lib/test/test_cprofile.py‎

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

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

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

413+
if (pObj->flags & POF_EXT_TIMER) {
414+
return;
415+
}
416+
407417
pContext = pObj->currentProfilerContext;
408418
if (pContext == NULL)
409419
return;
@@ -976,9 +986,12 @@ profiler_dealloc(PyObject *op)
976986
}
977987
}
978988

989+
/* Drop the external timer before flushing: it is Python code, and the
990+
profiler can be deallocated by the garbage collector. */
991+
Py_CLEAR(self->externalTimer);
992+
979993
flush_unmatched(self);
980994
clearEntries(self);
981-
Py_XDECREF(self->externalTimer);
982995
PyTypeObject *tp = Py_TYPE(self);
983996
tp->tp_free(self);
984997
Py_DECREF(tp);

0 commit comments

Comments
 (0)