From: Victor Stinner Date: Thu, 27 Nov 2025 11:35:00 +0000 (+0100) Subject: gh-116008: Detect freed thread state in faulthandler (#141988) X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d5d9e89dde9843a61b46872b1914c5b75ad05998;p=thirdparty%2FPython%2Fcpython.git gh-116008: Detect freed thread state in faulthandler (#141988) Add _PyMem_IsULongFreed() function. --- diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index ed943b510567..05484e847f11 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -70,6 +70,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 521d6322a5c4..48f9b4d04c6b 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1094,6 +1094,9 @@ tstate_is_freed(PyThreadState *tstate) if (_PyMem_IsPtrFreed(tstate->interp)) { return 1; } + if (_PyMem_IsULongFreed(tstate->thread_id)) { + return 1; + } return 0; } @@ -1113,7 +1116,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) } if (tstate_is_freed(tstate)) { - PUTS(fd, " \n"); + PUTS(fd, " \n"); return; } @@ -1138,12 +1141,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; } @@ -1240,7 +1247,9 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current) tstate->thread_id, sizeof(unsigned long) * 2); - write_thread_name(fd, tstate); + if (!_PyMem_IsULongFreed(tstate->thread_id)) { + write_thread_name(fd, tstate); + } PUTS(fd, " (most recent call first):\n"); } @@ -1298,7 +1307,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 @@ -1309,11 +1317,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);