1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
Description: Fix use-after-free crash in frame_dealloc
It was possible for the trashcan to delay the deallocation of a
PyFrameObject until after its corresponding _PyInterpreterFrame has
already been freed. So frame_dealloc needs to avoid dereferencing the
f_frame pointer unless it first checks that the pointer still points
to the interpreter frame within the frame object.
Origin: https://github.com/python/cpython/commit/46cae02085311481dc8b1ea9a5110969d9325bc7
Bug-upstream: https://github.com/python/cpython/issues/106092
Bug-Debian: https://bugs.debian.org/1050843
Author: Anders Kaseorg <andersk@mit.edu>
Last-Update: 2023-08-29
Applied-Upstream: 3.11.5
---
.../2023-07-18-16-13-51.gh-issue-106092.bObgRM.rst | 2 ++
Objects/frameobject.c | 13 +++++++------
2 files changed, 9 insertions(+), 6 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-07-18-16-13-51.gh-issue-106092.bObgRM.rst
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-07-18-16-13-51.gh-issue-106092.bObgRM.rst
@@ -0,0 +1,2 @@
+Fix a segmentation fault caused by a use-after-free bug in ``frame_dealloc``
+when the trashcan delays the deallocation of a ``PyFrameObject``.
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -851,9 +851,6 @@
/* It is the responsibility of the owning generator/coroutine
* to have cleared the generator pointer */
- assert(f->f_frame->owner != FRAME_OWNED_BY_GENERATOR ||
- _PyFrame_GetGenerator(f->f_frame)->gi_frame_state == FRAME_CLEARED);
-
if (_PyObject_GC_IS_TRACKED(f)) {
_PyObject_GC_UNTRACK(f);
}
@@ -861,10 +858,14 @@
Py_TRASHCAN_BEGIN(f, frame_dealloc);
PyCodeObject *co = NULL;
+ /* GH-106092: If f->f_frame was on the stack and we reached the maximum
+ * nesting depth for deallocations, the trashcan may have delayed this
+ * deallocation until after f->f_frame is freed. Avoid dereferencing
+ * f->f_frame unless we know it still points to valid memory. */
+ _PyInterpreterFrame *frame = (_PyInterpreterFrame *)f->_f_frame_data;
+
/* Kill all local variables including specials, if we own them */
- if (f->f_frame->owner == FRAME_OWNED_BY_FRAME_OBJECT) {
- assert(f->f_frame == (_PyInterpreterFrame *)f->_f_frame_data);
- _PyInterpreterFrame *frame = (_PyInterpreterFrame *)f->_f_frame_data;
+ if (f->f_frame == frame && frame->owner == FRAME_OWNED_BY_FRAME_OBJECT) {
/* Don't clear code object until the end */
co = frame->f_code;
frame->f_code = NULL;
|