diff --git a/Cargo.toml b/Cargo.toml index 960d4f61b3..e9a417b983 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ targets = [ ] [dependencies] -libc = { git = "https://github.com/rust-lang/libc", rev = "44cc30c6b68427d3628926868758d35fe561bbe6", features = [ "extra_traits" ] } +libc = { git = "https://github.com/rust-lang/libc", rev = "d3cb0e7f081f6746cf9897c9bee15c3a22e986ed", features = [ "extra_traits" ] } bitflags = "1.1" cfg-if = "1.0" pin-utils = { version = "0.1.0", optional = true } diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs index 9687e05d42..e8d0a037bb 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -5,7 +5,7 @@ use crate::sys::signal::Signal; use crate::unistd::Pid; use crate::Result; use cfg_if::cfg_if; -use libc::{self, c_long, c_void, siginfo_t}; +use libc::{self, c_long, c_void, ptrace_syscall_info, siginfo_t}; use std::{mem, ptr}; pub type AddressType = *mut ::libc::c_void; @@ -121,6 +121,7 @@ libc_enum! { #[cfg(all(target_os = "linux", target_env = "gnu", any(target_arch = "x86", target_arch = "x86_64")))] PTRACE_SYSEMU_SINGLESTEP, + PTRACE_GET_SYSCALL_INFO, } } @@ -152,6 +153,77 @@ libc_enum! { } } +#[cfg_attr(docsrs, doc(cfg(all())))] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SyscallInfo { + /// Type of system call stop + pub op: SyscallInfoOp, + /// AUDIT_ARCH_* value; see seccomp(2) + pub arch: u32, + /// CPU instruction pointer + pub instruction_pointer: u64, + /// CPU stack pointer + pub stack_pointer: u64, +} + +#[cfg_attr(docsrs, doc(cfg(all())))] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum SyscallInfoOp { + None, + /// System call entry. + Entry { + /// System call number. + nr: i64, + /// System call arguments. + args: [u64; 6], + }, + /// System call exit. + Exit { + /// System call return value. + ret_val: i64, + /// System call error flag. + is_error: u8, + }, + /// PTRACE_EVENT_SECCOMP stop. + Seccomp { + /// System call number. + nr: i64, + /// System call arguments. + args: [u64; 6], + /// SECCOMP_RET_DATA portion of SECCOMP_RET_TRACE return value. + ret_data: u32, + }, +} + +impl SyscallInfo { + pub fn from_raw(raw: ptrace_syscall_info) -> Result { + let op = match raw.op { + libc::PTRACE_SYSCALL_INFO_NONE => Ok(SyscallInfoOp::None), + libc::PTRACE_SYSCALL_INFO_ENTRY => Ok(SyscallInfoOp::Entry { + nr: unsafe { raw.u.entry.nr as _ }, + args: unsafe { raw.u.entry.args }, + }), + libc::PTRACE_SYSCALL_INFO_EXIT => Ok(SyscallInfoOp::Exit { + ret_val: unsafe { raw.u.exit.sval }, + is_error: unsafe { raw.u.exit.is_error }, + }), + libc::PTRACE_SYSCALL_INFO_SECCOMP => Ok(SyscallInfoOp::Seccomp { + nr: unsafe { raw.u.seccomp.nr as _ }, + args: unsafe { raw.u.seccomp.args }, + ret_data: unsafe { raw.u.seccomp.ret_data }, + }), + _ => Err(Errno::ENOSYS), + }?; + + Ok(SyscallInfo { + op, + arch: raw.arch, + instruction_pointer: raw.instruction_pointer, + stack_pointer: raw.stack_pointer, + }) + } +} + libc_bitflags! { /// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request. /// See `man ptrace` for more details. @@ -292,6 +364,21 @@ pub fn getsiginfo(pid: Pid) -> Result { ptrace_get_data::(Request::PTRACE_GETSIGINFO, pid) } +/// Get ptrace syscall info as with `ptrace(PTRACE_GET_SYSCALL_INFO,...)` +/// Only available on Linux 5.3+ +pub fn getsyscallinfo(pid: Pid) -> Result { + let mut data = mem::MaybeUninit::uninit(); + unsafe { + ptrace_other( + Request::PTRACE_GET_SYSCALL_INFO, + pid, + mem::size_of::() as *mut c_void, + data.as_mut_ptr() as *mut _ as *mut c_void, + )?; + } + SyscallInfo::from_raw(unsafe { data.assume_init() }) +} + /// Set siginfo as with `ptrace(PTRACE_SETSIGINFO,...)` pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> { let ret = unsafe { diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index 530560fe17..e8a53df406 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -273,3 +273,87 @@ fn test_ptrace_syscall() { } } } + +#[cfg(target_os = "linux")] +#[test] +fn test_ptrace_getsyscallinfo() { + use nix::sys::ptrace; + use nix::sys::ptrace::SyscallInfoOp; + use nix::sys::signal::kill; + use nix::sys::signal::Signal; + use nix::sys::wait::{waitpid, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::getpid; + use nix::unistd::ForkResult::*; + + require_capability!("test_ptrace_getsyscallinfo", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + ptrace::traceme().unwrap(); + // first sigstop until parent is ready to continue + let pid = getpid(); + kill(pid, Signal::SIGSTOP).unwrap(); + kill(pid, Signal::SIGTERM).unwrap(); + unsafe { + ::libc::_exit(0); + } + } + + Parent { child } => { + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, Signal::SIGSTOP)) + ); + + // set this option to recognize syscall-stops + ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD) + .unwrap(); + + // kill entry + ptrace::syscall(child, None).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::PtraceSyscall(child)) + ); + + let syscall_info = ptrace::getsyscallinfo(child); + + if syscall_info == Err(Errno::EIO) { + skip!("PTRACE_GET_SYSCALL_INFO is not supported on this platform. Skipping test."); + } + + assert!(matches!( + syscall_info.unwrap().op, + SyscallInfoOp::Entry { + nr: ::libc::SYS_kill, + args: [pid, sig, ..] + } if pid == child.as_raw() as _ && sig == ::libc::SIGTERM as _ + )); + + // kill exit + ptrace::syscall(child, None).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::PtraceSyscall(child)) + ); + + assert_eq!( + ptrace::getsyscallinfo(child).unwrap().op, + SyscallInfoOp::Exit { + ret_val: 0, + is_error: 0 + } + ); + + // resume child + ptrace::detach(child, None).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false)) + ); + } + } +}