From 80b752285d861ca5d092d02f8e2117b88b54d6d7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 27 Nov 2025 17:55:31 +0100 Subject: [PATCH] [3.13] gh-116008: Detect freed thread state in faulthandler (#141988) (#142017) gh-116008: Detect freed thread state in faulthandler (#141988) Add _PyMem_IsULongFreed() function. (cherry picked from commit d5d9e89dde9843a61b46872b1914c5b75ad05998) --- Include/internal/pycore_pymem.h | 21 +++++++++++++++++++++ Python/traceback.c | 19 ++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index 79c43a0768b7..76d58d1d251d 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -106,6 +106,27 @@ static inline int _PyMem_IsPtrFreed(const void *ptr) #endif } +// Similar to _PyMem_IsPtrFreed() but expects an 'unsigned long' instead of a +// pointer. +static inline int _PyMem_IsULongFreed(unsigned long value) +{ +#if SIZEOF_LONG == 8 + return (value == 0 + || value == (unsigned long)0xCDCDCDCDCDCDCDCD + || value == (unsigned long)0xDDDDDDDDDDDDDDDD + || value == (unsigned long)0xFDFDFDFDFDFDFDFD + || value == (unsigned long)0xFFFFFFFFFFFFFFFF); +#elif SIZEOF_LONG == 4 + return (value == 0 + || value == (unsigned long)0xCDCDCDCD + || value == (unsigned long)0xDDDDDDDD + || value == (unsigned long)0xFDFDFDFD + || value == (unsigned long)0xFFFFFFFF); +#else +# error "unknown long size" +#endif +} + extern int _PyMem_GetAllocatorName( const char *name, PyMemAllocatorName *allocator); diff --git a/Python/traceback.c b/Python/traceback.c index 7e7739682794..f98408337164 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -960,6 +960,9 @@ tstate_is_freed(PyThreadState *tstate) if (_PyMem_IsPtrFreed(tstate->interp)) { return 1; } + if (_PyMem_IsULongFreed(tstate->thread_id)) { + return 1; + } return 0; } @@ -979,7 +982,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) } if (tstate_is_freed(tstate)) { - PUTS(fd, " \n"); + PUTS(fd, " \n"); return; } @@ -1004,12 +1007,16 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) PUTS(fd, " \n"); break; } + // Read frame->previous early since memory can be freed during + // dump_frame() + _PyInterpreterFrame *previous = frame->previous; + if (dump_frame(fd, frame) < 0) { PUTS(fd, " \n"); break; } - frame = frame->previous; + frame = previous; if (frame == NULL) { break; } @@ -1100,7 +1107,6 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, return "unable to get the thread head state"; /* Dump the traceback of each thread */ - tstate = PyInterpreterState_ThreadHead(interp); unsigned int nthreads = 0; _Py_BEGIN_SUPPRESS_IPH do @@ -1111,11 +1117,18 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, PUTS(fd, "...\n"); break; } + + if (tstate_is_freed(tstate)) { + PUTS(fd, "\n"); + break; + } + write_thread_id(fd, tstate, tstate == current_tstate); if (tstate == current_tstate && tstate->interp->gc.collecting) { PUTS(fd, " Garbage-collecting\n"); } dump_traceback(fd, tstate, 0); + tstate = PyThreadState_Next(tstate); nthreads++; } while (tstate != NULL); -- 2.47.3