From 88d565f32a709140664444c6dea20ecd35a10e94 Mon Sep 17 00:00:00 2001 From: Bill Fisher Date: Fri, 23 Dec 2022 07:45:53 -0700 Subject: gh-99110: Initialize `frame->previous` in init_frame to fix segmentation fault when accessing `frame.f_back` (#100182) --- Include/internal/pycore_frame.h | 5 ++++- Lib/test/test_frame.py | 9 +++++++++ .../2022-12-12-01-05-16.gh-issue-99110.1JqtIg.rst | 2 ++ Modules/_testcapimodule.c | 18 ++++++++++++++++++ Objects/frameobject.c | 1 + 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-12-12-01-05-16.gh-issue-99110.1JqtIg.rst diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h index 7fa410d..f18723b 100644 --- a/Include/internal/pycore_frame.h +++ b/Include/internal/pycore_frame.h @@ -96,7 +96,10 @@ static inline void _PyFrame_StackPush(_PyInterpreterFrame *f, PyObject *value) { void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *dest); -/* Consumes reference to func and locals */ +/* Consumes reference to func and locals. + Does not initialize frame->previous, which happens + when frame is linked into the frame stack. + */ static inline void _PyFrame_InitializeSpecials( _PyInterpreterFrame *frame, PyFunctionObject *func, diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index ed413f1..40c734b 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -408,6 +408,15 @@ class TestCAPI(unittest.TestCase): frame = next(gen) self.assertIs(gen, _testcapi.frame_getgenerator(frame)) + def test_frame_fback_api(self): + """Test that accessing `f_back` does not cause a segmentation fault on + a frame created with `PyFrame_New` (GH-99110).""" + def dummy(): + pass + + frame = _testcapi.frame_new(dummy.__code__, globals(), locals()) + # The following line should not cause a segmentation fault. + self.assertIsNone(frame.f_back) if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-12-12-01-05-16.gh-issue-99110.1JqtIg.rst b/Misc/NEWS.d/next/Core and Builtins/2022-12-12-01-05-16.gh-issue-99110.1JqtIg.rst new file mode 100644 index 0000000..175740d --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-12-12-01-05-16.gh-issue-99110.1JqtIg.rst @@ -0,0 +1,2 @@ +Initialize frame->previous in frameobject.c to fix a segmentation fault when +accessing frames created by :c:func:`PyFrame_New`. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 35c895d..c32fdb5 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -20,6 +20,7 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" +#include "frameobject.h" // PyFrame_New #include "marshal.h" // PyMarshal_WriteLongToFile #include "structmember.h" // for offsetof(), T_OBJECT #include // FLT_MAX @@ -2840,6 +2841,22 @@ frame_getlasti(PyObject *self, PyObject *frame) } static PyObject * +frame_new(PyObject *self, PyObject *args) +{ + PyObject *code, *globals, *locals; + if (!PyArg_ParseTuple(args, "OOO", &code, &globals, &locals)) { + return NULL; + } + if (!PyCode_Check(code)) { + PyErr_SetString(PyExc_TypeError, "argument must be a code object"); + return NULL; + } + PyThreadState *tstate = PyThreadState_Get(); + + return (PyObject *)PyFrame_New(tstate, (PyCodeObject *)code, globals, locals); +} + +static PyObject * test_frame_getvar(PyObject *self, PyObject *args) { PyObject *frame, *name; @@ -3277,6 +3294,7 @@ static PyMethodDef TestMethods[] = { {"frame_getgenerator", frame_getgenerator, METH_O, NULL}, {"frame_getbuiltins", frame_getbuiltins, METH_O, NULL}, {"frame_getlasti", frame_getlasti, METH_O, NULL}, + {"frame_new", frame_new, METH_VARARGS, NULL}, {"frame_getvar", test_frame_getvar, METH_VARARGS, NULL}, {"frame_getvarstring", test_frame_getvarstring, METH_VARARGS, NULL}, {"eval_get_func_name", eval_get_func_name, METH_O, NULL}, diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 74c26d8..eab85c0 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -1013,6 +1013,7 @@ init_frame(_PyInterpreterFrame *frame, PyFunctionObject *func, PyObject *locals) PyCodeObject *code = (PyCodeObject *)func->func_code; _PyFrame_InitializeSpecials(frame, (PyFunctionObject*)Py_NewRef(func), Py_XNewRef(locals), code); + frame->previous = NULL; for (Py_ssize_t i = 0; i < code->co_nlocalsplus; i++) { frame->localsplus[i] = NULL; } -- cgit v0.12