From ac97b9fcd53b4713d0c9da34ee4dcba16fd1c457 Mon Sep 17 00:00:00 2001 From: Peter Zhang Date: Sat, 25 Feb 2023 01:06:39 +0000 Subject: [PATCH] ptrace: implement getsyscallinfo --- CHANGELOG.md | 2 + src/sys/ptrace/linux.rs | 95 +++++++++++++++++++++++++++++++++++++++++ test/sys/test_ptrace.rs | 93 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index daa8b6d940..c3d282b0e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Added `mq_timedreceive` to `::nix::mqueue`. ([#1966])(https://github.com/nix-rust/nix/pull/1966) - Added `LocalPeerPid` to `nix::sys::socket::sockopt` for macOS. ([#1967](https://github.com/nix-rust/nix/pull/1967)) +- Added `getsyscallinfo` to `nix::sys::ptrace` for Linux. + ([#2006](https://github.com/nix-rust/nix/pull/2006)) ### Changed diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs index 9687e05d42..3267602d4d 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -22,6 +22,9 @@ pub type AddressType = *mut ::libc::c_void; ))] use libc::user_regs_struct; +#[cfg(all(target_os = "linux", target_env = "gnu"))] +use libc::ptrace_syscall_info; + cfg_if! { if #[cfg(any(all(target_os = "linux", target_arch = "s390x"), all(target_os = "linux", target_env = "gnu"), @@ -121,6 +124,8 @@ libc_enum! { #[cfg(all(target_os = "linux", target_env = "gnu", any(target_arch = "x86", target_arch = "x86_64")))] PTRACE_SYSEMU_SINGLESTEP, + #[cfg(all(target_os = "linux", target_env = "gnu"))] + PTRACE_GET_SYSCALL_INFO, } } @@ -152,6 +157,80 @@ libc_enum! { } } +#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[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(all(target_os = "linux", target_env = "gnu"))] +#[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: u64, + /// 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: u64, + /// System call arguments. + args: [u64; 6], + /// SECCOMP_RET_DATA portion of SECCOMP_RET_TRACE return value. + ret_data: u32, + }, +} + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +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::EINVAL), + }?; + + 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 +371,22 @@ 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+ +#[cfg(all(target_os = "linux", target_env = "gnu"))] +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..a942bb0cb3 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -273,3 +273,96 @@ fn test_ptrace_syscall() { } } } + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[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(); + unsafe { + // make a test syscall that can be intercepted by the tracer + ::libc::syscall( + ::libc::SYS_kill, + pid.as_raw(), + ::libc::SIGKILL, + ); + ::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 + | ptrace::Options::PTRACE_O_EXITKILL, + ) + .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, + args: [pid, sig, ..] + } if nr == ::libc::SYS_kill as _ && 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)) + ); + } + } +}