]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-116008: Detect freed thread state in faulthandler (GH-141988) (#142013)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 27 Nov 2025 20:34:00 +0000 (21:34 +0100)
committerGitHub <noreply@github.com>
Thu, 27 Nov 2025 20:34:00 +0000 (20:34 +0000)
gh-116008: Detect freed thread state in faulthandler (GH-141988)

Add _PyMem_IsULongFreed() function.
(cherry picked from commit d5d9e89dde9843a61b46872b1914c5b75ad05998)

Co-authored-by: Victor Stinner <vstinner@python.org>
Include/internal/pycore_pymem.h
Python/traceback.c

index e27cff03daa8f989bd7c159e9bf46fa625283ae3..f0b90a25597d0d87689392e8a6bd6cf78742479f 100644 (file)
@@ -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);
index a46276f66b285b7e95679487f7a9590a9da58072..86273da3c8ae3ccbf803888c8be7eda4ae86a90e 100644 (file)
@@ -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);