Skip to content

Commit

Permalink
ptrace: implement getsyscallinfo
Browse files Browse the repository at this point in the history
  • Loading branch information
mbyzhang committed Mar 23, 2023
1 parent b2318f9 commit 3d0d383
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -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 }
Expand Down
97 changes: 97 additions & 0 deletions src/sys/ptrace/linux.rs
Expand Up @@ -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"),
Expand Down Expand Up @@ -121,6 +124,10 @@ 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,
PTRACE_GETSIGMASK,
PTRACE_SETSIGMASK,
}
}

Expand Down Expand Up @@ -152,6 +159,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: 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,
},
}

#[cfg(all(target_os = "linux", target_env = "gnu"))]
impl SyscallInfo {
pub fn from_raw(raw: ptrace_syscall_info) -> Result<SyscallInfo> {
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.
Expand Down Expand Up @@ -292,6 +373,22 @@ pub fn getsiginfo(pid: Pid) -> Result<siginfo_t> {
ptrace_get_data::<siginfo_t>(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<SyscallInfo> {
let mut data = mem::MaybeUninit::uninit();
unsafe {
ptrace_other(
Request::PTRACE_GET_SYSCALL_INFO,
pid,
mem::size_of::<ptrace_syscall_info>() 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 {
Expand Down
84 changes: 84 additions & 0 deletions test/sys/test_ptrace.rs
Expand Up @@ -273,3 +273,87 @@ 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();
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,
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))
);
}
}
}

0 comments on commit 3d0d383

Please sign in to comment.