You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
prevent unwinding past FFI boundaries in code generation #18510
It's undefined to unwind past an FFI boundary such as a pub extern "C" fn. Code generation should automatically insert a landing pad doing an abort. This will eliminate the class of memory safety errors resulting from unwinding into C from Rust. LLVM will be able to optimize it out if it is being caught and handled explicitly, such as to translate into an error code for C.
Triage: I'm not aware of a code change here, I believe that we are expecting people to handle this themselves with catch_panic or whatever it's called.
/cc @rust-lang/libs @rust-lang/lang is that the only solution we are pursing here, or do we plan on doing it automatically eventually?
@steveklabnik IIRC, there's still work to do to ensure that you correctly get an abort if you don't recover from the panic before hitting the boundary. I believe that's what this issue is about.
Has there been any work done on this? Automatically inserting catch_unwind statements at FFI boundaries that automatically abort on uncaught panics seems like a straightforward way to close this soundness hole. I see where @brson silently adjusted the tags, so maybe the core team reviewed it back in August.
No work has been done on this to my knowledge, but I am in favor of the solution you proposed @coder543, and would be happy to work with you on implementing it. If nothing else, it would help us gain experience with overhead etc.
I think that the strategy we would use is to modify the code that generates MIR (our mid-level intermediate IR) for calls, which is found in this file here. You can see that it generates the code to execute upon unwind right here, on this line -- currently, this is always cleanup code. We could modify it to inspect the type of the value being invoked (and, in particular, its ABI) and generate alternate unwind code that aborts (I'm not sure the best way to encode an abort right now though).
The function being called is stored in the fun variable, which is an Operand<'tcx>. Operands support a ty method (defined here). In principle, I suppose, we could call it like fun.ty(&this.local_decls, this.hir.tcx()) -- that method is only meant to be called after MIR construction is complete, but I think it will actually work just fine. (Otherwise, we could extract the type of the function being called from the HAIR, which is the intermediate representation we are building things from; but that's just a touch more involved.)
The main question then is how to generate MIR that aborts. Right now I think we have no way to encode an abort into the IR. We could probably add an ABORT terminator (i.e., a new variant of the MIR data structure TerminatorKind, found in src/librustc/mir/mod.rs).
So, if you were going to implement this, I think we would do the following steps:
Add an ABORT terminator to TerminatorKind and work through the various implications. That could be a PR by itself, although it'd be presently unused.
Inspect the types of callees as described above, find those with C ABIs, and generate an ABORT terminator instead.
That might be two PRs, or maybe one. To get started, one might also skip the first step, and just experiment with having the compiler print out a match when it finds a C ABI function, and then worry about how to handle it.
My use case is that I would like to be able to catch Rust panics from the C/C++ side.
I have two concerns about this approach: 1) does it really work in practice, and if so what kind of exception is a Rust exception in C++ and 2) why can't you use catch_unwind?
@nikomatsakis or anyone who would like to provide mentoring/review/feedback:
So I made some progress today! (Yay!) As can be seen in diwic@729092c
It's probably not ready for PR yet, could use some mentoring / feedback:
Should tests be added and if so how can one test that an abort has happened? As of now I have only manually inspected mir and llvm-ir to see that it looks correct.
I'm not sure why my is_foreign_item row is not working, I think that would be better if we could use it
For all places where the compiler complained about a missing TerminatorKind::Abort branch, I basically added one that did the same as TerminatorKind::Resume (except for code generation). This might need to be double checked due to behavioral differences (after all, resume resumes and abort aborts).
Not sure what to do on the mir inlining pass. But it does not seem to happen in my small tests, maybe this is something experimental which is not enabled by default?
Should tests be added and if so how can one test that an abort has happened? As of now I have only manually inspected mir and llvm-ir to see that it looks correct.
I'm not sure why my is_foreign_item row is not working, I think that would be better if we could use it
Can you say a bit more, I'm not sure what that means?
For all places where the compiler complained about a missing TerminatorKind::Abort branch
OK, I'll double-check when you open a PR.
Not sure what to do on the mir inlining pass. But it does not seem to happen in my small tests, maybe this is something experimental which is not enabled by default?
It is experimental and does not happen by default. You can enable it with -Zmir-opt-level=2. It would be great if you did test it -- I think you should be able to "link up" the abort block from the inlined function into the abort block in the caller, presumably handling it in a similar fashion to how resume is handled or something...I'll have to go look in more depth.
In general, it'd be great if you wanted to open a PR and we can discuss in more depth there! Feel free to tag it with [WIP] in the subject line, and be sure to put r? @nikomatsakis so that it gets assigned to me.
Activity
pnkfelix commentedon Nov 6, 2014
Assigning P-high, not 1.0.
steveklabnik commentedon Dec 31, 2015
Triage: I'm not aware of a code change here, I believe that we are expecting people to handle this themselves with
catch_panicor whatever it's called./cc @rust-lang/libs @rust-lang/lang is that the only solution we are pursing here, or do we plan on doing it automatically eventually?
aturon commentedon Dec 31, 2015
@steveklabnik IIRC, there's still work to do to ensure that you correctly get an abort if you don't recover from the panic before hitting the boundary. I believe that's what this issue is about.
ranma42 commentedon Dec 31, 2015
cc @ranma42
coder543 commentedon Jul 10, 2017
Has there been any work done on this? Automatically inserting catch_unwind statements at FFI boundaries that automatically abort on uncaught panics seems like a straightforward way to close this soundness hole. I see where @brson silently adjusted the tags, so maybe the core team reviewed it back in August.
nikomatsakis commentedon Jul 17, 2017
UPDATE: This comment appears to have been a bit confused! See revised instructions below.
No work has been done on this to my knowledge, but I am in favor of the solution you proposed @coder543, and would be happy to work with you on implementing it. If nothing else, it would help us gain experience with overhead etc.
I think that the strategy we would use is to modify the code that generates MIR (our mid-level intermediate IR) for calls, which is found in this file here. You can see that it generates the code to execute upon unwind right here, on this line -- currently, this is always cleanup code. We could modify it to inspect the type of the value being invoked (and, in particular, its ABI) and generate alternate unwind code that aborts (I'm not sure the best way to encode an abort right now though).
The function being called is stored in the
funvariable, which is anOperand<'tcx>. Operands support atymethod (defined here). In principle, I suppose, we could call it likefun.ty(&this.local_decls, this.hir.tcx())-- that method is only meant to be called after MIR construction is complete, but I think it will actually work just fine. (Otherwise, we could extract the type of the function being called from the HAIR, which is the intermediate representation we are building things from; but that's just a touch more involved.)The main question then is how to generate MIR that aborts. Right now I think we have no way to encode an abort into the IR. We could probably add an ABORT terminator (i.e., a new variant of the MIR data structure
TerminatorKind, found insrc/librustc/mir/mod.rs).So, if you were going to implement this, I think we would do the following steps:
TerminatorKindand work through the various implications. That could be a PR by itself, although it'd be presently unused.CABIs, and generate an ABORT terminator instead.That might be two PRs, or maybe one. To get started, one might also skip the first step, and just experiment with having the compiler print out a match when it finds a C ABI function, and then worry about how to handle it.
22 remaining items
diwic commentedon Oct 7, 2017
@iainnicol
I have two concerns about this approach: 1) does it really work in practice, and if so what kind of exception is a Rust exception in C++ and 2) why can't you use
catch_unwind?diwic commentedon Oct 7, 2017
@nikomatsakis or anyone who would like to provide mentoring/review/feedback:
So I made some progress today! (Yay!) As can be seen in diwic@729092c
It's probably not ready for PR yet, could use some mentoring / feedback:
is_foreign_itemrow is not working, I think that would be better if we could use itTerminatorKind::Abortbranch, I basically added one that did the same asTerminatorKind::Resume(except for code generation). This might need to be double checked due to behavioral differences (after all,resumeresumes andabortaborts).nikomatsakis commentedon Nov 22, 2017
@diwic
Yes! Tests should definitely be added. One way to do this is to write a
run-passtest that invokes itself usingCommand, passing some arguments and then checking the return code.sigpipe-should-be-ignored.rsis an example of a test that uses that technique.Can you say a bit more, I'm not sure what that means?
OK, I'll double-check when you open a PR.
It is experimental and does not happen by default. You can enable it with
-Zmir-opt-level=2. It would be great if you did test it -- I think you should be able to "link up" the abort block from the inlined function into the abort block in the caller, presumably handling it in a similar fashion to how resume is handled or something...I'll have to go look in more depth.In general, it'd be great if you wanted to open a PR and we can discuss in more depth there! Feel free to tag it with
[WIP]in the subject line, and be sure to putr? @nikomatsakisso that it gets assigned to me.diwic commentedon Dec 19, 2017
Second attempt here: #46833
Mir: Abort on nounwind ABIs
Mir: fixup nits in previous commit (f536143)
bstrie commentedon Dec 25, 2017
Thank you so much for pushing on this old bug @diwic!
diwic commentedon Dec 25, 2017
@iainnicol
JFTR, as implemented, tagging the function with the #[unwind] attribute does opt out of the abort generation.
steveklabnik commentedon Mar 2, 2018
re-opening as 1.24.1 removed this behavior, even though it's expected to come back soon.
nox commentedon Mar 2, 2018
rluashould be added to https://github.com/rust-lang/rust/blob/master/src/tools/cargotest/main.rsMark-Simulacrum commentedon Jul 29, 2018
Closing this in favor of #52652 (which is more recent and active).
SimonSapin commentedon Jul 29, 2018
Oops, sorry I didn’t find this when opening #52652 !