From 903a52f602054dff1258b5ca04591f0c5edbe08b Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Tue, 28 Mar 2017 16:55:36 -0400 Subject: [PATCH] Add WaitStatus::PtraceSyscall for use with PTRACE_O_TRACESYSGOOD The recommended way to trace syscalls with ptrace is to set the PTRACE_O_TRACESYSGOOD option, to distinguish syscall stops from receiving an actual SIGTRAP. In C, this would cause WSTOPSIG to return SIGTRAP | 0x80, but nix wants to parse that as an actual signal. Add another wait status type for syscall stops (in the language of the ptrace(2) manpage, "PTRACE_EVENT stops" and "Syscall-stops" are different things), and mask out bit 0x80 from signals before trying to parse it. Closes #550 --- CHANGELOG.md | 3 +++ src/sys/wait.rs | 19 ++++++++++++++-- test/sys/test_wait.rs | 50 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 817a89f45e..b7edf991c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added `cfmakeraw`, `cfsetspeed`, and `tcgetsid`. ([#527](https://github.com/nix-rust/nix/pull/527)) - Added "bad none", "bad write_ptr", "bad write_int", and "bad readwrite" variants to the `ioctl!` macro. ([#670](https://github.com/nix-rust/nix/pull/670)) +- On Linux and Android, added support for receiving `PTRACE_O_TRACESYSGOOD` + events from `wait` and `waitpid` using `WaitStatus::PtraceSyscall` + ([#566](https://github.com/nix-rust/nix/pull/566)). ### Changed - Changed `ioctl!(write ...)` into `ioctl!(write_ptr ...)` and `ioctl!(write_int ..)` variants diff --git a/src/sys/wait.rs b/src/sys/wait.rs index ee0beade24..7033422da2 100644 --- a/src/sys/wait.rs +++ b/src/sys/wait.rs @@ -47,6 +47,8 @@ pub enum WaitStatus { Stopped(Pid, Signal), #[cfg(any(target_os = "linux", target_os = "android"))] PtraceEvent(Pid, Signal, c_int), + #[cfg(any(target_os = "linux", target_os = "android"))] + PtraceSyscall(Pid), Continued(Pid), StillAlive } @@ -56,6 +58,7 @@ pub enum WaitStatus { mod status { use sys::signal::Signal; use libc::c_int; + use libc::SIGTRAP; pub fn exited(status: i32) -> bool { (status & 0x7F) == 0 @@ -82,7 +85,17 @@ mod status { } pub fn stop_signal(status: i32) -> Signal { - Signal::from_c_int((status & 0xFF00) >> 8).unwrap() + // Keep only 7 bits of the signal: the high bit + // is used to indicate syscall stops, below. + Signal::from_c_int((status & 0x7F00) >> 8).unwrap() + } + + pub fn syscall_stop(status: i32) -> bool { + // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect + // of delivering SIGTRAP | 0x80 as the signal number for syscall + // stops. This allows easily distinguishing syscall stops from + // genuine SIGTRAP signals. + ((status & 0xFF00) >> 8) == SIGTRAP | 0x80 } pub fn stop_additional(status: i32) -> c_int { @@ -196,7 +209,9 @@ fn decode(pid : Pid, status: i32) -> WaitStatus { if #[cfg(any(target_os = "linux", target_os = "android"))] { fn decode_stopped(pid: Pid, status: i32) -> WaitStatus { let status_additional = status::stop_additional(status); - if status_additional == 0 { + if status::syscall_stop(status) { + WaitStatus::PtraceSyscall(pid) + } else if status_additional == 0 { WaitStatus::Stopped(pid, status::stop_signal(status)) } else { WaitStatus::PtraceEvent(pid, status::stop_signal(status), status::stop_additional(status)) diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index 2e28d9e78f..5f6c923184 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -34,3 +34,53 @@ fn test_wait_exit() { Err(_) => panic!("Error: Fork Failed") } } + +#[cfg(any(target_os = "linux", target_os = "android"))] +// FIXME: qemu-user doesn't implement ptrace on most arches +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod ptrace { + use nix::sys::ptrace::*; + use nix::sys::ptrace::ptrace::*; + use nix::sys::signal::*; + use nix::sys::wait::*; + use nix::unistd::*; + use nix::unistd::ForkResult::*; + use std::{ptr, process}; + + fn ptrace_child() -> ! { + let _ = ptrace(PTRACE_TRACEME, Pid::from_raw(0), ptr::null_mut(), ptr::null_mut()); + // As recommended by ptrace(2), raise SIGTRAP to pause the child + // until the parent is ready to continue + let _ = raise(SIGTRAP); + process::exit(0) + } + + fn ptrace_parent(child: Pid) { + // Wait for the raised SIGTRAP + assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP))); + // We want to test a syscall stop and a PTRACE_EVENT stop + assert!(ptrace_setoptions(child, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXIT).is_ok()); + + // First, stop on the next system call, which will be exit() + assert!(ptrace(PTRACE_SYSCALL, child, ptr::null_mut(), ptr::null_mut()).is_ok()); + assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child))); + // Then get the ptrace event for the process exiting + assert!(ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok()); + assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, SIGTRAP, PTRACE_EVENT_EXIT))); + // Finally get the normal wait() result, now that the process has exited + assert!(ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok()); + assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0))); + } + + #[test] + fn test_wait_ptrace() { + #[allow(unused_variables)] + let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + + match fork() { + Ok(Child) => ptrace_child(), + Ok(Parent { child }) => ptrace_parent(child), + Err(_) => panic!("Error: Fork Failed") + } + } +}