Skip to content

Commit

Permalink
[3.11] gh-110052: Fix faulthandler for freed tstate (#110069) (#110072)
Browse files Browse the repository at this point in the history
gh-110052: Fix faulthandler for freed tstate (#110069)

faulthandler now detected freed interp and freed tstate, and no
longer dereference them.

Backport to 3.11: add pycore_pymem.h include to traceback.c.

(cherry picked from commit 2e37a38)
  • Loading branch information
vstinner committed Sep 29, 2023
1 parent efe83ad commit 615d7fc
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 12 deletions.
3 changes: 1 addition & 2 deletions Modules/faulthandler.c
Expand Up @@ -231,7 +231,6 @@ faulthandler_dump_traceback(int fd, int all_threads,
PyInterpreterState *interp)
{
static volatile int reentrant = 0;
PyThreadState *tstate;

if (reentrant)
return;
Expand All @@ -246,7 +245,7 @@ faulthandler_dump_traceback(int fd, int all_threads,
fault if the thread released the GIL, and so this function cannot be
used. Read the thread specific storage (TSS) instead: call
PyGILState_GetThisThreadState(). */
tstate = PyGILState_GetThisThreadState();
PyThreadState *tstate = PyGILState_GetThisThreadState();

if (all_threads) {
(void)_Py_DumpTracebackThreads(fd, NULL, tstate);
Expand Down
48 changes: 38 additions & 10 deletions Python/traceback.c
Expand Up @@ -12,6 +12,7 @@
#include "pycore_parser.h" // _PyParser_ASTFromString
#include "pycore_pyarena.h" // _PyArena_Free()
#include "pycore_pyerrors.h" // _PyErr_Fetch()
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_traceback.h" // EXCEPTION_TB_HEADER

Expand Down Expand Up @@ -1234,23 +1235,45 @@ dump_frame(int fd, _PyInterpreterFrame *frame)
PUTS(fd, "\n");
}

static int
tstate_is_freed(PyThreadState *tstate)
{
if (_PyMem_IsPtrFreed(tstate)) {
return 1;
}
if (_PyMem_IsPtrFreed(tstate->interp)) {
return 1;
}
return 0;
}


static int
interp_is_freed(PyInterpreterState *interp)
{
return _PyMem_IsPtrFreed(interp);
}


static void
dump_traceback(int fd, PyThreadState *tstate, int write_header)
{
_PyInterpreterFrame *frame;
unsigned int depth;

if (write_header) {
PUTS(fd, "Stack (most recent call first):\n");
}

frame = tstate->cframe->current_frame;
if (tstate_is_freed(tstate)) {
PUTS(fd, " <tstate is freed>\n");
return;
}

_PyInterpreterFrame *frame = tstate->cframe->current_frame;
if (frame == NULL) {
PUTS(fd, " <no Python frame>\n");
return;
}

depth = 0;
unsigned int depth = 0;
while (1) {
if (MAX_FRAME_DEPTH <= depth) {
PUTS(fd, " ...\n");
Expand Down Expand Up @@ -1305,9 +1328,6 @@ const char*
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
PyThreadState *current_tstate)
{
PyThreadState *tstate;
unsigned int nthreads;

if (current_tstate == NULL) {
/* _Py_DumpTracebackThreads() is called from signal handlers by
faulthandler.
Expand All @@ -1323,6 +1343,10 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
current_tstate = PyGILState_GetThisThreadState();
}

if (current_tstate != NULL && tstate_is_freed(current_tstate)) {
return "tstate is freed";
}

if (interp == NULL) {
if (current_tstate == NULL) {
interp = _PyGILState_GetInterpreterStateUnsafe();
Expand All @@ -1337,14 +1361,18 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
}
assert(interp != NULL);

if (interp_is_freed(interp)) {
return "interp is freed";
}

/* Get the current interpreter from the current thread */
tstate = PyInterpreterState_ThreadHead(interp);
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
if (tstate == NULL)
return "unable to get the thread head state";

/* Dump the traceback of each thread */
tstate = PyInterpreterState_ThreadHead(interp);
nthreads = 0;
unsigned int nthreads = 0;
_Py_BEGIN_SUPPRESS_IPH
do
{
Expand Down

0 comments on commit 615d7fc

Please sign in to comment.