https://github.com/python/cpython/commit/d32160aab15e3d4a5c9c09537605e61f2ba418a6
commit: d32160aab15e3d4a5c9c09537605e61f2ba418a6
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-09-24T16:29:21Z
summary:

[3.15] gh-157639: Fix use-after-free when an external timer re-enters the 
profiler (GH-157648) (#158113)

gh-157639: Fix use-after-free when an external timer re-enters the profiler 
(GH-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
(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_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]

Reply via email to