Skip to content

std::process:Command::spawn panics when pre_exec hook fails if SIGCHLD is ignored #110317

Open
@kennylevinsen

Description

@kennylevinsen

On Linux when a CommandExt::pre_exec hook is in use, std::process::Command::spawn forks, calls do_exec and then runs each of the hooks in order. If do_exec fails (if a pre_exec hook or exec itself fails for example), the Err bubbles up and the child process writes the errno to the pipe of the parent before exiting.

After the parent reads the error, it calls Command::wait which internally calls waitpid(2). When a SIGCHLD handler is installed - in this case, SIG_IGN - there is no child to wait for, so waitpid(2) returns ECHILD. This leads to spawn panicking:

thread 'main' panicked at 'wait() should either return Ok or panic', library/std/src/sys/unix/process/process_unix.rs:129:21

I expected to be able to return an error from pre_exec hooks without causing a panic, even if SIGCHLD is handled.

The pre_exec hook is a prerequisite in order to disable the posix_spawn path. Note that there are other ways to disable posix_spawn, which would reproduce the issue as well.

Reproduction

The issue can be reproduced as follows:

// Built with libc 0.2.141
fn main() {
    use std::os::unix::process::CommandExt;
    let mut cmd = std::process::Command::new("hopefully invalid path");

    unsafe {
        libc::signal(libc::SIGCHLD, libc::SIG_IGN);
        cmd.pre_exec(|| Ok(()));
    }

    cmd.spawn().unwrap();
}

Which results in

thread 'main' panicked at 'wait() should either return Ok or panic', library/std/src/sys/unix/process/process_unix.rs:129:21
stack backtrace:
   0: rust_begin_unwind
             at /rustc/5e1d3299a290026b85787bc9c7e72bcc53ac283f/library/std/src/panicking.rs:577:5
   1: core::panicking::panic_fmt
             at /rustc/5e1d3299a290026b85787bc9c7e72bcc53ac283f/library/core/src/panicking.rs:67:14
   2: std::sys::unix::process::process_inner::<impl std::sys::unix::process::process_common::Command>::spawn
   3: std::process::Command::spawn
             at /rustc/5e1d3299a290026b85787bc9c7e72bcc53ac283f/library/std/src/process.rs:893:9
   4: rust_test::main
             at ./src/main.rs:10:5
   5: core::ops::function::FnOnce::call_once
             at /rustc/5e1d3299a290026b85787bc9c7e72bcc53ac283f/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

The waitpid behavior can also be observed in plain C with:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

int main() {
	signal(SIGCHLD, SIG_IGN);
	pid_t pid = fork();
	if (pid == -1) {
		fprintf(stderr, "fork failed: %d, %s\n", errno, strerror(errno));
		return 1;
	}

	if (pid == 0) {
		fprintf(stderr, "child!\n");
		return 0;
	}

	int status = 0;
	int res = waitpid(pid, &status, 0);
	fprintf(stderr, "res: %d, status: %d, errno: %d, %s\n", res, status, errno, strerror(errno));
	return 0;
}

Which outputs:

child!
res: -1, errno: 10, No child processes⏎

If the signal handler is commented out, waitpid works as the Command::spawn expects.

Meta

  • rustc --version --verbose:
rustc 1.70.0-nightly (5e1d3299a 2023-03-31)
binary: rustc
commit-hash: 5e1d3299a290026b85787bc9c7e72bcc53ac283f
commit-date: 2023-03-31
host: x86_64-unknown-linux-gnu
release: 1.70.0-nightly
LLVM version: 16.0.0
  • Kernel: Linux 6.2.10-arch1-1
  • glibc: 2.37-2

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ioArea: `std::io`, `std::fs`, `std::net` and `std::path`A-processArea: `std::process` and `std::env`C-bugCategory: This is a bug.O-unixOperating system: Unix-likeT-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions