Skip to content
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

abstract_issubclass() doesn't take bases tuple item ref #83563

Closed
Jongy mannequin opened this issue Jan 18, 2020 · 6 comments
Closed

abstract_issubclass() doesn't take bases tuple item ref #83563

Jongy mannequin opened this issue Jan 18, 2020 · 6 comments
Labels
3.7 (EOL) end of life 3.8 only security fixes 3.9 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-crash A hard crash of the interpreter, possibly with a core dump

Comments

@Jongy
Copy link
Mannequin

Jongy mannequin commented Jan 18, 2020

BPO 39382
Nosy @nascheme, @serhiy-storchaka, @pppery, @miss-islington, @Jongy
PRs
  • bpo-39382: Avoid dangling object use in abstract_issubclass() #18530
  • [3.7] bpo-39382: Avoid dangling object use in abstract_issubclass() (GH-18530) #18606
  • [3.8] bpo-39382: Avoid dangling object use in abstract_issubclass() (GH-18530) #18607
  • Files
  • abstract_issubclass_refcount_fix.diff: abstract_issubclass() bases tuple item refcount fix
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2020-02-22.14:23:09.222>
    created_at = <Date 2020-01-18.17:30:12.767>
    labels = ['interpreter-core', '3.7', '3.8', '3.9', 'type-crash']
    title = "abstract_issubclass() doesn't take bases tuple item ref"
    updated_at = <Date 2020-02-22.14:23:09.221>
    user = 'https://github.com/Jongy'

    bugs.python.org fields:

    activity = <Date 2020-02-22.14:23:09.221>
    actor = 'serhiy.storchaka'
    assignee = 'none'
    closed = True
    closed_date = <Date 2020-02-22.14:23:09.222>
    closer = 'serhiy.storchaka'
    components = ['Interpreter Core']
    creation = <Date 2020-01-18.17:30:12.767>
    creator = 'Yonatan Goldschmidt'
    dependencies = []
    files = ['48851']
    hgrepos = []
    issue_num = 39382
    keywords = ['patch']
    message_count = 6.0
    messages = ['360247', '362050', '362155', '362463', '362464', '362465']
    nosy_count = 5.0
    nosy_names = ['nascheme', 'serhiy.storchaka', 'ppperry', 'miss-islington', 'Yonatan Goldschmidt']
    pr_nums = ['18530', '18606', '18607']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'crash'
    url = 'https://bugs.python.org/issue39382'
    versions = ['Python 3.7', 'Python 3.8', 'Python 3.9']

    @Jongy
    Copy link
    Mannequin Author

    Jongy mannequin commented Jan 18, 2020

    I encountered a crash using rpyc. Since rpyc is pure-Python, I guessed the problem is with Python itself.

    Originally tested on v3.7, the rest of this issue is based on v3.9.0a2 (compiling on an Ubuntu 18.04 docker).

    I narrowed down the rpyc-based snippet to this:

        # server side
        class X: pass
        x_instance = X()
    
        from rpyc.core import SlaveService
        from rpyc.utils.classic import DEFAULT_SERVER_PORT
        from rpyc.utils.server import ThreadedServer
        t = ThreadedServer(SlaveService, port=DEFAULT_SERVER_PORT, reuse_addr=True)
        t.start()
    
        # client side
        import rpyc
        conn = rpyc.classic.connect("localhost")
        x = conn.modules.__main__.x_instance
        y = x.__class__
        issubclass(y, int)

    Client side gets a SIGSEGV in _PyObject_LookupAttr, dereferencing an invalid tp pointer read from a posioned v object.

    After some reference count debugging, I found that for the rpyc y object (in the client code), accessing __bases__ returns a tuple with refcount=1, and it has a single item whose refcount is 1 as well.

    abstract_issubclass() calls abstract_get_bases() to get this refcount=1 tuple, and in the fastpath for single inheritance (tuple size = 1) it loads the single item from the tuple (derived = PyTuple_GET_ITEM(bases, 0)) and then decrements the refcount on the tuple, effectively deallocating the tuple and the derived object (whose only reference was from the tuple).

    I tried to mimic the Python magic rpyc does to get the same crash without rpyc, and reached the following snippet (which doesn't exhibit the problem):

        class Meta(type):
            def __getattribute__(self, attr):
                if attr == "__bases__":
                    class Base: pass
                    return (Base, )
                return type.__getattribute__(self, attr)
    
        class X(metaclass=Meta):
            pass
    issubclass(X().__class__, int)
    

    In this case, the refcount is decremented from 4 to 3 as abstract_issubclass() gets rid of the tuple (instead of from 1 to 0 as happens in the rpyc case). I don't know how rpyc does it.

    Attached is a patch that solves the problem (takes a ref of the tuple item before releasing the ref of the tuple itself).
    I'm not sure this change is worth the cost because, well, I don't fully understand the severity of it since I couldn't reproduce it without using rpyc. I assume dynamically-created, unreferenced __bases__ tuples as I have here are not so common.

    Anyway, if you do decide it's worth it, I'd be happy to improve the patch (it's quite messy the way this function is structured) and post it to GitHub :)

    Yonatan

    @Jongy Jongy mannequin added 3.9 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-crash A hard crash of the interpreter, possibly with a core dump labels Jan 18, 2020
    @serhiy-storchaka
    Copy link
    Member

    Thank you for your report and patch. Agree that the code does not look safe. Do you mind to create a pull request?

    Would be nice to add a test for it. The three references to the Python class are:

    • the __dict__ descriptor
    • the __weakref__ descriptor
    • the __mro__ tuple

    You can get rid of the first two by setting __slots__ = () in the class definition. But it is not so easy with the __mro__ tuple. I will try more.

    @serhiy-storchaka serhiy-storchaka added 3.7 (EOL) end of life 3.8 only security fixes labels Feb 16, 2020
    @pppery
    Copy link
    Mannequin

    pppery mannequin commented Feb 17, 2020

    I posted a test on the PR

    @serhiy-storchaka
    Copy link
    Member

    New changeset 1c56f8f by Yonatan Goldschmidt in branch 'master':
    bpo-39382: Avoid dangling object use in abstract_issubclass() (GH-18530)
    1c56f8f

    @miss-islington
    Copy link
    Contributor

    New changeset 43a0137 by Miss Islington (bot) in branch '3.7':
    bpo-39382: Avoid dangling object use in abstract_issubclass() (GH-18530)
    43a0137

    @miss-islington
    Copy link
    Contributor

    New changeset 0c1827e by Miss Islington (bot) in branch '3.8':
    bpo-39382: Avoid dangling object use in abstract_issubclass() (GH-18530)
    0c1827e

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life 3.8 only security fixes 3.9 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-crash A hard crash of the interpreter, possibly with a core dump
    Projects
    None yet
    Development

    No branches or pull requests

    2 participants