Skip to content

Commit 2712e72

Browse files
committed
pythongh-140815: Fix faulthandler for invalid/freed frame
faulthandler now detects if a frame or a code object is invalid or freed. Add helper functions: * _PyCode_SafeAddr2Line() * _PyFrame_SafeGetCode() * _PyFrame_SafeGetBytecode() * _PyFrame_SafeGetLasti() _PyMem_IsPtrFreed() now also considers the pointer 0x1 as freed.
1 parent e733dc9 commit 2712e72

5 files changed

Lines changed: 120 additions & 30 deletions

File tree

Include/internal/pycore_code.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,11 @@ extern void _PyLineTable_InitAddressRange(
274274
/** API for traversing the line number table. */
275275
PyAPI_FUNC(int) _PyLineTable_NextAddressRange(PyCodeAddressRange *range);
276276
extern int _PyLineTable_PreviousAddressRange(PyCodeAddressRange *range);
277-
// This is used in dump_frame() in traceback.c without an attached tstate.
278-
extern int _PyCode_Addr2LineNoTstate(PyCodeObject *co, int addr);
277+
278+
// Similar to PyCode_Addr2Line(), but return -1 if the code object is invalid.
279+
// Used by dump_frame() in Python/traceback.c.
280+
extern int _PyCode_SafeAddr2Line(PyCodeObject *co, int addr);
281+
279282

280283
/** API for executors */
281284
extern void _PyCode_Clear_Executors(PyCodeObject *code);

Include/internal/pycore_interpframe.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,27 @@ static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
2424
return (PyCodeObject *)executable;
2525
}
2626

27+
// Similar to _PyFrame_GetCode(), but return NULL if the frame is invalid or
28+
// freed. Used by dump_frame() in Python/traceback.c.
29+
static inline PyCodeObject* _PyFrame_SafeGetCode(_PyInterpreterFrame *f) {
30+
if (PyStackRef_IsNull(f->f_executable)) {
31+
return NULL;
32+
}
33+
void *ptr;
34+
memcpy(&ptr, &f->f_executable, sizeof(f->f_executable));
35+
if (_PyMem_IsPtrFreed(ptr)) {
36+
return NULL;
37+
}
38+
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
39+
if (_PyObject_IsFreed(executable)) {
40+
return NULL;
41+
}
42+
if (!PyCode_Check(executable)) {
43+
return NULL;
44+
}
45+
return (PyCodeObject *)executable;
46+
}
47+
2748
static inline _Py_CODEUNIT *
2849
_PyFrame_GetBytecode(_PyInterpreterFrame *f)
2950
{
@@ -37,6 +58,41 @@ _PyFrame_GetBytecode(_PyInterpreterFrame *f)
3758
#endif
3859
}
3960

61+
// Similar to _PyFrame_GetCode(), but return NULL if the code object is invalid
62+
// or freed. Used by _PyFrame_SafeGetLasti().
63+
static inline _Py_CODEUNIT *
64+
_PyFrame_SafeGetBytecode(_PyInterpreterFrame *f)
65+
{
66+
#ifdef Py_GIL_DISABLED
67+
PyCodeObject *co = _PyFrame_SafeGetCode(f);
68+
if (co == NULL) {
69+
return NULL;
70+
}
71+
_PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
72+
assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size);
73+
return (_Py_CODEUNIT *)tlbc->entries[f->tlbc_index];
74+
#else
75+
PyCodeObject *co = _PyFrame_SafeGetCode(f);
76+
if (co == NULL) {
77+
return NULL;
78+
}
79+
return _PyCode_CODE(co);
80+
#endif
81+
}
82+
83+
// Similar to _PyFrame_GetCode(), but return NULL if the frame is invalid or
84+
// freed. Used by dump_frame() in Python/traceback.c.
85+
static inline int
86+
_PyFrame_SafeGetLasti(struct _PyInterpreterFrame *frame)
87+
{
88+
_Py_CODEUNIT *bytecode = _PyFrame_SafeGetBytecode(frame);
89+
if (bytecode == NULL) {
90+
return -1;
91+
}
92+
int lasti = ((int)(frame->instr_ptr - bytecode));
93+
return lasti * sizeof(_Py_CODEUNIT);
94+
}
95+
4096
static inline PyFunctionObject *_PyFrame_GetFunction(_PyInterpreterFrame *f) {
4197
PyObject *func = PyStackRef_AsPyObjectBorrow(f->f_funcobj);
4298
assert(PyFunction_Check(func));

Include/internal/pycore_pymem.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ static inline int _PyMem_IsPtrFreed(const void *ptr)
5454
{
5555
uintptr_t value = (uintptr_t)ptr;
5656
#if SIZEOF_VOID_P == 8
57-
return (value == 0
57+
return (value < 256 // NULL, 0x1, 0x2, ...
5858
|| value == (uintptr_t)0xCDCDCDCDCDCDCDCD
5959
|| value == (uintptr_t)0xDDDDDDDDDDDDDDDD
6060
|| value == (uintptr_t)0xFDFDFDFDFDFDFDFD);
6161
#elif SIZEOF_VOID_P == 4
62-
return (value == 0
62+
return (value < 256
6363
|| value == (uintptr_t)0xCDCDCDCD
6464
|| value == (uintptr_t)0xDDDDDDDD
6565
|| value == (uintptr_t)0xFDFDFDFD);

Objects/codeobject.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,8 +1005,8 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
10051005
* source location tracking (co_lines/co_positions)
10061006
******************/
10071007

1008-
int
1009-
_PyCode_Addr2LineNoTstate(PyCodeObject *co, int addrq)
1008+
static int
1009+
_PyCode_Addr2Line(PyCodeObject *co, int addrq)
10101010
{
10111011
if (addrq < 0) {
10121012
return co->co_firstlineno;
@@ -1020,12 +1020,29 @@ _PyCode_Addr2LineNoTstate(PyCodeObject *co, int addrq)
10201020
return _PyCode_CheckLineNumber(addrq, &bounds);
10211021
}
10221022

1023+
int
1024+
_PyCode_SafeAddr2Line(PyCodeObject *co, int addrq)
1025+
{
1026+
if (addrq < 0) {
1027+
return co->co_firstlineno;
1028+
}
1029+
if (co->_co_monitoring && co->_co_monitoring->lines) {
1030+
return _Py_Instrumentation_GetLine(co, addrq/sizeof(_Py_CODEUNIT));
1031+
}
1032+
if (!(addrq >= 0 && addrq < _PyCode_NBYTES(co))) {
1033+
return -1;
1034+
}
1035+
PyCodeAddressRange bounds;
1036+
_PyCode_InitAddressRange(co, &bounds);
1037+
return _PyCode_CheckLineNumber(addrq, &bounds);
1038+
}
1039+
10231040
int
10241041
PyCode_Addr2Line(PyCodeObject *co, int addrq)
10251042
{
10261043
int lineno;
10271044
Py_BEGIN_CRITICAL_SECTION(co);
1028-
lineno = _PyCode_Addr2LineNoTstate(co, addrq);
1045+
lineno = _PyCode_Addr2Line(co, addrq);
10291046
Py_END_CRITICAL_SECTION();
10301047
return lineno;
10311048
}

Python/traceback.c

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,44 +1028,61 @@ _Py_DumpWideString(int fd, wchar_t *str)
10281028

10291029
/* Write a frame into the file fd: "File "xxx", line xxx in xxx".
10301030
1031-
This function is signal safe. */
1031+
This function is signal safe.
10321032
1033-
static void
1033+
Return 0 on success. Return -1 if the frame is invalid. */
1034+
1035+
static int
10341036
dump_frame(int fd, _PyInterpreterFrame *frame)
10351037
{
1036-
assert(frame->owner < FRAME_OWNED_BY_INTERPRETER);
1038+
if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
1039+
/* Ignore trampoline frame */
1040+
return 0;
1041+
}
10371042

1038-
PyCodeObject *code =_PyFrame_GetCode(frame);
1043+
PyCodeObject *code = _PyFrame_SafeGetCode(frame);
1044+
if (code == NULL) {
1045+
return -1;
1046+
}
1047+
1048+
int res = 0;
10391049
PUTS(fd, " File ");
10401050
if (code->co_filename != NULL
10411051
&& PyUnicode_Check(code->co_filename))
10421052
{
10431053
PUTS(fd, "\"");
10441054
_Py_DumpASCII(fd, code->co_filename);
10451055
PUTS(fd, "\"");
1046-
} else {
1056+
}
1057+
else {
10471058
PUTS(fd, "???");
1059+
res = -1;
10481060
}
1049-
int lasti = PyUnstable_InterpreterFrame_GetLasti(frame);
1050-
int lineno = _PyCode_Addr2LineNoTstate(code, lasti);
1061+
10511062
PUTS(fd, ", line ");
1063+
int lasti = _PyFrame_SafeGetLasti(frame);
1064+
int lineno = -1;
1065+
if (0 <= lasti) {
1066+
lineno = _PyCode_SafeAddr2Line(code, lasti);
1067+
}
10521068
if (lineno >= 0) {
10531069
_Py_DumpDecimal(fd, (size_t)lineno);
10541070
}
10551071
else {
10561072
PUTS(fd, "???");
1073+
res = -1;
10571074
}
1058-
PUTS(fd, " in ");
10591075

1060-
if (code->co_name != NULL
1061-
&& PyUnicode_Check(code->co_name)) {
1076+
PUTS(fd, " in ");
1077+
if (code->co_name != NULL && PyUnicode_Check(code->co_name)) {
10621078
_Py_DumpASCII(fd, code->co_name);
10631079
}
10641080
else {
10651081
PUTS(fd, "???");
1082+
res = -1;
10661083
}
1067-
10681084
PUTS(fd, "\n");
1085+
return res;
10691086
}
10701087

10711088
static int
@@ -1108,17 +1125,6 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
11081125

11091126
unsigned int depth = 0;
11101127
while (1) {
1111-
if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
1112-
/* Trampoline frame */
1113-
frame = frame->previous;
1114-
if (frame == NULL) {
1115-
break;
1116-
}
1117-
1118-
/* Can't have more than one shim frame in a row */
1119-
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
1120-
}
1121-
11221128
if (MAX_FRAME_DEPTH <= depth) {
11231129
if (MAX_FRAME_DEPTH < depth) {
11241130
PUTS(fd, "plus ");
@@ -1128,11 +1134,19 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
11281134
break;
11291135
}
11301136

1131-
dump_frame(fd, frame);
1137+
if (dump_frame(fd, frame) < 0) {
1138+
PUTS(fd, " <invalid frame>\n");
1139+
break;
1140+
}
1141+
11321142
frame = frame->previous;
11331143
if (frame == NULL) {
11341144
break;
11351145
}
1146+
if (_PyMem_IsPtrFreed(frame)) {
1147+
PUTS(fd, " <freed frame>\n");
1148+
break;
1149+
}
11361150
depth++;
11371151
}
11381152
}

0 commit comments

Comments
 (0)