diff --git a/Doc/data/python3.13.abi b/Doc/data/python3.13.abi
index 619d84ae4ab051..d02189517317d7 100644
--- a/Doc/data/python3.13.abi
+++ b/Doc/data/python3.13.abi
@@ -1143,6 +1143,7 @@
+
@@ -1669,7 +1670,7 @@
-
+
@@ -2458,7 +2459,7 @@
-
+
@@ -3485,20 +3486,20 @@
-
-
+
+
-
+
-
+
-
+
-
+
@@ -3734,7 +3735,7 @@
-
+
@@ -3842,52 +3843,52 @@
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
@@ -4102,7 +4103,7 @@
-
+
@@ -4144,7 +4145,7 @@
-
+
@@ -5367,96 +5368,96 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -5495,7 +5496,7 @@
-
+
@@ -5641,26 +5642,26 @@
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
@@ -5854,7 +5855,7 @@
-
+
@@ -5975,50 +5976,50 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -6330,170 +6331,170 @@
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
@@ -6509,7 +6510,7 @@
-
+
@@ -6725,125 +6726,125 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -7525,7 +7526,7 @@
-
+
@@ -7549,18 +7550,22 @@
-
+
-
+
+
+
+
+
-
+
-
+
@@ -7608,38 +7613,38 @@
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
@@ -8239,7 +8244,7 @@
-
+
@@ -9408,7 +9413,7 @@
-
+
@@ -9887,98 +9892,98 @@
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -11888,7 +11893,7 @@
-
+
@@ -11953,7 +11958,7 @@
-
+
@@ -11963,7 +11968,7 @@
-
+
@@ -12170,150 +12175,150 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -16455,18 +16460,18 @@
-
+
-
+
-
-
+
+
-
-
+
+
-
+
@@ -16488,22 +16493,31 @@
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -16835,74 +16849,74 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -20984,7 +20998,7 @@
-
+
@@ -21057,61 +21071,61 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -21514,13 +21528,13 @@
-
+
-
+
@@ -22004,243 +22018,243 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -22786,15 +22800,15 @@
-
+
-
+
-
+
@@ -23391,7 +23405,7 @@
-
+
@@ -23483,7 +23497,7 @@
-
+
@@ -23561,26 +23575,26 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
@@ -23880,7 +23894,7 @@
-
+
@@ -23943,7 +23957,7 @@
-
+
@@ -23993,57 +24007,57 @@
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -24051,41 +24065,41 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -24098,16 +24112,16 @@
-
+
-
+
-
+
@@ -24292,122 +24306,122 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
+
@@ -24916,18 +24930,11 @@
-
+
-
-
-
-
-
-
-
@@ -25083,77 +25090,77 @@
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -25240,9 +25247,9 @@
-
-
-
+
+
+
@@ -26022,7 +26029,7 @@
-
+
@@ -26615,7 +26622,7 @@
-
+
@@ -27623,7 +27630,7 @@
-
+
@@ -27631,16 +27638,16 @@
-
+
-
-
+
+
-
-
+
+
@@ -27689,12 +27696,12 @@
-
+
-
+
@@ -27856,15 +27863,12 @@
-
-
-
-
+
-
+
@@ -28183,7 +28187,7 @@
-
+
@@ -28369,9 +28373,6 @@
-
-
-
@@ -28449,41 +28450,41 @@
-
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
@@ -28496,11 +28497,11 @@
-
+
-
+
@@ -28559,7 +28560,7 @@
-
+
@@ -28622,111 +28623,114 @@
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
@@ -28738,16 +28742,16 @@
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -29392,6 +29396,11 @@
+
+
+
+
+
diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index 25605533aacf8f..41df3a34c91f33 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -105,7 +105,6 @@ extern int _PyPerfTrampoline_SetCallbacks(_PyPerf_Callbacks *);
extern void _PyPerfTrampoline_GetCallbacks(_PyPerf_Callbacks *);
extern int _PyPerfTrampoline_Init(int activate);
extern int _PyPerfTrampoline_Fini(void);
-extern void _PyPerfTrampoline_FreeArenas(void);
extern int _PyIsPerfTrampolineActive(void);
extern PyStatus _PyPerfTrampoline_AfterFork_Child(void);
#ifdef PY_HAVE_PERF_TRAMPOLINE
diff --git a/Include/internal/pycore_ceval_state.h b/Include/internal/pycore_ceval_state.h
index 009a1ea41eb985..a109c195724915 100644
--- a/Include/internal/pycore_ceval_state.h
+++ b/Include/internal/pycore_ceval_state.h
@@ -90,6 +90,9 @@ struct _ceval_runtime_state {
struct trampoline_api_st trampoline_api;
FILE *map_file;
Py_ssize_t persist_after_fork;
+ _PyFrameEvalFunction prev_eval_frame;
+ Py_ssize_t trampoline_refcount;
+ int code_watcher_id;
#else
int _not_used;
#endif
diff --git a/Misc/NEWS.d/next/Core and Builtins/2025-12-27-23-57-43.gh-issue-143228.m3EF9E.rst b/Misc/NEWS.d/next/Core and Builtins/2025-12-27-23-57-43.gh-issue-143228.m3EF9E.rst
new file mode 100644
index 00000000000000..893bc29543d91d
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2025-12-27-23-57-43.gh-issue-143228.m3EF9E.rst
@@ -0,0 +1,4 @@
+Fix use-after-free in perf trampoline when toggling profiling while
+threads are running or during interpreter finalization with daemon threads
+active. The fix uses reference counting to ensure trampolines are not freed
+while any code object could still reference them. Pach by Pablo Galindo
diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c
index b1b787fc27892e..5589ec1c36232f 100644
--- a/Python/perf_trampoline.c
+++ b/Python/perf_trampoline.c
@@ -204,6 +204,43 @@ enum perf_trampoline_type {
#define perf_map_file _PyRuntime.ceval.perf.map_file
#define persist_after_fork _PyRuntime.ceval.perf.persist_after_fork
#define perf_trampoline_type _PyRuntime.ceval.perf.perf_trampoline_type
+#define prev_eval_frame _PyRuntime.ceval.perf.prev_eval_frame
+#define trampoline_refcount _PyRuntime.ceval.perf.trampoline_refcount
+#define code_watcher_id _PyRuntime.ceval.perf.code_watcher_id
+
+static void free_code_arenas(void);
+
+static void
+perf_trampoline_reset_state(void)
+{
+ free_code_arenas();
+ if (code_watcher_id >= 0) {
+ PyCode_ClearWatcher(code_watcher_id);
+ code_watcher_id = -1;
+ }
+ extra_code_index = -1;
+}
+
+static int
+perf_trampoline_code_watcher(PyCodeEvent event, PyCodeObject *co)
+{
+ if (event != PY_CODE_EVENT_DESTROY) {
+ return 0;
+ }
+ if (extra_code_index == -1) {
+ return 0;
+ }
+ py_trampoline f = NULL;
+ int ret = _PyCode_GetExtra((PyObject *)co, extra_code_index, (void **)&f);
+ if (ret != 0 || f == NULL) {
+ return 0;
+ }
+ trampoline_refcount--;
+ if (trampoline_refcount == 0) {
+ perf_trampoline_reset_state();
+ }
+ return 0;
+}
static void
perf_map_write_entry(void *state, const void *code_addr,
@@ -405,6 +442,7 @@ py_trampoline_evaluator(PyThreadState *ts, _PyInterpreterFrame *frame,
perf_code_arena->code_size, co);
_PyCode_SetExtra((PyObject *)co, extra_code_index,
(void *)new_trampoline);
+ trampoline_refcount++;
f = new_trampoline;
}
assert(f != NULL);
@@ -428,6 +466,7 @@ int PyUnstable_PerfTrampoline_CompileCode(PyCodeObject *co)
}
trampoline_api.write_state(trampoline_api.state, new_trampoline,
perf_code_arena->code_size, co);
+ trampoline_refcount++;
return _PyCode_SetExtra((PyObject *)co, extra_code_index,
(void *)new_trampoline);
}
@@ -482,6 +521,10 @@ _PyPerfTrampoline_Init(int activate)
{
#ifdef PY_HAVE_PERF_TRAMPOLINE
PyThreadState *tstate = _PyThreadState_GET();
+ if (code_watcher_id == 0) {
+ // Initialize to -1 since 0 is a valid watcher ID
+ code_watcher_id = -1;
+ }
if (tstate->interp->eval_frame &&
tstate->interp->eval_frame != py_trampoline_evaluator) {
PyErr_SetString(PyExc_RuntimeError,
@@ -505,6 +548,13 @@ _PyPerfTrampoline_Init(int activate)
if (new_code_arena() < 0) {
return -1;
}
+ code_watcher_id = PyCode_AddWatcher(perf_trampoline_code_watcher);
+ if (code_watcher_id < 0) {
+ PyErr_FormatUnraisable("Failed to register code watcher for perf trampoline");
+ free_code_arenas();
+ return -1;
+ }
+ trampoline_refcount = 1; // Base refcount held by the system
perf_status = PERF_STATUS_OK;
}
#endif
@@ -526,17 +576,19 @@ _PyPerfTrampoline_Fini(void)
trampoline_api.free_state(trampoline_api.state);
perf_trampoline_type = PERF_TRAMPOLINE_UNSET;
}
- extra_code_index = -1;
+
+ // Prevent new trampolines from being created
perf_status = PERF_STATUS_NO_INIT;
-#endif
- return 0;
-}
-void _PyPerfTrampoline_FreeArenas(void) {
-#ifdef PY_HAVE_PERF_TRAMPOLINE
- free_code_arenas();
+ // Decrement base refcount. If refcount reaches 0, all code objects are already
+ // dead so clean up now. Otherwise, watcher remains active to clean up when last
+ // code object dies; extra_code_index stays valid so watcher can identify them.
+ trampoline_refcount--;
+ if (trampoline_refcount == 0) {
+ perf_trampoline_reset_state();
+ }
#endif
- return;
+ return 0;
}
int
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 8cc6bd0fa78906..bfa1fe4f603682 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -1929,7 +1929,6 @@ finalize_interp_clear(PyThreadState *tstate)
_PyArg_Fini();
_Py_ClearFileSystemEncoding();
_PyPerfTrampoline_Fini();
- _PyPerfTrampoline_FreeArenas();
}
finalize_interp_types(tstate->interp);