-
-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-39382: Avoid dangling object use in abstract_issubclass() #18530
bpo-39382: Avoid dangling object use in abstract_issubclass() #18530
Conversation
Take a reference of __bases__ tuple item before releasing the tuple reference, as it may destroy the tuple, possibly destroying (some of) its items.
Does class FakeClass:
def __init__(self,base=False):
self.base = base
@property
def __bases__(self):
if self.base:
return ()
return (FakeClass(True),)
issubclass(FakeClass(), int) work as a test case? It crashes for me on Python 3.7 |
Yeah, it appears to exhibit the same outcome. It crashes on master (4c1b6a6) but doesn't crash on this branch. Thanks for that, @pppery ! Well, this snippet is much less complicated than what I was trying to achieve (as I described in the bpo, I encountered this while using rpyc so I tried to imitate rpyc's I don't understand though, while it exhibits the bug, this doesn't:
I've added prints in |
I don't know why that works either. |
I get a crash with both examples. |
Weird. My assumption is that in the second example, another object gets allocated in the exact, same memory chunk, and so Anyway, I'll add a test using the first example, and a NEWS entry. |
The following snippet crashes:
(and the I'll add a test based on it. |
In the |
Objects/abstract.c
Outdated
@@ -2377,11 +2377,17 @@ abstract_issubclass(PyObject *derived, PyObject *cls) | |||
PyObject *bases = NULL; | |||
Py_ssize_t i, n; | |||
int r = 0; | |||
int derived_ref = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps just take a ref of derived
before the loop, and remove this variable and its checks altogether? My intention with derived_ref
was to avoid unnecessary memory writes to a shared/common location.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it may simplify the code, at cost of making it slightly slower. But this is not performance critical code, it is only called when any of arguments is not a type (very unusual case).
Alternatively, you can keep a reference to bases
until you get a new bases
. Py_SETREF
may help to keep the code shorter: Py_SETREF(bases, abstract_get_bases(derived))
, but it may still look less obvious.
Left it on to you. All variants are good enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Py_SETREF
seems nice. I didn't know of this macro. I'll use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not public. Don't use it in third-party code. It is safer to copy its definition under other name if you need it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't think I understood that last comment of yours. What do you mean by third-party code? Using it here is okay, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In your own code, in the code of your company or in the code of other projects except CPython.
It is not the part of the public C API and can be renamed, changed or removed in new Python versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhm, I see. Thanks for the tip!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Just added suggestion to reword the NEWS entry.
Objects/abstract.c
Outdated
@@ -2377,11 +2377,17 @@ abstract_issubclass(PyObject *derived, PyObject *cls) | |||
PyObject *bases = NULL; | |||
Py_ssize_t i, n; | |||
int r = 0; | |||
int derived_ref = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it may simplify the code, at cost of making it slightly slower. But this is not performance critical code, it is only called when any of arguments is not a type (very unusual case).
Alternatively, you can keep a reference to bases
until you get a new bases
. Py_SETREF
may help to keep the code shorter: Py_SETREF(bases, abstract_get_bases(derived))
, but it may still look less obvious.
Left it on to you. All variants are good enough.
@@ -0,0 +1,3 @@ | |||
Hold reference to ``__bases__`` tuple item before releasing the tuple itself | |||
in ``abstract_issubclass()``, avoiding a use-after-free in the next loop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
abstract_issubclass()
is an implementation detail. Python users do not know anything about it. Use things which expose the bug on the Python level: issubclass()
.
Also "hold reference to __bases__
tuple item" says how the bug has been fixed. Instead describe what the bug looked itself from the point of view of the common Python user (a crash in issubclass()
when __bases__
contains the only reference to its item, or something like).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, will change it to a more user-facing message.
d74f8bb
to
9d7f197
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!
Apologies, but I forgot about one tiny detail. Do you mind to add your name in Misc/ACKS and add the sentence "Patch by yourname." to the end of the NEWS entry?
Objects/abstract.c
Outdated
@@ -2377,11 +2377,17 @@ abstract_issubclass(PyObject *derived, PyObject *cls) | |||
PyObject *bases = NULL; | |||
Py_ssize_t i, n; | |||
int r = 0; | |||
int derived_ref = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not public. Don't use it in third-party code. It is safer to copy its definition under other name if you need it.
Codecov Report
@@ Coverage Diff @@
## master #18530 +/- ##
==========================================
- Coverage 82.20% 82.11% -0.09%
==========================================
Files 1958 1955 -3
Lines 589743 584066 -5677
Branches 44457 44457
==========================================
- Hits 484772 479631 -5141
+ Misses 95308 94788 -520
+ Partials 9663 9647 -16
Continue to review full report at Codecov.
|
Ofc, will add. Not sure about this Mac failure. I don't think it's related to my change. About the commit message for the squash: The first commit message is not entirely relevant anymore, so I'm thinking something about:
|
Ignore the failure on macOS. It is not related. |
@serhiy-storchaka anything else needed, or it looks ready to you? |
Thanks @Jongy for the PR, and @serhiy-storchaka for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7. |
Thanks @Jongy for the PR, and @serhiy-storchaka for merging it 🌮🎉.. I'm working now to backport this PR to: 3.8. |
GH-18606 is a backport of this pull request to the 3.7 branch. |
GH-18607 is a backport of this pull request to the 3.8 branch. |
Hold reference of __bases__ tuple until tuple item is done with, because by dropping the reference the item may be destroyed. (cherry picked from commit 1c56f8f) Co-authored-by: Yonatan Goldschmidt <yon.goldschmidt@gmail.com>
Hold reference of __bases__ tuple until tuple item is done with, because by dropping the reference the item may be destroyed. (cherry picked from commit 1c56f8f) Co-authored-by: Yonatan Goldschmidt <yon.goldschmidt@gmail.com>
Thanks @serhiy-storchaka :) |
Take a reference of
__bases__
tuple item before releasing the tuple reference,as it may destroy the tuple, possibly destroying (some of) its items.
@serhiy-storchaka, I will look into your suggestion in the bpo and see if I can reproduce it with a short snippet, so I can add tests for it. I will also think of some way to simplify the code, this function became too complicated with this change, perhaps it's time to break it?
Therefore, opening as a draft for the meantime.
https://bugs.python.org/issue39382