Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,13 @@ extern void _PyLineTable_InitAddressRange(
/** API for traversing the line number table. */
extern int _PyLineTable_NextAddressRange(PyCodeAddressRange *range);
extern int _PyLineTable_PreviousAddressRange(PyCodeAddressRange *range);
// This is used in dump_frame() in traceback.c without an attached tstate.
extern int _PyCode_Addr2LineNoTstate(PyCodeObject *co, int addr);

// Similar to PyCode_Addr2Line(), but return -1 if the code object is invalid
// and can be called without an attached tstate. Used by dump_frame() in
// Python/traceback.c. The function uses heuristics to detect freed memory,
// it's not 100% reliable.
extern int _PyCode_SafeAddr2Line(PyCodeObject *co, int addr);


/** API for executors */
extern void _PyCode_Clear_Executors(PyCodeObject *code);
Expand Down
55 changes: 55 additions & 0 deletions Include/internal/pycore_interpframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,36 @@ static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
return (PyCodeObject *)executable;
}

// Similar to _PyFrame_GetCode(), but return NULL if the frame is invalid or
// freed. Used by dump_frame() in Python/traceback.c. The function uses
// heuristics to detect freed memory, it's not 100% reliable.
static inline PyCodeObject*
_PyFrame_SafeGetCode(_PyInterpreterFrame *f)
{
// globals and builtins may be NULL on a legit frame, but it's unlikely.
// It's more likely that it's a sign of an invalid frame.
if (f->f_globals == NULL || f->f_builtins == NULL) {
return NULL;
}

if (PyStackRef_IsNull(f->f_executable)) {
return NULL;
}
void *ptr;
memcpy(&ptr, &f->f_executable, sizeof(f->f_executable));
if (_PyMem_IsPtrFreed(ptr)) {
return NULL;
}
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
if (_PyObject_IsFreed(executable)) {
return NULL;
}
if (!PyCode_Check(executable)) {
return NULL;
}
return (PyCodeObject *)executable;
}

static inline _Py_CODEUNIT *
_PyFrame_GetBytecode(_PyInterpreterFrame *f)
{
Expand All @@ -37,6 +67,31 @@ _PyFrame_GetBytecode(_PyInterpreterFrame *f)
#endif
}

// Similar to PyUnstable_InterpreterFrame_GetLasti(), but return NULL if the
// frame is invalid or freed. Used by dump_frame() in Python/traceback.c. The
// function uses heuristics to detect freed memory, it's not 100% reliable.
static inline int
_PyFrame_SafeGetLasti(struct _PyInterpreterFrame *f)
{
// Code based on _PyFrame_GetBytecode() but replace _PyFrame_GetCode()
// with _PyFrame_SafeGetCode().
PyCodeObject *co = _PyFrame_SafeGetCode(f);
if (co == NULL) {
return -1;
}

_Py_CODEUNIT *bytecode;
#ifdef Py_GIL_DISABLED
_PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size);
bytecode = (_Py_CODEUNIT *)tlbc->entries[f->tlbc_index];
#else
bytecode = _PyCode_CODE(co);
#endif

return (int)(f->instr_ptr - bytecode) * sizeof(_Py_CODEUNIT);
}

