-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
std::process::unix: Command: Do not unwind past fork(), in child #80263
Conversation
r? @m-ou-se (rust-highfive has picked a reviewer for you, use r? to override) |
@rustbot modify labels +T-libs +A-runtime |
I don't think the current behavior is strange, or at least no more than |
Oh, but you're only changing the |
Josh Stone writes ("Re: [rust-lang/rust] std::process::unix: Do not unwind past fork(), in child (#80263)"):
I don't think the current behavior is strange, or at least no more than fork
itself is strange. You've created a new process as a copy of the first, and it
could certainly return from the fork point, so why wouldn't unwinding do that
too? Think of a forking daemon, for example.
I think you must have misunderstood the context. The code I am
changing here is part of the implementation of the (portable)
`Command` facility.
The user who is using Command should not expect panics (whether from
some bug in Command, or some pre_exec hook) to have this behaviour.
I agree that a user who was using raw libc::fork might well want to
unwind past fork in the child. But `Command` is not `libc::fork`.
Considering your daemon example: `Command` can't (sensibly) be used
for trad unix daemonisation. Someone who wants to do trad unix
daemonisation is well-served by libc (and perhaps higher-level
but still non-portable facilities in non-std crates).
Ian.
…--
Ian Jackson <[email protected]> These opinions are my own.
Pronouns: they/he. If I emailed you from @fyvzl.net or @evade.org.uk,
that is a private address which bypasses my fierce spamfilter.
|
Josh Stone writes ("Re: [rust-lang/rust] std::process::unix: Do not unwind past fork(), in child (#80263)"):
Oh, but you're only changing the fork in Command -- that's much more specific
than your PR description implies.
Ah, yes. Our messages crossed.
There is no other call to libc::fork in std.
…--
Ian Jackson <[email protected]> These opinions are my own.
Pronouns: they/he. If I emailed you from @fyvzl.net or @evade.org.uk,
that is a private address which bypasses my fierce spamfilter.
|
I edited the title to clarify the scope. Thanks for your attention. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this!
@@ -53,7 +57,8 @@ impl Command { | |||
|
|||
let pid = unsafe { | |||
match result { | |||
0 => { | |||
0 => (#[unwind(aborts)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be the first usage of #[unwind(aborts)]
outside a test, and the first usage of #[unwind]
on a closure. Not sure if this is stable enough.
(I believe this attribute was originally only meant for extern
functions.)
@Mark-Simulacrum Do you know, or do you know who would know?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review.
In defence of using this here: my MR adds a test case which exercises this panic -> unwind -> abort path, and of course there is no non-abort return path because the closure ends with _exit
(and returns !
)
If #[unwind(aborts)]
is not stable enough, should I open-code something with catch_unwind
? It seemed to me that using a built-in library facility (even an unstable one) for this was better than an ad-hoc reimplementation of the same functionality.
@rustbot modify labels -S-waiting-on-author +S-waiting-on-review |
r? @Mark-Simulacrum to validate this usage of |
Yes, catch_unwind is probably the better option. I'm pretty sure the unwind attribute in theory should work, but I wouldn't want to rely on it in this context personally. catch_unwind should still compile to similar or identical assembly as the unwind attr. |
@ijackson it also looks like your git commits aren't associated with your github account, fwiw (fine for contributing, just saying in case it's unintentional). If you want to squash commits on the next push that'll also avoid another runaround. |
26bcd81
to
4bafe7b
Compare
The job Click to see the possible cause of the failure (guessed by this bot)
|
4bafe7b
to
a38d929
Compare
The failure is this, from my new test case:
139 is I could add SIGSEGV to the permitted list. But, is this really right? It seems odd. I think @Mark-Simulacrum do you know if this is an expected result from |
@bors r- |
I want to reproduce this failure locally. Can someone help me with a build/install problem, which is blocking me? I think I need a cross rustc targeting But I had trouble finding how to build that cross toolchain. Rust's I looked for instructions in various places including general search engines and the in-tree |
@rustbot modify labels +E-help-wanted +O-musl +A-rustbuild -T-libs -A-runtime |
My recommendation is to test via the Docker container ( |
I managed to get a musl i686 build by inspecting the rules and doing stuff by hand. It worked just fine both before and after my change. I'm now wrestling docker. |
Well, eventually my formal docker run with
That's with 20e2172 which is tree-identical to the 73ae9d635b31311e980b2d8fad7277e384f6f2cb which bors built. (Strictly, I added one further change, to drop So I apparently cannot reproduce this failure locally. I hesitate to suggest just trying it again since "random lossage" is really not a very convincing explanation. Is there some way to debug this in something more closely resembling the CI environment ? |
OTOH this does give me confidence that the test case is correct not to consider So the fact that this segfaulted in the CI suggests a real bug. I doubt that's in my patch to the stdlib (which doesn't even introduce any new unsafe). It also seems to me that there is nothing in my test case which ought to cause UB. The ony unsafe is this:
So I am led to think there is a pre-existing bug in panic handling :-/ |
I think panicking from the child process is already UB (in a multithreaded program)? The documentation of
The
(EDIT: May not be relevant here - and the
I believe panicking or catching unwinding is not signal-safe, because |
Hmmm. I don't think I really agree. In C, malloc after fork is not, in general, UB. The spec says
"Errors" is quite vague but my interpretation is that eg trying to acquire a stdio lock might fail because it was locked by a thread in the parent at the time of the fork. But maybe musl is more restrictive about this. What can be done about this? I don't think we can really have a programming environment where panicking is UB. Perhaps the right answer is to install an aborting panic hook so that we never unwind. Even with [edited to link to SuS] |
More digging found me these links: https://lists.uclibc.org/pipermail/uclibc/2011-March/045130.html The first one is from the musl authors. One striking statement there is that malloc after fork in a multithreaded program used to be specified to work but nowadays the libc is allowed to make it UB. I'm glad that I'm not going completely mad! In practical terms it seems that at least for musl this is difficult to make work and not likely to be done any time soon, if at all. And the Rust stdlib should be conservative. So I think that means preventing any calls to malloc after fork. I'll see what I can do to ensure that at least in a plausible subset of cases (which I think has to include at least array bounds violations!). Maybe I can also somehow nobble the global allocator to abort immediately. The footgun here is quite large... |
I think I have succeeded. The result doesn't look much like this MR and I think it is probably more sensible to start afresh. So I will close this one and make a new MR shortly. |
Do not allocate or unwind after fork ### Objective scenarios * Make (simple) panics safe in `Command::pre_exec_hook`, including most `panic!` calls, `Option::unwrap`, and array bounds check failures. * Make it possible to `libc::fork` and then safely panic in the child (needed for the above, but this requirement means exposing the new raw hook API which the `Command` implementation needs). * In singlethreaded programs, where panic in `pre_exec_hook` is already memory-safe, prevent the double-unwinding malfunction rust-lang#79740. I think we want to make panic after fork safe even though the post-fork child environment is only experienced by users of `unsafe`, beause the subset of Rust in which any panic is UB is really far too hazardous and unnatural. #### Approach * Provide a way for a program to, at runtime, switch to having panics abort. This makes it possible to panic without making *any* heap allocations, which is needed because on some platforms malloc is UB in a child forked from a multithreaded program (see rust-lang#80263 (comment), and maybe also the SuS [spec](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html)). * Make that change in the child spawned by `Command`. * Document the rules comprehensively enough that a programmer has a fighting chance of writing correct code. * Test that this all works as expected (and in particular, that there aren't any heap allocations we missed) Fixes rust-lang#79740 #### Rejected (or previously attempted) approaches * Change the panic machinery to be able to unwind without allocating, at least when the payload and message are both `'static`. This seems like it would be even more subtle. Also that is a potentially-hot path which I don't want to mess with. * Change the existing panic hook mechanism to not convert the message to a `String` before calling the hook. This would be a surprising change for existing code and would not be detected by the type system. * Provide a `raw_panic_hook` function to intercept panics in a way that doesn't allocate. (That was an earlier version of this MR.) ### History This MR could be considered a v2 of rust-lang#80263. Thanks to everyone who commented there. In particular, thanks to `@m-ou-se,` `@Mark-Simulacrum` and `@hyd-dev.` (Tagging you since I think you might be interested in this new MR.) Compared to rust-lang#80263, this MR has very substantial changes and additions. Additionally, I have recently (2021-04-20) completely revised this series following very helpful comments from `@m-ou-se.` r? `@m-ou-se`
Do not allocate or unwind after fork ### Objective scenarios * Make (simple) panics safe in `Command::pre_exec_hook`, including most `panic!` calls, `Option::unwrap`, and array bounds check failures. * Make it possible to `libc::fork` and then safely panic in the child (needed for the above, but this requirement means exposing the new raw hook API which the `Command` implementation needs). * In singlethreaded programs, where panic in `pre_exec_hook` is already memory-safe, prevent the double-unwinding malfunction rust-lang#79740. I think we want to make panic after fork safe even though the post-fork child environment is only experienced by users of `unsafe`, beause the subset of Rust in which any panic is UB is really far too hazardous and unnatural. #### Approach * Provide a way for a program to, at runtime, switch to having panics abort. This makes it possible to panic without making *any* heap allocations, which is needed because on some platforms malloc is UB in a child forked from a multithreaded program (see rust-lang#80263 (comment), and maybe also the SuS [spec](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html)). * Make that change in the child spawned by `Command`. * Document the rules comprehensively enough that a programmer has a fighting chance of writing correct code. * Test that this all works as expected (and in particular, that there aren't any heap allocations we missed) Fixes rust-lang#79740 #### Rejected (or previously attempted) approaches * Change the panic machinery to be able to unwind without allocating, at least when the payload and message are both `'static`. This seems like it would be even more subtle. Also that is a potentially-hot path which I don't want to mess with. * Change the existing panic hook mechanism to not convert the message to a `String` before calling the hook. This would be a surprising change for existing code and would not be detected by the type system. * Provide a `raw_panic_hook` function to intercept panics in a way that doesn't allocate. (That was an earlier version of this MR.) ### History This MR could be considered a v2 of rust-lang#80263. Thanks to everyone who commented there. In particular, thanks to `@m-ou-se,` `@Mark-Simulacrum` and `@hyd-dev.` (Tagging you since I think you might be interested in this new MR.) Compared to rust-lang#80263, this MR has very substantial changes and additions. Additionally, I have recently (2021-04-20) completely revised this series following very helpful comments from `@m-ou-se.` r? `@m-ou-se`
Unwinding past fork() in the child causes whatever traps the unwind
to return twice. This is very strange and clearly not desirable.
With the default behaviour of the thread library, this can even result
in a panic in the child being transformed into zero exit status (ie,
success) as seen in the parent!
If unwinding reaches the fork point, the child should abort.