Skip to content

Commit 12efa53

Browse files
committedNov 16, 2023
if available use a Child's pidfd for kill/wait
1 parent 3ffbb48 commit 12efa53

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed
 

‎library/std/src/os/linux/process.rs

+6
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ pub trait CommandExt: Sealed {
152152
/// in a guaranteed race-free manner (e.g. if the `clone3` system call
153153
/// is supported). Otherwise, [`pidfd`] will return an error.
154154
///
155+
/// If a pidfd has been successfully created and not been taken from the `Child`
156+
/// then calls to `kill()`, `wait()` and `try_wait()` will use the pidfd
157+
/// instead of the pid. This can prevent pid recycling races, e.g.
158+
/// those caused by rogue libraries in the same process prematurely reaping
159+
/// zombie children via `waitpid(-1, ...)` calls.
160+
///
155161
/// [`Command`]: process::Command
156162
/// [`Child`]: process::Child
157163
/// [`pidfd`]: fn@ChildExt::pidfd

‎library/std/src/sys/unix/process/process_unix.rs

+62-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use core::ffi::NonZero_c_int;
99

1010
#[cfg(target_os = "linux")]
1111
use crate::os::linux::process::PidFd;
12+
#[cfg(target_os = "linux")]
13+
use crate::os::unix::io::AsRawFd;
1214

1315
#[cfg(any(
1416
target_os = "macos",
@@ -816,17 +818,41 @@ impl Process {
816818
// and used for another process, and we probably shouldn't be killing
817819
// random processes, so return Ok because the process has exited already.
818820
if self.status.is_some() {
819-
Ok(())
820-
} else {
821-
cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
821+
return Ok(());
822+
}
823+
#[cfg(target_os = "linux")]
824+
if let Some(pid_fd) = self.pidfd.as_ref() {
825+
// pidfd_send_signal predates pidfd_open. so if we were able to get an fd then sending signals will work too
826+
return cvt(unsafe {
827+
libc::syscall(
828+
libc::SYS_pidfd_send_signal,
829+
pid_fd.as_raw_fd(),
830+
libc::SIGKILL,
831+
crate::ptr::null::<()>(),
832+
0,
833+
)
834+
})
835+
.map(drop);
822836
}
837+
cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
823838
}
824839

825840
pub fn wait(&mut self) -> io::Result<ExitStatus> {
826841
use crate::sys::cvt_r;
827842
if let Some(status) = self.status {
828843
return Ok(status);
829844
}
845+
#[cfg(target_os = "linux")]
846+
if let Some(pid_fd) = self.pidfd.as_ref() {
847+
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
848+
849+
cvt_r(|| unsafe {
850+
libc::waitid(libc::P_PIDFD, pid_fd.as_raw_fd() as u32, &mut siginfo, libc::WEXITED)
851+
})?;
852+
let status = ExitStatus::from_waitid_siginfo(siginfo);
853+
self.status = Some(status);
854+
return Ok(status);
855+
}
830856
let mut status = 0 as c_int;
831857
cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?;
832858
self.status = Some(ExitStatus::new(status));
@@ -837,6 +863,25 @@ impl Process {
837863
if let Some(status) = self.status {
838864
return Ok(Some(status));
839865
}
866+
#[cfg(target_os = "linux")]
867+
if let Some(pid_fd) = self.pidfd.as_ref() {
868+
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
869+
870+
cvt(unsafe {
871+
libc::waitid(
872+
libc::P_PIDFD,
873+
pid_fd.as_raw_fd() as u32,
874+
&mut siginfo,
875+
libc::WEXITED | libc::WNOHANG,
876+
)
877+
})?;
878+
if unsafe { siginfo.si_pid() } == 0 {
879+
return Ok(None);
880+
}
881+
let status = ExitStatus::from_waitid_siginfo(siginfo);
882+
self.status = Some(status);
883+
return Ok(Some(status));
884+
}
840885
let mut status = 0 as c_int;
841886
let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?;
842887
if pid == 0 {
@@ -866,6 +911,20 @@ impl ExitStatus {
866911
ExitStatus(status)
867912
}
868913

914+
#[cfg(target_os = "linux")]
915+
pub fn from_waitid_siginfo(siginfo: libc::siginfo_t) -> ExitStatus {
916+
let status = unsafe { siginfo.si_status() };
917+
918+
match siginfo.si_code {
919+
libc::CLD_EXITED => ExitStatus((status & 0xff) << 8),
920+
libc::CLD_KILLED => ExitStatus(status),
921+
libc::CLD_DUMPED => ExitStatus(status | 0x80),
922+
libc::CLD_CONTINUED => ExitStatus(0xffff),
923+
libc::CLD_STOPPED | libc::CLD_TRAPPED => ExitStatus(((status & 0xff) << 8) | 0x7f),
924+
_ => unreachable!("waitid() should only return the above codes"),
925+
}
926+
}
927+
869928
fn exited(&self) -> bool {
870929
libc::WIFEXITED(self.0)
871930
}

‎library/std/src/sys/unix/process/process_unix/tests.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ fn test_command_fork_no_unwind() {
6464
#[test]
6565
#[cfg(target_os = "linux")]
6666
fn test_command_pidfd() {
67+
use crate::assert_matches::assert_matches;
6768
use crate::os::fd::{AsRawFd, RawFd};
6869
use crate::os::linux::process::{ChildExt, CommandExt};
6970
use crate::process::Command;
@@ -78,7 +79,7 @@ fn test_command_pidfd() {
7879
};
7980

8081
// always exercise creation attempts
81-
let child = Command::new("echo").create_pidfd(true).spawn().unwrap();
82+
let mut child = Command::new("false").create_pidfd(true).spawn().unwrap();
8283

8384
// but only check if we know that the kernel supports pidfds
8485
if pidfd_open_available {
@@ -88,4 +89,12 @@ fn test_command_pidfd() {
8889
let flags = super::cvt(unsafe { libc::fcntl(pidfd.as_raw_fd(), libc::F_GETFD) }).unwrap();
8990
assert!(flags & libc::FD_CLOEXEC != 0);
9091
}
92+
let status = child.wait().expect("error waiting on pidfd");
93+
assert_eq!(status.code(), Some(1));
94+
95+
let mut child = Command::new("sleep").arg("1000").create_pidfd(true).spawn().unwrap();
96+
assert_matches!(child.try_wait(), Ok(None));
97+
child.kill().expect("failed to kill child");
98+
let status = child.wait().expect("error waiting on pidfd");
99+
assert_eq!(status.signal(), Some(libc::SIGKILL));
91100
}

0 commit comments

Comments
 (0)