Skip to content
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

Support setting signal masks for child processes on Unix #100737

Closed
wants to merge 2 commits into from

Conversation

sunshowers
Copy link
Contributor

@sunshowers sunshowers commented Aug 19, 2022

This commit implements basic support for setting signal masks during process spawning.

  • With the posix_spawn code path, this means setting posix_spawnattr_setsigmask.
  • With the fork/exec path, this means calling pthread_sigmask in the child before executing it.

This PR retains the default behavior (empty signal mask). I've also put up #101077 as a dependent PR to change the default behavior.

API change proposal:

Unresolved questions:

  • Is the possible UB with masking SIGSEGV etc (documented in the POSIX spec) enough reason to mark this unsafe?

@rustbot rustbot added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Aug 19, 2022
@rust-highfive
Copy link
Collaborator

r? @joshtriplett

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 19, 2022
@sunshowers
Copy link
Contributor Author

Ah I'm not sure if there should be a reviewer at the moment, this is meant to be a draft PR :)

@jyn514 jyn514 added S-experimental Status: Ongoing experiment that does not require reviewing and won't be merged in its current state. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Aug 19, 2022
@jyn514
Copy link
Member

jyn514 commented Aug 19, 2022

@sunshowers I've unassigned a reviewer for now, but please do run r? rust-lang/libs-api once this is ready for review so it doesn't get lost :)

@@ -128,6 +169,47 @@ pub enum Stdio {
Fd(FileDesc),
}

pub struct SignalSet {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be private?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It follows the pattern of the other items in the module which are all declared pub (though they actually resolve to pub(crate)).

@sunshowers
Copy link
Contributor Author

Updates:

  • Addressed review feedback from @joshtriplett
  • Added documentation and examples

I think this is ready for review now.

@sunshowers sunshowers marked this pull request as ready for review August 19, 2022 18:58
@rustbot
Copy link
Collaborator

rustbot commented Aug 19, 2022

Hey! It looks like you've submitted a new PR for the library teams!

If this PR contains changes to any rust-lang/rust public library APIs then please comment with @rustbot label +T-libs-api -T-libs to tag it appropriately. If this PR contains changes to any unstable APIs please edit the PR description to add a link to the relevant API Change Proposal or create one if you haven't already. If you're unsure where your change falls no worries, just leave it as is and the reviewer will take a look and make a decision to forward on if necessary.

Examples of T-libs-api changes:

  • Stabilizing library features
  • Introducing insta-stable changes such as new implementations of existing stable traits on existing stable types
  • Introducing new or changing existing unstable library APIs (excluding permanently unstable features / features without a tracking issue)
  • Changing public documentation in ways that create new stability guarantees
  • Changing observable runtime behavior of library APIs

@sunshowers
Copy link
Contributor Author

r? rust-lang/libs-api

@sunshowers
Copy link
Contributor Author

sunshowers commented Aug 19, 2022

(I still need to write up the change proposal I guess!)

edit: done: rust-lang/libs-team#88

@sunshowers sunshowers changed the title [WIP] implement signal mask support Support setting signal masks for child processes on Unix Aug 19, 2022
@thomcc
Copy link
Member

thomcc commented Aug 19, 2022

Is the possible UB with masking SIGSEGV etc (documented in the POSIX spec) enough reason to mark this unsafe?

I guess the problem would be that it changes the documented contract of the pre_exec hook. I don't think there's a way to raise SIGBUS, SIGFPE, or SIGSEGV from safe code (well, a way that would violate the UB), but SIGILL is also listed as one of the signals that is UB to trigger when it's blocked, and we commonly use that for an "abort immediately" function (e.g. core::intrinsics::abort()). (Often this is used for "we need to abort and are inside libcore or liballoc rather than libstd")

That said, pre_exec is already unsafe and you have to be careful anyway, so I'm not 100% sure the extent to which this matters. The easy way of triggering sigill from safe code is a double panic, but inside pre_exec, the first panic will lead to sys::abort_internal(), e.g. process::abort() (and not intrinsics::abort()), which shouldn't raise SIGILL. (I guess a small caveat here is the panic_immediate_abort feature, which does use intrinsics::abort()).

I'm not sure if this is worth marking as unsafe, it seems unfortunate if so, but it's probably worth considering carefully if we aren't going to, to avoid a footgun. That said, there's probably enough time to get this right prior to stabilizing, so I don't know that this is a blocker.

@sunshowers
Copy link
Contributor Author

sunshowers commented Aug 19, 2022

That said, pre_exec is already unsafe and you have to be careful anyway, so I'm not 100% sure the extent to which this matters.

Yeah, that makes sense. Note that existing code won't be affected though -- only users of both signal_mask and pre_exec are potentially affected. So at least in principle, this could be documented as an additional constraint on pre_exec, and signal_mask can be left safe.

@thomcc
Copy link
Member

thomcc commented Aug 19, 2022

Yeah, so I suppose we could adjust the safety contract of pre_exec at the same time. I'm not sure if this is something we generally want to do, but pre_exec is rare and highly dodgy already, and it's pretty unlikely anybody is triggering sigill inside their pre_exec, so maybe it's fine.

@sunshowers sunshowers closed this Aug 19, 2022
@sunshowers sunshowers reopened this Aug 19, 2022
@sunshowers
Copy link
Contributor Author

(whoops, closed by accident)

The other issue with safety I was thinking of is: what happens if the child itself has a segfault unrelated to the parent, and the parent has masked SIGSEGV? This isn't UB within Rust, but it is UB across the system. I don't know if unsafe extends to that kind of UB.

@thomcc
Copy link
Member

thomcc commented Aug 19, 2022

I don't know if unsafe extends to that kind of UB.

In general it doesn't. It's totally safe to spawn processes that crash or do other UB (std::process::Command::spawn is safe after all). We have no way of preventing it, it's kind of outside Rust's ability to defend against in general.

That said, fork/exec stuff is ofc where the line gets blurry here (for example, technically pre_exec is run inside the child process, but it's still unsafe).

We're going to need this for upcoming signal mask support.
@sunshowers
Copy link
Contributor Author

I've put up #101077 as a dependent PR on top of this one.

This commit implements basic support for setting signal masks during process spawning.

* With the `posix_spawn` code path, this means setting `posix_spawnattr_setsigmask`.
* With the `fork/exec` path, this means calling `pthread_sigmask` in the child before executing it.

This also fixes a bug: previously, we were always setting the signal mask to
the empty set, breaking tools like `nohup`. With this change, we inherit
the signal mask from the parent by default.
Copy link
Member

@thomcc thomcc left a comment

Choose a reason for hiding this comment

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

The impl looks fine although I'll probably want to review again after the situation with the other PR is resolved.

@@ -296,6 +396,10 @@ impl Command {
pub fn get_pgroup(&self) -> Option<pid_t> {
self.pgroup
}
#[allow(dead_code)]
pub fn get_signal_mask(&self) -> io::Result<&SignalSet> {
self.signal_mask.get_or_try_init(SignalSet::empty)
Copy link
Member

Choose a reason for hiding this comment

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

We'll want to remove the OnceLock from this if the other PR's FCP doesn't get accepted.

@thomcc
Copy link
Member

thomcc commented Aug 28, 2022

I'm going to mark this as blocked on the other PR, as it's unlikely that we want this impl without that (and the resolution there may change how we approach this API).

@thomcc thomcc added the S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work. label Aug 28, 2022
@bors
Copy link
Contributor

bors commented Aug 29, 2022

☔ The latest upstream changes (presumably #100786) made this pull request unmergeable. Please resolve the merge conflicts.

/// success and has no effect.
///
/// Blocking some signals like `SIGSEGV` can lead to undefined behavior in
/// the child. See the [`pthread_sigmask`] man page for more information.
Copy link
Contributor

Choose a reason for hiding this comment

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

sigprocmask is probably the more canonical reference.

Also, while the manpage may say that the effect of blocking synchronous traps (ie, SEGV/BUS/FPE/ILL raised while executing an instruction) is undefined, I wouldn't read that as being the same as UB at the Rust/llvm level.

In practice I think it would either deliver the signal as SIG_DFL (ie, Linux), or endlessly re-trap on the same instruction (what x86 macOS seems to do) - either way the program has effectively terminated and won't enter any UB states as far as the program semantics are concerned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Heh it's actually the same man page for both pthread_sigmask and sigprocmask. I can change the reference in the docs to be sigprocmask though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Huh - on Fedora they're separate, but pthread_sigmask has almost no content and refers to sigprocmask for discussion of the detailed semantics.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I was referring to the man pages from the official POSIX spec here, as linked in the comment)

workingjubilee pushed a commit to tcdi/postgrestd that referenced this pull request Sep 15, 2022
Align android `sigaddset` impl with the reference impl from Bionic

In rust-lang/rust#100737 I noticed we were treating the sigset_t as an array of bytes, while referencing code from android (https://github.com/aosp-mirror/platform_bionic/blob/ad8dcd6023294b646e5a8288c0ed431b0845da49/libc/include/android/legacy_signal_inlines.h) which treats it as an array of unsigned long.

That said, the behavior difference is so subtle here that it's not hard to see why nobody noticed. This fixes the implementation to be equivalent to the one in bionic.
@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 8, 2022
@Dylan-DPC Dylan-DPC removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 9, 2022
@thomcc
Copy link
Member

thomcc commented Nov 10, 2022

I think this is no longer blocked.

@thomcc thomcc added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work. labels Nov 10, 2022
@sunshowers
Copy link
Contributor Author

sunshowers commented Nov 10, 2022

Yep, planning to get to it soon. Meanwhile I've worked around it on beta Rust (1.66) by calling pthread_sigmask before and after process spawning, which appears to work.

@thomcc
Copy link
Member

thomcc commented Nov 10, 2022

No rush, just got a ping on it from someone.

@JohnCSimon
Copy link
Member

@sunshowers
ping from triage - can you post your status on this PR? This has sat unmoved for a while now. Thanks!

FYI: when a PR is ready for review, send a message containing
@rustbot ready to switch to S-waiting-on-review so the PR is in the reviewer's backlog.

@Dylan-DPC
Copy link
Member

Closing this due to inactivity. Feel free to make a new pr if you wish to continue this.

@Dylan-DPC Dylan-DPC closed this Feb 23, 2023
@Dylan-DPC Dylan-DPC added S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 23, 2023
@sunshowers
Copy link
Contributor Author

sunshowers commented Feb 26, 2023

Apologies, I started a new full-time job since I originally wrote this and haven't had the time to follow up on it. After #101077, I also figured out a way to do this without needing to add new API surface to std:

  1. Temporarily use pthread_sigmask to set a new sigmask.
  2. Spawn the process.
  3. Unset the sigmask.

(and use a scope guard to ensure that the sigmask is unset in case of panic.)

This means that I'm probably not going to pursue this further. However, someone else is welcome to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.