https://github.com/python/cpython/commit/a6fa91cab267ea634a3cc2f3f7eeb22697b1358f
commit: a6fa91cab267ea634a3cc2f3f7eeb22697b1358f
branch: main
author: Lazizbek Ergashev <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-09-24T17:01:34+01:00
summary:
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
files:
A Misc/NEWS.d/next/Library/2026-09-17-03-20-24.gh-issue-157639.VP3kc4.rst
M Lib/test/test_profiling/test_tracing_profiler.py
M Modules/_lsprof.c
diff --git a/Lib/test/test_profiling/test_tracing_profiler.py
b/Lib/test/test_profiling/test_tracing_profiler.py
index 6a4d968f12ef15..50de9af86e86d0 100644
--- a/Lib/test/test_profiling/test_tracing_profiler.py
+++ b/Lib/test/test_profiling/test_tracing_profiler.py
@@ -85,6 +85,27 @@ def __call__(self):
profiler_with_evil_timer.clear()
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
+ def test_enable_in_external_timer(self):
+ # gh-157639: Enabling the profiler from an external timer should not
crash
+ import _lsprof
+
+ # the timer re-arms monitoring from inside disable(), so the tool
+ # id stays claimed once the profiler is torn down
+ self.addCleanup(sys.monitoring.free_tool_id,
sys.monitoring.PROFILER_ID)
+
+ def timer():
+ try:
+ profiler.enable()
+ except Exception:
+ pass
+ return 0
+
+ profiler = _lsprof.Profiler(timer=timer)
+ profiler.enable()
+ (lambda: None)()
+ profiler.disable()
+ profiler.clear()
+
def test_profile_enable_disable(self):
prof = self.profilerclass()
# Make sure we clean ourselves up if the test fails for some reason.
diff --git
a/Misc/NEWS.d/next/Library/2026-09-17-03-20-24.gh-issue-157639.VP3kc4.rst
b/Misc/NEWS.d/next/Library/2026-09-17-03-20-24.gh-issue-157639.VP3kc4.rst
new file mode 100644
index 00000000000000..de1fd1286d7a57
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-17-03-20-24.gh-issue-157639.VP3kc4.rst
@@ -0,0 +1,3 @@
+Fix a crash in :mod:`!cProfile` when an external timer re-enters the
+profiler. Profiling events raised while the external timer runs are now
+ignored.
diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c
index 4e50ca64f59af2..65857c495f2339 100644
--- a/Modules/_lsprof.c
+++ b/Modules/_lsprof.c
@@ -363,6 +363,12 @@ ptrace_enter_call(PyObject *self, void *key, PyObject
*userObj)
ProfilerEntry *profEntry;
ProfilerContext *pContext;
+ /* Events raised by the external timer must be ignored: it can run
+ arbitrary code while a context is still being unwound. */
+ if (pObj->flags & POF_EXT_TIMER) {
+ return;
+ }
+
/* In the case of entering a generator expression frame via a
* throw (gen_send_ex(.., 1)), we may already have an
* Exception set here. We must not mess around with this
@@ -405,6 +411,10 @@ ptrace_leave_call(PyObject *self, void *key)
ProfilerEntry *profEntry;
ProfilerContext *pContext;
+ if (pObj->flags & POF_EXT_TIMER) {
+ return;
+ }
+
pContext = pObj->currentProfilerContext;
if (pContext == NULL)
return;
@@ -980,10 +990,13 @@ profiler_dealloc(PyObject *op)
}
}
+ /* Drop the external timer before flushing: it is Python code, and the
+ profiler can be deallocated by the garbage collector. */
+ Py_CLEAR(self->externalTimer);
+
flush_unmatched(self);
clearEntries(self);
Py_XDECREF(self->missing);
- Py_XDECREF(self->externalTimer);
PyTypeObject *tp = Py_TYPE(self);
tp->tp_free(self);
Py_DECREF(tp);
_______________________________________________
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]