Skip to content

bpo-25750: fix refcounts in type_getattro() #6118

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

Merged
merged 1 commit into from
Sep 7, 2018

Conversation

jdemeyer
Copy link
Contributor

@jdemeyer jdemeyer commented Mar 14, 2018

When calling tp_descr_get(attr, obj, type), make sure that we own a reference to attr. In the existing code, we only have a borrowed reference.

This bug goes back to at least Python 2.7, so ideally it should be backported to all previous versions.

https://bugs.python.org/issue25750

Downstream: https://trac.sagemath.org/ticket/19633

@the-knights-who-say-ni
Copy link

Hello, and thanks for your contribution!

I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA).

Unfortunately we couldn't find an account corresponding to your GitHub username on bugs.python.org (b.p.o) to verify you have signed the CLA (this might be simply due to a missing "GitHub Name" entry in your b.p.o account settings). This is necessary for legal reasons before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue.

Thanks again to your contribution and we look forward to looking at it!

Copy link
Member

@berkerpeksag berkerpeksag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In https://bugs.python.org/issue25750#msg255566 you said:

My patch does fix the crash in Lib/test/crashers/borrowed_ref_2.py on Python 2.7.10.

Is it possible to convert https://github.com/python/cpython/blob/2.7/Lib/test/crashers/borrowed_ref_2.py to a simple test case?

Thanks!

@jdemeyer
Copy link
Contributor Author

Is it possible to convert https://github.com/python/cpython/blob/2.7/Lib/test/crashers/borrowed_ref_2.py to a simple test case?

I can try, but I never managed to get the vanilla Python testsuite to pass on my system, so it's hard to test it myself.

@berkerpeksag
Copy link
Member

We can use Travis to test it :)

By the way, the test suite should pass on most systems. I suggest reporting test failures on your system on bugs.p.o.

@jdemeyer
Copy link
Contributor Author

jdemeyer commented Apr 2, 2018

Is it possible to convert https://github.com/python/cpython/blob/2.7/Lib/test/crashers/borrowed_ref_2.py to a simple test case?

That example seems to crash only on Python 2, not Python 3. In fact, I do not know how to reproduce the crash on Python 3.

@jdemeyer
Copy link
Contributor Author

I added a testcase. This was a lot harder to reproduce on Python 3.8 than Python 2.7, probably due to the changes regarding calling functions.

@jdemeyer
Copy link
Contributor Author

So, in some sense, the backport to Python 2.7 is more important than the fix to Python 3.8. The underlying issue is the same, it's just harder to accidentally hit it because of unrelated changes.

@jdemeyer
Copy link
Contributor Author

jdemeyer commented Jul 5, 2018

@berkerpeksag Can you please finish the review of this PR?

try:
from _testcapi import bad_get
except ImportError:
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just skip the test with an explanation instead of returning None here.

@@ -0,0 +1,2 @@
Fix rare Python crash due to bad refcounting in ``type_getattro()`` if a
descriptor deletes itself from the class.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add "Patch by Jeroen Demeyer."

