Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
Cpython
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
sip
Cpython
Commits
d0ab10f6
Unverified
Commit
d0ab10f6
authored
2 years ago
by
Miss Islington (bot)
Committed by
Pablo Galindo
2 years ago
Browse files
Options
Downloads
Patches
Plain Diff
[3.11] GH-97002: Prevent _PyInterpreterFrames from backing more than one PyFrameObject (GH-98002)
(cherry picked from commit
21a2d9ff
)
parent
154b3cd7
Branches
Branches containing commit
Tags
Tags containing commit
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
Lib/test/test_frame.py
+65
-0
65 additions, 0 deletions
Lib/test/test_frame.py
Misc/NEWS.d/next/Core and Builtins/2022-10-06-02-11-34.gh-issue-97002.Zvsk71.rst
+3
-0
3 additions, 0 deletions
...nd Builtins/2022-10-06-02-11-34.gh-issue-97002.Zvsk71.rst
Python/frame.c
+23
-6
23 additions, 6 deletions
Python/frame.c
with
91 additions
and
6 deletions
Lib/test/test_frame.py
+
65
−
0
View file @
d0ab10f6
import
gc
import
re
import
sys
import
types
...
...
@@ -258,5 +259,69 @@ def gen():
gen
()
@support.cpython_only
def
test_sneaky_frame_object
(
self
):
def
trace
(
frame
,
event
,
arg
):
"""
Don
'
t actually do anything, just force a frame object to be created.
"""
def
callback
(
phase
,
info
):
"""
Yo dawg, I heard you like frames, so I
'
m allocating a frame while
you
'
re allocating a frame, so you can have a frame while you have a
frame!
"""
nonlocal
sneaky_frame_object
sneaky_frame_object
=
sys
.
_getframe
().
f_back
# We're done here:
gc
.
callbacks
.
remove
(
callback
)
def
f
():
while
True
:
yield
old_threshold
=
gc
.
get_threshold
()
old_callbacks
=
gc
.
callbacks
[:]
old_enabled
=
gc
.
isenabled
()
old_trace
=
sys
.
gettrace
()
try
:
# Stop the GC for a second while we set things up:
gc
.
disable
()
# Create a paused generator:
g
=
f
()
next
(
g
)
# Move all objects to the oldest generation, and tell the GC to run
# on the *very next* allocation:
gc
.
collect
()
gc
.
set_threshold
(
1
,
0
,
0
)
# Okay, so here's the nightmare scenario:
# - We're tracing the resumption of a generator, which creates a new
# frame object.
# - The allocation of this frame object triggers a collection
# *before* the frame object is actually created.
# - During the collection, we request the exact same frame object.
# This test does it with a GC callback, but in real code it would
# likely be a trace function, weakref callback, or finalizer.
# - The collection finishes, and the original frame object is
# created. We now have two frame objects fighting over ownership
# of the same interpreter frame!
sys
.
settrace
(
trace
)
gc
.
callbacks
.
append
(
callback
)
sneaky_frame_object
=
None
gc
.
enable
()
next
(
g
)
# g.gi_frame should be the the frame object from the callback (the
# one that was *requested* second, but *created* first):
self
.
assertIs
(
g
.
gi_frame
,
sneaky_frame_object
)
finally
:
gc
.
set_threshold
(
*
old_threshold
)
gc
.
callbacks
[:]
=
old_callbacks
sys
.
settrace
(
old_trace
)
if
old_enabled
:
gc
.
enable
()
if
__name__
==
"
__main__
"
:
unittest
.
main
()
This diff is collapsed.
Click to expand it.
Misc/NEWS.d/next/Core and Builtins/2022-10-06-02-11-34.gh-issue-97002.Zvsk71.rst
0 → 100644
+
3
−
0
View file @
d0ab10f6
Fix an issue where several frame objects could be backed by the same
interpreter frame, possibly leading to corrupted memory and hard crashes of
the interpreter.
This diff is collapsed.
Click to expand it.
Python/frame.c
+
23
−
6
View file @
d0ab10f6
...
...
@@ -35,14 +35,31 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame)
Py_XDECREF
(
error_type
);
Py_XDECREF
(
error_value
);
Py_XDECREF
(
error_traceback
);
return
NULL
;
}
PyErr_Restore
(
error_type
,
error_value
,
error_traceback
);
if
(
frame
->
frame_obj
)
{
// GH-97002: How did we get into this horrible situation? Most likely,
// allocating f triggered a GC collection, which ran some code that
// *also* created the same frame... while we were in the middle of
// creating it! See test_sneaky_frame_object in test_frame.py for a
// concrete example.
//
// Regardless, just throw f away and use that frame instead, since it's
// already been exposed to user code. It's actually a bit tricky to do
// this, since we aren't backed by a real _PyInterpreterFrame anymore.
// Just pretend that we have an owned, cleared frame so frame_dealloc
// doesn't make the situation worse:
f
->
f_frame
=
(
_PyInterpreterFrame
*
)
f
->
_f_frame_data
;
f
->
f_frame
->
owner
=
FRAME_CLEARED
;
f
->
f_frame
->
frame_obj
=
f
;
Py_DECREF
(
f
);
return
frame
->
frame_obj
;
}
else
{
assert
(
frame
->
owner
!=
FRAME_OWNED_BY_FRAME_OBJECT
);
assert
(
frame
->
owner
!=
FRAME_CLEARED
);
f
->
f_frame
=
frame
;
frame
->
frame_obj
=
f
;
PyErr_Restore
(
error_type
,
error_value
,
error_traceback
);
}
return
f
;
}
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment