diff --git a/changelog/2739.added.md b/changelog/2739.added.md new file mode 100644 index 0000000000..1afdaa6ec6 --- /dev/null +++ b/changelog/2739.added.md @@ -0,0 +1 @@ +Added set_syscall_info to ptrace on linux diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs index d067ef5789..50e26d2d24 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -148,6 +148,8 @@ libc_enum! { PTRACE_SYSEMU_SINGLESTEP, #[cfg(all(target_os = "linux", target_env = "gnu"))] PTRACE_GET_SYSCALL_INFO, + #[cfg(all(target_os = "linux", target_env = "gnu"))] + PTRACE_SET_SYSCALL_INFO, } } @@ -576,6 +578,21 @@ pub fn syscall_info(pid: Pid) -> Result { ptrace_get_data::(Request::PTRACE_GET_SYSCALL_INFO, pid) } +/// Set the information of the syscall that caused the stop, as with +/// `ptrace(PTRACE_SET_SYSCALL_INFO, ...`. +#[cfg(all(target_os = "linux", target_env = "gnu"))] +pub fn set_syscall_info(pid: Pid, syscall_info: &libc::ptrace_syscall_info) -> Result<()> { + let res = unsafe { + libc::ptrace( + Request::PTRACE_SET_SYSCALL_INFO as RequestType, + libc::pid_t::from(pid), + mem::size_of::(), + syscall_info as *const _ as *const c_void, + ) + }; + Errno::result(res).map(drop) +} + /// Sets the process as traceable, as with `ptrace(PTRACE_TRACEME, ...)` /// /// Indicates that this process is to be traced by its parent. diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index 9f1a3c3bc1..b21e79ff57 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -13,8 +13,6 @@ use nix::unistd::getpid; #[cfg(linux_android)] use std::mem; -use crate::*; - #[test] fn test_ptrace() { // Just make sure ptrace can be called at all, for now. @@ -410,3 +408,73 @@ fn test_ptrace_syscall_info() { }, } } + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[test] +fn test_ptrace_set_syscall_info() { + use nix::sys::mman::{mmap_anonymous, MapFlags, ProtFlags}; + use nix::sys::ptrace; + use nix::sys::signal::{raise, Signal::*}; + use nix::sys::wait::{waitpid, WaitStatus}; + use nix::unistd::{fork, ForkResult::*}; + use std::num::NonZero; + + require_capability!("test_ptrace_set_syscall_info", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + ptrace::traceme().unwrap(); + raise(SIGSTOP).unwrap(); + + unsafe { + let my_memory = mmap_anonymous( + None, + NonZero::new(1).unwrap(), + ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE, + ) + .unwrap_or_else(|_| ::libc::_exit(1)) + .cast::(); + + *my_memory.as_ptr() = 42; + ::libc::_exit(0); + } + } + Parent { child } => { + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, SIGSTOP)) + ); + ptrace::setoptions(child, Options::PTRACE_O_TRACESYSGOOD).unwrap(); + ptrace::syscall(child, None).unwrap(); + + // Hijack the syscall and remove PROT_WRITE to force a SEGFAULT in the child + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::PtraceSyscall(child)) + ); + let mut si = ptrace::syscall_info(child).unwrap(); + assert_eq!(si.op, libc::PTRACE_SYSCALL_INFO_ENTRY); + unsafe { + si.u.entry.args[2] = ProtFlags::PROT_NONE.bits() as u64; + } + let set_syscall_res = ptrace::set_syscall_info(child, &si); + ptrace::cont(child, None).unwrap(); + + if set_syscall_res.is_err() { + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Exited(child, 0)) + ); + crate::skip!("PTRACE_SET_SYSCALL_INFO failed: Linux >= 6.16 is required, skipping test."); + } + + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, SIGSEGV)) + ); + ptrace::detach(child, SIGSEGV).unwrap(); + } + } +}