From 7d8452acccea21abe25be614a8ffbc941c2c084e Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Sun, 28 Dec 2025 13:50:23 +0000 Subject: [PATCH] [3.13] gh-143228: Fix UAF in perf trampoline during finalization (GH-143233) (cherry picked from commit 3ccc76f036bfaabb5a4631783b966501fe64859a) Co-authored-by: Pablo Galindo Salgado --- Doc/data/python3.13.abi | 1711 +++++++++-------- Include/internal/pycore_ceval.h | 1 - Include/internal/pycore_ceval_state.h | 3 + ...-12-27-23-57-43.gh-issue-143228.m3EF9E.rst | 4 + Python/perf_trampoline.c | 68 +- Python/pylifecycle.c | 1 - 6 files changed, 927 insertions(+), 861 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2025-12-27-23-57-43.gh-issue-143228.m3EF9E.rst 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);