Skip to content

Commit 88d565f

Browse files
authored
gh-99110: Initialize frame->previous in init_frame to fix segmentation fault when accessing frame.f_back (#100182)
1 parent 2659036 commit 88d565f

File tree

5 files changed

+34
-1
lines changed

5 files changed

+34
-1
lines changed

Include/internal/pycore_frame.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ static inline void _PyFrame_StackPush(_PyInterpreterFrame *f, PyObject *value) {
9696

9797
void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *dest);
9898

99-
/* Consumes reference to func and locals */
99+
/* Consumes reference to func and locals.
100+
Does not initialize frame->previous, which happens
101+
when frame is linked into the frame stack.
102+
*/
100103
static inline void
101104
_PyFrame_InitializeSpecials(
102105
_PyInterpreterFrame *frame, PyFunctionObject *func,

Lib/test/test_frame.py

+9
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,15 @@ def test_frame_get_generator(self):
408408
frame = next(gen)
409409
self.assertIs(gen, _testcapi.frame_getgenerator(frame))
410410

411+
def test_frame_fback_api(self):
412+
"""Test that accessing `f_back` does not cause a segmentation fault on
413+
a frame created with `PyFrame_New` (GH-99110)."""
414+
def dummy():
415+
pass
416+
417+
frame = _testcapi.frame_new(dummy.__code__, globals(), locals())
418+
# The following line should not cause a segmentation fault.
419+
self.assertIsNone(frame.f_back)
411420

412421
if __name__ == "__main__":
413422
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Initialize frame->previous in frameobject.c to fix a segmentation fault when
2+
accessing frames created by :c:func:`PyFrame_New`.

Modules/_testcapimodule.c

+18
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#define PY_SSIZE_T_CLEAN
2121

2222
#include "Python.h"
23+
#include "frameobject.h" // PyFrame_New
2324
#include "marshal.h" // PyMarshal_WriteLongToFile
2425
#include "structmember.h" // for offsetof(), T_OBJECT
2526
#include <float.h> // FLT_MAX
@@ -2839,6 +2840,22 @@ frame_getlasti(PyObject *self, PyObject *frame)
28392840
return PyLong_FromLong(lasti);
28402841
}
28412842

2843+
static PyObject *
2844+
frame_new(PyObject *self, PyObject *args)
2845+
{
2846+
PyObject *code, *globals, *locals;
2847+
if (!PyArg_ParseTuple(args, "OOO", &code, &globals, &locals)) {
2848+
return NULL;
2849+
}
2850+
if (!PyCode_Check(code)) {
2851+
PyErr_SetString(PyExc_TypeError, "argument must be a code object");
2852+
return NULL;
2853+
}
2854+
PyThreadState *tstate = PyThreadState_Get();
2855+
2856+
return (PyObject *)PyFrame_New(tstate, (PyCodeObject *)code, globals, locals);
2857+
}
2858+
28422859
static PyObject *
28432860
test_frame_getvar(PyObject *self, PyObject *args)
28442861
{
@@ -3277,6 +3294,7 @@ static PyMethodDef TestMethods[] = {
32773294
{"frame_getgenerator", frame_getgenerator, METH_O, NULL},
32783295
{"frame_getbuiltins", frame_getbuiltins, METH_O, NULL},
32793296
{"frame_getlasti", frame_getlasti, METH_O, NULL},
3297+
{"frame_new", frame_new, METH_VARARGS, NULL},
32803298
{"frame_getvar", test_frame_getvar, METH_VARARGS, NULL},
32813299
{"frame_getvarstring", test_frame_getvarstring, METH_VARARGS, NULL},
32823300
{"eval_get_func_name", eval_get_func_name, METH_O, NULL},

Objects/frameobject.c

+1
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,7 @@ init_frame(_PyInterpreterFrame *frame, PyFunctionObject *func, PyObject *locals)
10131013
PyCodeObject *code = (PyCodeObject *)func->func_code;
10141014
_PyFrame_InitializeSpecials(frame, (PyFunctionObject*)Py_NewRef(func),
10151015
Py_XNewRef(locals), code);
1016+
frame->previous = NULL;
10161017
for (Py_ssize_t i = 0; i < code->co_nlocalsplus; i++) {
10171018
frame->localsplus[i] = NULL;
10181019
}

0 commit comments

Comments
 (0)