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

gh-125789: fix side-effects inasyncio callback scheduling methods#125833

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Conversation

@picnixz
Copy link
Member

@picnixzpicnixz commentedOct 22, 2024
edited
Loading

This is a proposal for fixing the side-effects that could arise fromPy_EQ. Similar to the patch where I fixedOrderedDict.__eq__, I did not modify the pure Python implementation but I can do it (I don't think we want to align both implementations; we just don't want the interpreter to crash and I don't think we can make it crash using the pure Python implementation only).

@Nico-Posada
Copy link
Contributor

You can still trigger a use after free with these changes since the tuple item never gets incref'd which allows you to abuse__eq__ andNotImplemented within evil classes.

importasynciofut=asyncio.Future()classsetup:def__eq__(self,other):print("in setup __eq__")returnFalsedef__del__(self):print("deleting self")cb_pad=lambda: ...fut.add_done_callback(cb_pad)# sets fut->fut_callback0fut.add_done_callback(setup())# sets fut->fut_callbacks[0]# removes callback from fut->fut_callback0 setting it to NULLfut.remove_done_callback(cb_pad)# evil MUST be a subclass of setup so that the evil __eq__ gets called firstclassevil(setup):def__eq__(self,value):fut._callbacks.clear()print("in evil __eq__")returnNotImplementedfut.remove_done_callback(evil())

@picnixz
Copy link
MemberAuthor

Oups, I noticed that there was already a UAF in the code but I forgot to fix it. Thanks!

Copy link
Member

@gvanrossumgvanrossum left a comment

Choose a reason for hiding this comment

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

Are you missing a file? This seems to only change tests.

@picnixz
Copy link
MemberAuthor

picnixz commentedOct 22, 2024
edited
Loading

Are you missing a file? This seems to only change tests.

Err... the_asynciomodule.c file was modified on my side. Maybe you are looking at a single commit? (or you marked the file as being reviewed?)

gvanrossum reacted with thumbs up emoji

Copy link
Member

@gvanrossumgvanrossum left a comment

Choose a reason for hiding this comment

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

LGTM, I just have some minor rewordings for some comments and messages to suggest.

@Nico-Posada
Copy link
Contributor

One more UAF needs to be patched out, it's possible to corrupt fut_callback0 then do the NotImplemented trick to use callback0 after it has been freed. Just needs an incref before usage.

if (self->fut_callback0!=NULL) {
intcmp=PyObject_RichCompareBool(self->fut_callback0,fn,Py_EQ);
if (cmp==-1) {

POC

importasynciofut=asyncio.Future()classa:def__eq__(self,other):print("in a __eq__",self)returnTruedef__del__(self):print("deleting",self)classb(a):def__eq__(self,other):print("in b __eq__")fut.remove_done_callback(None)returnNotImplementedfut.add_done_callback(a())fut.remove_done_callback(b())

@kumaraditya303kumaraditya303 self-assigned thisOct 23, 2024
Copy link
Member

@1st11st1 left a comment

Choose a reason for hiding this comment

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

Looks good overall.

@picnixz
Copy link
MemberAuthor

Thanks Nico for your insight and others for the review. I appreciate you taking the time to hunt those UAF so if you want to have a look at other usages of Py_EQ, I'd be happy to review/write the corresponding PRs. I'll address the other comments in a few hours!

Nico-Posada reacted with thumbs up emoji

@picnixzpicnixz added the 🔨 test-with-refleak-buildbotsTest PR w/ refleak buildbots; report in status section labelOct 23, 2024
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by@picnixz for commitf7b6730 🤖

If you want to schedule another build, you need to add the🔨 test-with-refleak-buildbots label again.

@bedevere-botbedevere-bot removed the 🔨 test-with-refleak-buildbotsTest PR w/ refleak buildbots; report in status section labelOct 23, 2024
return0;
}

if (!PyList_CheckExact(fut->fut_callbacks)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a test for this? Is there a way to assign a non-list to fut._callbacks?

Copy link
MemberAuthor

@picnixzpicnixzOct 24, 2024
edited
Loading

Choose a reason for hiding this comment

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

Mmh, actually no. The property is read-only and even with subclassing it's not possible. I'll replace the list checks by assertions.

EDIT: NVM, the macros PyList_GET* macros already take care of the assertion.

@graingert
Copy link
Contributor

What happens if fut._callbacks is mutated from another thread on 3.13t?

willingc reacted with eyes emoji

@picnixz
Copy link
MemberAuthor

What happens if fut._callbacks is mutated from another thread on 3.13t?

Err... I don't know. I haven't thought about this. I think it should be addressed in a follow-up PR though.

willingc reacted with eyes emoji

@picnixz
Copy link
MemberAuthor

Would it be easier to always return a copy from FutureObj_get_callbacks?

It might be easier but I don't know how it could affect performances. Asyncio experts need to confirm whether this could be a solution or not. For instance, we could return atuple instead of a list, and this would at least avoid people using.clear() or changing an item, though I'm not sure if we can still do other bad tricks.

Btw, there are some inconsistencies between the Python and the C implementation for that. When we don't have any callback, the C implementation returns None but the Python implementation returns an empty list. Should it be changed?

@graingert
Copy link
Contributor

graingert commentedOct 24, 2024
edited
Loading

It might be easier but I don't know how it could affect performances

It would be very rare to read fut._callbacks and it only seems to make sense in tests so performance of that branch is a non-issue. Adding extra checks to add_done_callback and schedule_callbacks is performance sensitive.

I think also that it would be more consistent to always return a copy. Perhaps PyFuture could also return a copy using a@property

I also suspect a copy is the only way to fix this on 3.13t without adding extra locks

@picnixz
Copy link
MemberAuthor

It would be very rare to read fut._callbacks and it only seems to make sense in tests so performance of that branch is a non-issue. Adding extra checks to add_done_callback and schedule_callbacks is performance sensitive.

Actually,fut._callbacks is already a copy of the internal list if the callback0 is set. The path that we attack is essentially:

if (fut->fut_callback0==NULL) {if (fut->fut_callbacks==NULL) {Py_RETURN_NONE;        }returnPy_NewRef(fut->fut_callbacks);// here    }

So we could also do something like

PyObject*new_list=PyList_New(PyList_GET_SIZE(fut->fut_callbacks));if (new_list==NULL) {returnNULL;        }for (i=0;i<PyList_GET_SIZE(fut->fut_callbacks);i++) {PyObject*cb=PyList_GET_ITEM(fut->fut_callbacks,i);Py_INCREF(cb);PyList_SET_ITEM(new_list,i,cb);        }returnnew_list;

instead and the problem is likely to be gone?

@picnixz
Copy link
MemberAuthor

We likely still have the UAF issue onfut->fut_callback0 but I think we wouldn't need to check at each loop iteration for the consistency offut->fut_callbacks.

Copy link
Contributor

@Nico-PosadaNico-Posada left a comment

Choose a reason for hiding this comment

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

A couple ideas.

returnNULL;
}
Py_INCREF(cb_tup);
PyObject*cb=PyTuple_GET_ITEM(cb_tup,0);
Copy link
Contributor

Choose a reason for hiding this comment

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

A little nitpicky, but the incref should be oncb notcb_tup. We don't necessarily care if the tuple gets deleted in PyObject_RichCompareBool, we care ifcb gets deleted. This technically works because I don't think there's a way to deletecb at this point if the tuple can't be deleted, but it's still worth noting.

gotofail;
}
Py_INCREF(cb_tup);
PyObject*cb=PyTuple_GET_ITEM(cb_tup,0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing here, I don't think it's possible to deletecb once the tuple has been incref'd, but if it's somehow possible to replace the first item in the tuple thencb will be freed possibly causing a UAF.

Copy link
Contributor

@kumaraditya303kumaraditya303 left a comment

Choose a reason for hiding this comment

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

We don't need all this complexity and type checks, I proposed an alternative at#125922

@bedevere-app
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phraseI have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@picnixz
Copy link
MemberAuthor

@kumaraditya303 While I understand that you're an asyncio maintainer, I would have appreciated that you first asked whether you wanted me to make the modifications here instead of plainly requesting changes and opening another PR. Nevertheless, I'm not sure that returning a copy entirely fixes the second UAF, in which case we can split the work into two (I also don't know which kind of "changes" you'd like to see here).

@kumaraditya303
Copy link
Contributor

While I understand that you're an asyncio maintainer, I would have appreciated that you first asked whether you wanted me to make the modifications here instead of plainly requesting changes and opening another PR. Nevertheless, I'm not sure that returning a copy entirely fixes the second UAF, in which case we can split the work into two (I also don't know which kind of "changes" you'd like to see here).

Please try to fix one issue in one PR. As far as I see, this PR has several changes, it change some pure python code, it tries to fix use-after-free issue related to malicious call_soon which is unrelated to issue, it would be better to fix that separately. Also this PR takes a different route to fix the issue by adding several type checks, which bulks out the code so I proposed an alternative to this in a separate PR.

Also since this bug exists in older versions, we should aim for the least invasive change and keep smaller PRs to be easily able to backport them.

I would appreciate if you create an issue of UAF issues related to scheduling and create an PR specifically fixing that, which I can easily backport. Thanks

@picnixz
Copy link
MemberAuthor

picnixz commentedOct 24, 2024
edited
Loading

Thanks. Yet, in the future, I would then appreciate that you mention those points first. For the UAF, I'll just wait that your change has been merged until creating a new one and extract the tests and PoCs.

@kumaraditya303
Copy link
Contributor

Thanks. Yet, in the future, I would then appreciate that you mention those points first. For the UAF, I'll just wait that your change has been merged until creating a new one and extract the tests and PoCs.

Okay, I'll take care of that next time, in the mean time you can review my PR

@picnixz
Copy link
MemberAuthor

picnixz commentedOct 24, 2024
edited
Loading

(I'll be back on my dev env tomorrow but I'll review this evening/tomorrow)

@picnixz
Copy link
MemberAuthor

@picnixzpicnixz deleted the fix/future-callback-type-check-125789 branchOctober 25, 2024 10:00
@picnixzpicnixz removed needs backport to 3.12only security fixes needs backport to 3.13bugs and security fixes labelsOct 29, 2024
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@graingertgraingertgraingert requested changes

@kumaraditya303kumaraditya303kumaraditya303 requested changes

@asvetlovasvetlovAwaiting requested review from asvetlovasvetlov is a code owner

@willingcwillingcAwaiting requested review from willingcwillingc is a code owner

@sethmlarsonsethmlarsonAwaiting requested review from sethmlarson

@1st11st1Awaiting requested review from 1st11st1 is a code owner

@gvanrossumgvanrossumAwaiting requested review from gvanrossum

+1 more reviewer

@Nico-PosadaNico-PosadaNico-Posada left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

@kumaraditya303kumaraditya303

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

7 participants

@picnixz@Nico-Posada@bedevere-bot@graingert@kumaraditya303@1st1@gvanrossum

[8]ページ先頭

©2009-2025 Movatter.jp