GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Bugs in asyncio.Future.remove_done_callback() cause segfault. · Issue #97592 · python/cpython

Bugs in asyncio.Future.remove_done_callback() cause segfault. #97592

New issue
Copy link
New issue
Copy link
Closed
Closed
Bugs in asyncio.Future.remove_done_callback() cause segfault.#97592
Copy link
Assignees
gvanrossum
Labels
3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixestopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump

Description

@xiaxinmeng
xiaxinmeng
opened on Sep 27, 2022
Issue body actions

Crash report

The following example triggers a segfault on the latest stable Python3.8.14. I think there might be a bug in asyncio.Future.remove_done_callback().

import asyncio
fut = asyncio.Future()
fut.add_done_callback(str)
for str in range(63):
    fut.add_done_callback(id)

class evil:
    def __eq__(self, other):
        fut.remove_done_callback(other)
fut.remove_done_callback(evil())

Error messages

Segmentation fault (core dumped)

Your environment

  • CPython versions tested on: Python 3.8.14, Python 3.9.0, main branch 68c46ae
  • Operating system and architecture: [GCC 7.5.0] on linux

Activity

  1. xiaxinmeng
    added
    type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
    on Sep 27, 2022
  2. AlexWaygood
    added
    topic-asyncio
    on Sep 27, 2022
  3. ezio-melotti
    added this to asyncioon Sep 27, 2022
  4. ezio-melotti
    moved this to Todo in asyncioon Sep 27, 2022
  5. gvanrossum
    added
    3.11only security fixesonly security fixes
    3.8 (EOL)end of lifeend of life
    3.9 (EOL)end of lifeend of life
    3.10 (EOL)end of lifeend of life
    3.12only security fixesonly security fixes
    on Sep 27, 2022
  6. gvanrossum commented on Sep 27, 2022

    @gvanrossum
    gvanrossum
    on Sep 27, 2022
    Member
    More actions

    Yeah, I can still repro this on 3.11. It doesn't crash when the pure-Python asyncio implementation is used (sys.modules['_asyncio'] = None) so this appears to be crashing in _asyncio.

    Someone needs to look into the code there and see what is happening.

  7. CharlieZhao95 commented on Sep 28, 2022

    @CharlieZhao95
    CharlieZhao95
    on Sep 28, 2022
    Contributor
    More actions

    I simply debugged with the following code:

    import asyncio
    
    async def crash():
        fut = asyncio.Future()
        fut.add_done_callback(str)
        for _ in range(10):
            fut.add_done_callback(id)
    
        class Evil:
            def __eq__(self, other):
                fut.remove_done_callback(other)
    
        fut.remove_done_callback(Evil())
        asyncio.create_task(set_after(fut, 1, '... world'))
        print('hello ...')
        print(await fut)
    
    
    if __name__ == "__main__":
        asyncio.run(crash())

    The core dump seems to happen in the _asyncio_Future_remove_done_callback function of _asynciomodule.c.
    At some point, the self->fut_callbacks is set to null, which causes crashing. In my tests it seems to be related to Py_CLEAR(self->fut_callbacks);

    static PyObject *
    _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
    {
    ...
        // The `self->fut_callbacks` here is a nullptr, which causes the core dump
        for (i = 0; i < PyList_GET_SIZE(self->fut_callbacks); i++) {
            int ret;
            PyObject *item = PyList_GET_ITEM(self->fut_callbacks, i);
            Py_INCREF(item);
            ret = PyObject_RichCompareBool(PyTuple_GET_ITEM(item, 0), fn, Py_EQ);
            if (ret == 0) {
                if (j < len) {
                    PyList_SET_ITEM(newlist, j, item);
                    j++;
                    continue;
                }
                ret = PyList_Append(newlist, item);
            }
            Py_DECREF(item);
            if (ret < 0) {
                goto fail;
            }
        }
    
        if (j == 0) {
            Py_CLEAR(self->fut_callbacks);
            Py_DECREF(newlist);
            return PyLong_FromSsize_t(len + cleared_callback0);
        }
    }
  8. gvanrossum commented on Sep 28, 2022

    @gvanrossum
    gvanrossum
    on Sep 28, 2022
    Member
    More actions

    Thanks, that's good research! The cause is now clear: the PyObject_RichCompareBool call invokes the evil class's __eq__ method, which calls the same function and ends up clearing fut_callbacks.

    The fix would seem to require some check for whether fut_callbacks is NULL before doing anything further to it. This seems a little tricky, we need to check in the for loop header, but also in the code (that you snipped) below that assigns newlist to a slice of fut_callbacks.

    Do you want to create the PR to get full credit?

  9. gvanrossum
    added a commit that references this issue on Sep 28, 2022

    pythonGH-97592: Anticipate fut_callbacks being cleared by PyObject_Ri…

    f1b2db8
  10. CharlieZhao95 commented on Sep 29, 2022

    @CharlieZhao95
    CharlieZhao95
    on Sep 29, 2022
    Contributor
    More actions

    Thanks for your guidance! I notice that you have created a commit, I wonder if it would be better to add a test case that can trigger the crash :)

  11. kumaraditya303
    assigned
    gvanrossum
    on Sep 29, 2022
  12. kumaraditya303
    moved this from Todo to In Progress in asyncioon Sep 29, 2022
  13. gvanrossum commented on Sep 29, 2022

    @gvanrossum
    gvanrossum
    on Sep 29, 2022
    Member
    More actions

    Yeah, I didn't get to that yet. It should be a simple case of converting your test case to a unit test.

  14. gvanrossum commented on Sep 29, 2022

    @gvanrossum
    gvanrossum
    on Sep 29, 2022
    Member
    More actions

    FWIW looking for a good place to add that test I noticed that there was a very similar test case already, fixing #73149. @xiaxinmeng did you derive your demo from that code?

  15. gvanrossum
    added a commit that references this issue on Sep 29, 2022

    pythonGH-97592: Anticipate fut_callbacks being cleared by PyObject_Ri…

    bc55838
  16. kumaraditya303
    removed
    3.9 (EOL)end of lifeend of life
    3.8 (EOL)end of lifeend of life
    on Sep 30, 2022
  17. xiaxinmeng commented on Sep 30, 2022

    @xiaxinmeng
    xiaxinmeng
    on Sep 30, 2022
    Author
    More actions

    FWIW looking for a good place to add that test I noticed that there was a very similar test case already, fixing #73149. @xiaxinmeng did you derive your demo from that code?

    Thanks for your efforts on fixing this bug! Yes, this case is derived from that code. The original bug has been fixed and the original test case cannot crash Python anymore. So we mutate the dependency relations in the original code and found this one crashing Python again.

  18. gvanrossum
    added a commit that references this issue on Sep 30, 2022

    GH-97592: Fix crash in C remove_done_callback due to evil code (#97660)

    63780f4
  19. miss-islington
    added 2 commits that reference this issue on Sep 30, 2022

    pythonGH-97592: Fix crash in C remove_done_callback due to evil code (p…

    6ffd0e7

    pythonGH-97592: Fix crash in C remove_done_callback due to evil code (p…

    72e19e4
  20. gvanrossum
    closed this as completedon Sep 30, 2022
  21. Repository owner moved this from In Progress to Done in asyncioon Sep 30, 2022
  22. miss-islington
    added 2 commits that reference this issue on Sep 30, 2022

    GH-97592: Fix crash in C remove_done_callback due to evil code (GH-97660

    3124618

    GH-97592: Fix crash in C remove_done_callback due to evil code (GH-97660

    54bbb5e
  23. serhiy-storchaka
    added a commit that references this issue on Oct 2, 2022

    pythonGH-97592: Fix crash in C remove_done_callback due to evil code (p…

    67851dc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @gvanrossum
    gvanrossum

Labels

3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixestopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump

Projects

  • asyncio
    Status
    Done
    Show more project fields

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.