static inline PyFunctionObject *_PyFrame_GetFunction(_PyInterpreterFrame *f) {
PyObject *func = PyStackRef_AsPyObjectBorrow(f->f_funcobj);
assert(PyFunction_Check(func));
Expand Down
10 changes: 6 additions & 4 deletions Include/internal/pycore_pymem.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,17 @@ static inline int _PyMem_IsPtrFreed(const void *ptr)
{
uintptr_t value = (uintptr_t)ptr;
#if SIZEOF_VOID_P == 8
return (value == 0
return (value <= 0xff // NULL, 0x1, 0x2, ..., 0xff
|| value == (uintptr_t)0xCDCDCDCDCDCDCDCD
|| value == (uintptr_t)0xDDDDDDDDDDDDDDDD
|| value == (uintptr_t)0xFDFDFDFDFDFDFDFD);
|| value == (uintptr_t)0xFDFDFDFDFDFDFDFD
|| value >= (uintptr_t)0xFFFFFFFFFFFFFF00); // -0xff, ..., -2, -1
#elif SIZEOF_VOID_P == 4
return (value == 0
return (value <= 0xff
|| value == (uintptr_t)0xCDCDCDCD
|| value == (uintptr_t)0xDDDDDDDD
|| value == (uintptr_t)0xFDFDFDFD);
|| value == (uintptr_t)0xFDFDFDFD
|| value >= (uintptr_t)0xFFFFFF00);
#else
# error "unknown pointer size"
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`faulthandler` now detects if a frame or a code object is invalid or
freed. Patch by Victor Stinner.
23 changes: 20 additions & 3 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1013,8 +1013,8 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
* source location tracking (co_lines/co_positions)
******************/

int
_PyCode_Addr2LineNoTstate(PyCodeObject *co, int addrq)
static int
_PyCode_Addr2Line(PyCodeObject *co, int addrq)
{
if (addrq < 0) {
return co->co_firstlineno;
Expand All @@ -1028,12 +1028,29 @@ _PyCode_Addr2LineNoTstate(PyCodeObject *co, int addrq)
return _PyCode_CheckLineNumber(addrq, &bounds);
}

int
_PyCode_SafeAddr2Line(PyCodeObject *co, int addrq)
{
if (addrq < 0) {
return co->co_firstlineno;
}
if (co->_co_monitoring && co->_co_monitoring->lines) {
return _Py_Instrumentation_GetLine(co, addrq/sizeof(_Py_CODEUNIT));
}
if (!(addrq >= 0 && addrq < _PyCode_NBYTES(co))) {
return -1;
}
PyCodeAddressRange bounds;
_PyCode_InitAddressRange(co, &bounds);
return _PyCode_CheckLineNumber(addrq, &bounds);
}

int
PyCode_Addr2Line(PyCodeObject *co, int addrq)
{
int lineno;
Py_BEGIN_CRITICAL_SECTION(co);
lineno = _PyCode_Addr2LineNoTstate(co, addrq);
lineno = _PyCode_Addr2Line(co, addrq);
Py_END_CRITICAL_SECTION();
return lineno;
}
Expand Down
60 changes: 37 additions & 23 deletions Python/traceback.c
Original file line number Diff line number Diff line change
Expand Up @@ -976,44 +976,61 @@ _Py_DumpASCII(int fd, PyObject *text)

/* Write a frame into the file fd: "File "xxx", line xxx in xxx".

This function is signal safe. */
This function is signal safe.

static void
Return 0 on success. Return -1 if the frame is invalid. */

static int
dump_frame(int fd, _PyInterpreterFrame *frame)
{
assert(frame->owner < FRAME_OWNED_BY_INTERPRETER);
if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
/* Ignore trampoline frame */
return 0;
}

PyCodeObject *code =_PyFrame_GetCode(frame);
PyCodeObject *code = _PyFrame_SafeGetCode(frame);
if (code == NULL) {
return -1;
}

int res = 0;
PUTS(fd, " File ");
if (code->co_filename != NULL
&& PyUnicode_Check(code->co_filename))
{
PUTS(fd, "\"");
_Py_DumpASCII(fd, code->co_filename);
PUTS(fd, "\"");
} else {
}
else {
PUTS(fd, "???");
res = -1;
}
int lasti = PyUnstable_InterpreterFrame_GetLasti(frame);
int lineno = _PyCode_Addr2LineNoTstate(code, lasti);

PUTS(fd, ", line ");
int lasti = _PyFrame_SafeGetLasti(frame);
int lineno = -1;
if (lasti >= 0) {
lineno = _PyCode_SafeAddr2Line(code, lasti);
}
if (lineno >= 0) {
_Py_DumpDecimal(fd, (size_t)lineno);
}
else {
PUTS(fd, "???");
res = -1;
}
PUTS(fd, " in ");

if (code->co_name != NULL
&& PyUnicode_Check(code->co_name)) {
PUTS(fd, " in ");
if (code->co_name != NULL && PyUnicode_Check(code->co_name)) {
_Py_DumpASCII(fd, code->co_name);
}
else {
PUTS(fd, "???");
res = -1;
}

PUTS(fd, "\n");
return res;
}

static int
Expand Down Expand Up @@ -1056,17 +1073,6 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)

unsigned int depth = 0;
while (1) {
if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
/* Trampoline frame */
frame = frame->previous;
if (frame == NULL) {
break;
}

/* Can't have more than one shim frame in a row */
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
}

if (MAX_FRAME_DEPTH <= depth) {
if (MAX_FRAME_DEPTH < depth) {
PUTS(fd, "plus ");
Expand All @@ -1076,7 +1082,15 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
break;
}

dump_frame(fd, frame);
if (_PyMem_IsPtrFreed(frame)) {
PUTS(fd, " <freed frame>\n");
break;
}
if (dump_frame(fd, frame) < 0) {
PUTS(fd, " <invalid frame>\n");
break;
}

frame = frame->previous;
if (frame == NULL) {
break;
Expand Down
Loading