Repository navigation
prevent unwinding past FFI boundaries in code generation #18510
Description
Activity
- addedA-codegenArea: Code generationArea: Code generation
on Nov 1, 2014 Assigning P-high, not 1.0.
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?
@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.
cc @ranma42
- addedP-lowLow priorityLow priorityI-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.and removedP-mediumMedium priorityMedium priority
on Aug 25, 2016 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.
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:
- Add an ABORT terminator to
TerminatorKindand 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
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.
- Add an ABORT terminator to
22 remaining items
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_itemrow 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::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). - 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.
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.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::AbortbranchOK, 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 putr? @nikomatsakisso that it gets assigned to me.Second attempt here: #46833
- added 2 commits that reference this issue
on Dec 21, 2017 Thank you so much for pushing on this old bug @diwic!
Reacted by Hanna Kruppe and diwicWould it be possible to opt out of the abort generation? Perhaps by tagging the function with the existing #[unwind] attribute?
JFTR, as implemented, tagging the function with the #[unwind] attribute does opt out of the abort generation.
Reacted by Iain Nicolre-opening as 1.24.1 removed this behavior, even though it's expected to come back soon.
rluashould be added to https://github.com/rust-lang/rust/blob/master/src/tools/cargotest/main.rsReacted by Simon SapinClosing this in favor of #52652 (which is more recent and active).
Oops, sorry I didn’t find this when opening #52652 !
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.EDIT: Mentoring instructions can be found here.