[Python-checkins] gh-99110: Initialize `frame->previous` in init_frame to fix segmentation fault when accessing `frame.f_back` (#100182)

kumaraditya303 webhook-mailer at python.org
Fri Dec 23 09:45:59 EST 2022


https://github.com/python/cpython/commit/88d565f32a709140664444c6dea20ecd35a10e94
commit: 88d565f32a709140664444c6dea20ecd35a10e94
branch: main
author: Bill Fisher <william.w.fisher at gmail.com>
committer: kumaraditya303 <59607654+kumaraditya303 at users.noreply.github.com>
date: 2022-12-23T20:15:53+05:30
summary:

gh-99110: Initialize `frame->previous` in init_frame to fix segmentation fault when accessing `frame.f_back` (#100182)

files:
A Misc/NEWS.d/next/Core and Builtins/2022-12-12-01-05-16.gh-issue-99110.1JqtIg.rst
M Include/internal/pycore_frame.h
M Lib/test/test_frame.py
M Modules/_testcapimodule.c
M Objects/frameobject.c

diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index 7fa410d288c3..f18723b30322 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 ed413f105e5b..40c734b6e33a 100644
--- a/Lib/test/test_frame.py
+++ b/Lib/test/test_frame.py
@@ -408,6 +408,15 @@ def test_frame_get_generator(self):
         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 000000000000..175740dfca07
--- /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 35c895d9ceb2..c32fdb5f5fbe 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 <float.h>                // FLT_MAX
@@ -2839,6 +2840,22 @@ frame_getlasti(PyObject *self, PyObject *frame)
     return PyLong_FromLong(lasti);
 }
 
+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)
 {
@@ -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 74c26d8d4d96..eab85c08fc01 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;
     }



More information about the Python-checkins mailing list