https://github.com/python/cpython/commit/a1dd2480226370b6924dbd8a59f9c86dc856047b commit: a1dd2480226370b6924dbd8a59f9c86dc856047b branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: vstinner <[email protected]> date: 2025-11-27T20:34:00Z summary:
[3.14] gh-116008: Detect freed thread state in faulthandler (GH-141988) (#142013) gh-116008: Detect freed thread state in faulthandler (GH-141988) Add _PyMem_IsULongFreed() function. (cherry picked from commit d5d9e89dde9843a61b46872b1914c5b75ad05998) Co-authored-by: Victor Stinner <[email protected]> files: M Include/internal/pycore_pymem.h M Python/traceback.c diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index e27cff03daa8f9..f0b90a25597d0d 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 a46276f66b285b..86273da3c8ae3c 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1095,6 +1095,9 @@ tstate_is_freed(PyThreadState *tstate) if (_PyMem_IsPtrFreed(tstate->interp)) { return 1; } + if (_PyMem_IsULongFreed(tstate->thread_id)) { + return 1; + } return 0; } @@ -1114,7 +1117,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) } if (tstate_is_freed(tstate)) { - PUTS(fd, " <tstate is freed>\n"); + PUTS(fd, " <freed thread state>\n"); return; } @@ -1139,12 +1142,16 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) PUTS(fd, " <freed frame>\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, " <invalid frame>\n"); break; } - frame = frame->previous; + frame = previous; if (frame == NULL) { break; } @@ -1241,7 +1248,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"); } @@ -1299,7 +1308,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 @@ -1310,11 +1318,18 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, PUTS(fd, "...\n"); break; } + + if (tstate_is_freed(tstate)) { + PUTS(fd, "<freed thread state>\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); _______________________________________________ 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]
