https://github.com/python/cpython/commit/23052e5c0c3f7c52204f357e781a88a2afee2d8f commit: 23052e5c0c3f7c52204f357e781a88a2afee2d8f branch: 3.14 author: Pablo Galindo Salgado <[email protected]> committer: pablogsal <[email protected]> date: 2026-09-24T17:45:02+01:00 summary:
[3.14] gh-157639: Fix use-after-free when an external timer re-enters the profiler (GH-157648) (#158115) (cherry picked from commit a6fa91cab267ea634a3cc2f3f7eeb22697b1358f) Co-authored-by: Lazizbek Ergashev <[email protected]> files: A Misc/NEWS.d/next/Library/2026-09-17-03-20-24.gh-issue-157639.VP3kc4.rst M Lib/test/test_cprofile.py M Modules/_lsprof.c diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py index b14105974dcda4..5230348c0eb68f 100644 --- a/Lib/test/test_cprofile.py +++ b/Lib/test/test_cprofile.py @@ -84,6 +84,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 412909cd9bf39d..237919e889ca0c 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -362,6 +362,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 @@ -404,6 +410,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; @@ -976,9 +986,12 @@ 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->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]
