Skip to content

Commit 6f99c64

Browse files
committed
pythongh-116008: Detect freed thread state in faulthandler
Add _PyMem_IsULongFreed() function.
1 parent 9ac1428 commit 6f99c64

File tree

2 files changed

+40
-4
lines changed

2 files changed

+40
-4
lines changed

Include/internal/pycore_pymem.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,27 @@ static inline int _PyMem_IsPtrFreed(const void *ptr)
7070
#endif
7171
}
7272

73+
// Similar to _PyMem_IsPtrFreed() but expects an 'unsigned long' instead of a
74+
// pointer.
75+
static inline int _PyMem_IsULongFreed(unsigned long value)
76+
{
77+
#if SIZEOF_LONG == 8
78+
return (value == 0
79+
|| value == (unsigned long)0xCDCDCDCDCDCDCDCD
80+
|| value == (unsigned long)0xDDDDDDDDDDDDDDDD
81+
|| value == (unsigned long)0xFDFDFDFDFDFDFDFD
82+
|| value == (unsigned long)0xFFFFFFFFFFFFFFFF);
83+
#elif SIZEOF_LONG == 4
84+
return (value == 0
85+
|| value == (unsigned long)0xCDCDCDCD
86+
|| value == (unsigned long)0xDDDDDDDD
87+
|| value == (unsigned long)0xFDFDFDFD
88+
|| value == (unsigned long)0xFFFFFFFF);
89+
#else
90+
# error "unknown long size"
91+
#endif
92+
}
93+
7394
extern int _PyMem_GetAllocatorName(
7495
const char *name,
7596
PyMemAllocatorName *allocator);

Python/traceback.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,9 @@ tstate_is_freed(PyThreadState *tstate)
10941094
if (_PyMem_IsPtrFreed(tstate->interp)) {
10951095
return 1;
10961096
}
1097+
if (_PyMem_IsULongFreed(tstate->thread_id)) {
1098+
return 1;
1099+
}
10971100
return 0;
10981101
}
10991102

@@ -1113,7 +1116,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
11131116
}
11141117

11151118
if (tstate_is_freed(tstate)) {
1116-
PUTS(fd, " <tstate is freed>\n");
1119+
PUTS(fd, " <freed thread state>\n");
11171120
return;
11181121
}
11191122

@@ -1138,12 +1141,16 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
11381141
PUTS(fd, " <freed frame>\n");
11391142
break;
11401143
}
1144+
// Read frame->previous early since memory can be freed during
1145+
// dump_frame()
1146+
_PyInterpreterFrame *previous = frame->previous;
1147+
11411148
if (dump_frame(fd, frame) < 0) {
11421149
PUTS(fd, " <invalid frame>\n");
11431150
break;
11441151
}
11451152

1146-
frame = frame->previous;
1153+
frame = previous;
11471154
if (frame == NULL) {
11481155
break;
11491156
}
@@ -1240,7 +1247,9 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
12401247
tstate->thread_id,
12411248
sizeof(unsigned long) * 2);
12421249

1243-
write_thread_name(fd, tstate);
1250+
if (!_PyMem_IsULongFreed(tstate->thread_id)) {
1251+
write_thread_name(fd, tstate);
1252+
}
12441253

12451254
PUTS(fd, " (most recent call first):\n");
12461255
}
@@ -1298,7 +1307,6 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
12981307
return "unable to get the thread head state";
12991308

13001309
/* Dump the traceback of each thread */
1301-
tstate = PyInterpreterState_ThreadHead(interp);
13021310
unsigned int nthreads = 0;
13031311
_Py_BEGIN_SUPPRESS_IPH
13041312
do
@@ -1309,11 +1317,18 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
13091317
PUTS(fd, "...\n");
13101318
break;
13111319
}
1320+
1321+
if (tstate_is_freed(tstate)) {
1322+
PUTS(fd, "<freed thread state>\n");
1323+
break;
1324+
}
1325+
13121326
write_thread_id(fd, tstate, tstate == current_tstate);
13131327
if (tstate == current_tstate && tstate->interp->gc.collecting) {
13141328
PUTS(fd, " Garbage-collecting\n");
13151329
}
13161330
dump_traceback(fd, tstate, 0);
1331+
13171332
tstate = PyThreadState_Next(tstate);
13181333
nthreads++;
13191334
} while (tstate != NULL);

0 commit comments

Comments
 (0)