Skip to content

Commit a1dd248

Browse files
[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 d5d9e89) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 64d6bde commit a1dd248

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
@@ -1095,6 +1095,9 @@ tstate_is_freed(PyThreadState *tstate)
10951095
if (_PyMem_IsPtrFreed(tstate->interp)) {
10961096
return 1;
10971097
}
1098+
if (_PyMem_IsULongFreed(tstate->thread_id)) {
1099+
return 1;
1100+
}
10981101
return 0;
10991102
}
11001103

@@ -1114,7 +1117,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
11141117
}
11151118

11161119
if (tstate_is_freed(tstate)) {
1117-
PUTS(fd, " <tstate is freed>\n");
1120+
PUTS(fd, " <freed thread state>\n");
11181121
return;
11191122
}
11201123

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

1147-
frame = frame->previous;
1154+
frame = previous;
11481155
if (frame == NULL) {
11491156
break;
11501157
}
@@ -1241,7 +1248,9 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
12411248
tstate->thread_id,
12421249
sizeof(unsigned long) * 2);
12431250

1244-
write_thread_name(fd, tstate);
1251+
if (!_PyMem_IsULongFreed(tstate->thread_id)) {
1252+
write_thread_name(fd, tstate);
1253+
}
12451254

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

13011310
/* Dump the traceback of each thread */
1302-
tstate = PyInterpreterState_ThreadHead(interp);
13031311
unsigned int nthreads = 0;
13041312
_Py_BEGIN_SUPPRESS_IPH
13051313
do
@@ -1310,11 +1318,18 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
13101318
PUTS(fd, "...\n");
13111319
break;
13121320
}
1321+
1322+
if (tstate_is_freed(tstate)) {
1323+
PUTS(fd, "<freed thread state>\n");
1324+
break;
1325+
}
1326+
13131327
write_thread_id(fd, tstate, tstate == current_tstate);
13141328
if (tstate == current_tstate && tstate->interp->gc.collecting) {
13151329
PUTS(fd, " Garbage-collecting\n");
13161330
}
13171331
dump_traceback(fd, tstate, 0);
1332+
13181333
tstate = PyThreadState_Next(tstate);
13191334
nthreads++;
13201335
} while (tstate != NULL);

0 commit comments

Comments
 (0)