@@ -4776,6 +4798,7 @@ static PyMethodDef TestMethods[] = {
{"get_mapping_items", get_mapping_items, METH_O},
{"test_pythread_tss_key_state", test_pythread_tss_key_state, METH_VARARGS},
{"hamt", new_hamt, METH_NOARGS},
{"bad_get", bad_get, METH_FASTCALL},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason to use METH_FASTCALL here? Can't we just use METH_VARARGS? Did you use it because it was easy to reproduce the crash in Python 3?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if it's not possible to crash the interpreter in a pure Python 3 code, I wonder whether we should only fix this in Python 2.7.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's not possible to crash the interpreter in a pure Python 3 code

I never said that it's impossible. I just said that I didn't manage to. There is a lot of C code in CPython, I didn't audit all of it to ensure that it can't be used to get this crash. Even if I did, somebody might add a function tomorrow which does allow to induce the crash.

Copy link
Contributor Author

@jdemeyer jdemeyer Jul 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason to use METH_FASTCALL here?

Absolutely. When calling with METH_VARARGS, a temporary tuple is constructed which holds an additional reference. So there won't be a crash in that case. I'm guessing that this is also the reason why the crash is more likely to occur on Python 2.7.

descr = Descr()
def __new__(cls):
cls.descr = None
cls.lst = [2**i for i in range(10000)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm at work at the moment, so I don't have a chance to test this, but do we need cls.lst to reproduce the crash?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need cls.lst to reproduce the crash?

That line is just to mess with the memory to ensure a sufficiently corrupted state.

@jdemeyer
Copy link
Contributor Author

jdemeyer commented Jul 5, 2018

If you insist, here is a pure Python 3 reproducer:

import pickle

class Descr:
    __get__ = pickle.dump
    __set__ = None

class M(type):
    def __int__(self):
        self.write = None
        return 0

class X(type, metaclass=M):
    write = Descr()

class Y(metaclass=X):
    pass

Y.write

However, this code is so artificial that it would be really strange to add it to the testsuite. I think that the current C code does a much better job of showing the error.

@jdemeyer
Copy link
Contributor Author

jdemeyer commented Jul 5, 2018

I made the requested changes on the branch.

@jdemeyer
Copy link
Contributor Author

if it's not possible to crash the interpreter in a pure Python 3 code, I wonder whether we should only fix this in Python 2.7.

First of all, it is possible to crash the interpreter using pickle.dump as I demonstrated above.

Second, CPython is more than just the Python interpreter: even if a bug is only relevant for the Python/C API, it's still a bug that should be fixed.

So we can get this merged finally? It's an obvious bug fix, and I see nothing controversial about it.

@jdemeyer
Copy link
Contributor Author

jdemeyer commented Sep 4, 2018

@vstinner You obviously care about abusing borrowed references...

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typeobject.c fix LGTM, but I'm not sure about the test. The test seems quite complicated and not reliable :-( Do we really have to add an unit test for that?

@serhiy-storchaka: Would you be ok to omit the test on such fix?

I dislike having a test doing "cls.descr = None" and "Create this large list to corrupt some unused memory". IMHO the test is too close to the exact C implementation.

@jdemeyer
Copy link
Contributor Author

jdemeyer commented Sep 5, 2018

I added a testcase by request of @berkerpeksag

@jdemeyer
Copy link
Contributor Author

jdemeyer commented Sep 5, 2018

I dislike having a test doing "cls.descr = None"

Why? That is exactly the main point of the test (unless you prefer del cls.descr?). Only the "Create this large list to corrupt some unused memory" part is hackish indeed.

@vstinner
Copy link
Member

vstinner commented Sep 5, 2018

I dislike having a test doing "cls.descr = None"

Why? That is exactly the main point of the test (unless you prefer del cls.descr?).

If it becomes part of the test suite, it means that we have to support this weird use case. I'm not sure that it should be part of the "language specification".

@berkerpeksag
Copy link
Member

The whole point of this issue and PR is that someone is already depending on this behavior. We already have tests for more rare cases (e.g. https://bugs.python.org/issue31781) than this one.

We could wrap the test with the @cpython_only decorator if you think that the test is too close to the implementation.

@jdemeyer
Copy link
Contributor Author

jdemeyer commented Sep 6, 2018

I'm not sure that it should be part of the "language specification".

So you think that doing cls.descr = None is not part of the language specification? I would guess that adding/changing attributes of classes like that is a reasonable standard feature.

@vstinner
Copy link
Member

vstinner commented Sep 6, 2018

I have no strong opinion on this change. I let someone else take a decision.

When calling tp_descr_get(self, obj, type),
make sure that we own a reference to "self"
@jdemeyer
Copy link
Contributor Author

jdemeyer commented Sep 6, 2018

Since the testcase is apparently controversial, I decided to separate it as a new pull request: #9084

I really hope that this issue can now finally be fixed.

@vstinner vstinner merged commit 8f73548 into python:master Sep 7, 2018
@miss-islington
Copy link
Contributor

Thanks @jdemeyer for the PR, and @vstinner for merging it 🌮🎉.. I'm working now to backport this PR to: 2.7, 3.6, 3.7.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 7, 2018
When calling tp_descr_get(self, obj, type), make sure that
we own a strong reference to "self".
(cherry picked from commit 8f73548)

Co-authored-by: jdemeyer <[email protected]>
@bedevere-bot
Copy link

GH-9087 is a backport of this pull request to the 3.7 branch.

@bedevere-bot
Copy link

GH-9088 is a backport of this pull request to the 3.6 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 7, 2018
When calling tp_descr_get(self, obj, type), make sure that
we own a strong reference to "self".
(cherry picked from commit 8f73548)

Co-authored-by: jdemeyer <[email protected]>
@bedevere-bot
Copy link

GH-9089 is a backport of this pull request to the 2.7 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 7, 2018
When calling tp_descr_get(self, obj, type), make sure that
we own a strong reference to "self".
(cherry picked from commit 8f73548)

Co-authored-by: jdemeyer <[email protected]>
@vstinner
Copy link
Member

vstinner commented Sep 7, 2018

Since the testcase is apparently controversial, I decided to separate it as a new pull request: #9084

I'm sorry for being picky :-(

I really hope that this issue can now finally be fixed.

Sure, I just merged it. miss-inlington bot created the 3 backports and I already approved them, they will be merged once the CI tests pass.

miss-islington added a commit that referenced this pull request Sep 7, 2018
When calling tp_descr_get(self, obj, type), make sure that
we own a strong reference to "self".
(cherry picked from commit 8f73548)

Co-authored-by: jdemeyer <[email protected]>
vstinner pushed a commit that referenced this pull request Sep 7, 2018
When calling tp_descr_get(self, obj, type), make sure that
we own a strong reference to "self".
(cherry picked from commit 8f73548)

Co-authored-by: jdemeyer <[email protected]>
vstinner added a commit that referenced this pull request Sep 7, 2018
When calling tp_descr_get(self, obj, type), make sure that
we own a strong reference to "self".

(cherry picked from commit 8f73548)
@jdemeyer jdemeyer deleted the bpo-25750 branch August 8, 2019 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants