Skip to content

Commit 2c4e670

Browse files
committed
ptrace: implement getsyscallinfo
1 parent b2318f9 commit 2c4e670

File tree

4 files changed

+175
-2
lines changed

4 files changed

+175
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1616
- Added `mq_timedreceive` to `::nix::mqueue`.
1717
([#1966])(https://github.com/nix-rust/nix/pull/1966)
1818
- Added `LocalPeerPid` to `nix::sys::socket::sockopt` for macOS. ([#1967](https://github.com/nix-rust/nix/pull/1967))
19+
- Added `getsyscallinfo` to `nix::sys::ptrace` for Linux.
20+
([#2006](https://github.com/nix-rust/nix/pull/2006))
1921

2022
### Changed
2123

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ targets = [
2727
]
2828

2929
[dependencies]
30-
libc = { git = "https://github.com/rust-lang/libc", rev = "44cc30c6b68427d3628926868758d35fe561bbe6", features = [ "extra_traits" ] }
30+
libc = { git = "https://github.com/rust-lang/libc", rev = "d3cb0e7f081f6746cf9897c9bee15c3a22e986ed", features = [ "extra_traits" ] }
3131
bitflags = "1.1"
3232
cfg-if = "1.0"
3333
pin-utils = { version = "0.1.0", optional = true }

src/sys/ptrace/linux.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::sys::signal::Signal;
55
use crate::unistd::Pid;
66
use crate::Result;
77
use cfg_if::cfg_if;
8-
use libc::{self, c_long, c_void, siginfo_t};
8+
use libc::{self, c_long, c_void, ptrace_syscall_info, siginfo_t};
99
use std::{mem, ptr};
1010

1111
pub type AddressType = *mut ::libc::c_void;
@@ -121,6 +121,7 @@ libc_enum! {
121121
#[cfg(all(target_os = "linux", target_env = "gnu",
122122
any(target_arch = "x86", target_arch = "x86_64")))]
123123
PTRACE_SYSEMU_SINGLESTEP,
124+
PTRACE_GET_SYSCALL_INFO,
124125
}
125126
}
126127

@@ -152,6 +153,77 @@ libc_enum! {
152153
}
153154
}
154155

156+
#[cfg_attr(docsrs, doc(cfg(all())))]
157+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
158+
pub struct SyscallInfo {
159+
/// Type of system call stop
160+
pub op: SyscallInfoOp,
161+
/// AUDIT_ARCH_* value; see seccomp(2)
162+
pub arch: u32,
163+
/// CPU instruction pointer
164+
pub instruction_pointer: u64,
165+
/// CPU stack pointer
166+
pub stack_pointer: u64,
167+
}
168+
169+
#[cfg_attr(docsrs, doc(cfg(all())))]
170+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
171+
pub enum SyscallInfoOp {
172+
None,
173+
/// System call entry.
174+
Entry {
175+
/// System call number.
176+
nr: i64,
177+
/// System call arguments.
178+
args: [u64; 6],
179+
},
180+
/// System call exit.
181+
Exit {
182+
/// System call return value.
183+
ret_val: i64,
184+
/// System call error flag.
185+
is_error: u8,
186+
},
187+
/// PTRACE_EVENT_SECCOMP stop.
188+
Seccomp {
189+
/// System call number.
190+
nr: i64,
191+
/// System call arguments.
192+
args: [u64; 6],
193+
/// SECCOMP_RET_DATA portion of SECCOMP_RET_TRACE return value.
194+
ret_data: u32,
195+
},
196+
}
197+
198+
impl SyscallInfo {
199+
pub fn from_raw(raw: ptrace_syscall_info) -> Result<SyscallInfo> {
200+
let op = match raw.op {
201+
libc::PTRACE_SYSCALL_INFO_NONE => Ok(SyscallInfoOp::None),
202+
libc::PTRACE_SYSCALL_INFO_ENTRY => Ok(SyscallInfoOp::Entry {
203+
nr: unsafe { raw.u.entry.nr as _ },
204+
args: unsafe { raw.u.entry.args },
205+
}),
206+
libc::PTRACE_SYSCALL_INFO_EXIT => Ok(SyscallInfoOp::Exit {
207+
ret_val: unsafe { raw.u.exit.sval },
208+
is_error: unsafe { raw.u.exit.is_error },
209+
}),
210+
libc::PTRACE_SYSCALL_INFO_SECCOMP => Ok(SyscallInfoOp::Seccomp {
211+
nr: unsafe { raw.u.seccomp.nr as _ },
212+
args: unsafe { raw.u.seccomp.args },
213+
ret_data: unsafe { raw.u.seccomp.ret_data },
214+
}),
215+
_ => Err(Errno::ENOSYS),
216+
}?;
217+
218+
Ok(SyscallInfo {
219+
op,
220+
arch: raw.arch,
221+
instruction_pointer: raw.instruction_pointer,
222+
stack_pointer: raw.stack_pointer,
223+
})
224+
}
225+
}
226+
155227
libc_bitflags! {
156228
/// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request.
157229
/// See `man ptrace` for more details.
@@ -292,6 +364,21 @@ pub fn getsiginfo(pid: Pid) -> Result<siginfo_t> {
292364
ptrace_get_data::<siginfo_t>(Request::PTRACE_GETSIGINFO, pid)
293365
}
294366

367+
/// Get ptrace syscall info as with `ptrace(PTRACE_GET_SYSCALL_INFO,...)`
368+
/// Only available on Linux 5.3+
369+
pub fn getsyscallinfo(pid: Pid) -> Result<SyscallInfo> {
370+
let mut data = mem::MaybeUninit::uninit();
371+
unsafe {
372+
ptrace_other(
373+
Request::PTRACE_GET_SYSCALL_INFO,
374+
pid,
375+
mem::size_of::<ptrace_syscall_info>() as *mut c_void,
376+
data.as_mut_ptr() as *mut _ as *mut c_void,
377+
)?;
378+
}
379+
SyscallInfo::from_raw(unsafe { data.assume_init() })
380+
}
381+
295382
/// Set siginfo as with `ptrace(PTRACE_SETSIGINFO,...)`
296383
pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> {
297384
let ret = unsafe {

test/sys/test_ptrace.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,87 @@ fn test_ptrace_syscall() {
273273
}
274274
}
275275
}
276+
277+
#[cfg(target_os = "linux")]
278+
#[test]
279+
fn test_ptrace_getsyscallinfo() {
280+
use nix::sys::ptrace;
281+
use nix::sys::ptrace::SyscallInfoOp;
282+
use nix::sys::signal::kill;
283+
use nix::sys::signal::Signal;
284+
use nix::sys::wait::{waitpid, WaitStatus};
285+
use nix::unistd::fork;
286+
use nix::unistd::getpid;
287+
use nix::unistd::ForkResult::*;
288+
289+
require_capability!("test_ptrace_getsyscallinfo", CAP_SYS_PTRACE);
290+
291+
let _m = crate::FORK_MTX.lock();
292+
293+
match unsafe { fork() }.expect("Error: Fork Failed") {
294+
Child => {
295+
ptrace::traceme().unwrap();
296+
// first sigstop until parent is ready to continue
297+
let pid = getpid();
298+
kill(pid, Signal::SIGSTOP).unwrap();
299+
kill(pid, Signal::SIGTERM).unwrap();
300+
unsafe {
301+
::libc::_exit(0);
302+
}
303+
}
304+
305+
Parent { child } => {
306+
assert_eq!(
307+
waitpid(child, None),
308+
Ok(WaitStatus::Stopped(child, Signal::SIGSTOP))
309+
);
310+
311+
// set this option to recognize syscall-stops
312+
ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD)
313+
.unwrap();
314+
315+
// kill entry
316+
ptrace::syscall(child, None).unwrap();
317+
assert_eq!(
318+
waitpid(child, None),
319+
Ok(WaitStatus::PtraceSyscall(child))
320+
);
321+
322+
let syscall_info = ptrace::getsyscallinfo(child);
323+
324+
if syscall_info == Err(Errno::EIO) {
325+
skip!("PTRACE_GET_SYSCALL_INFO is not supported on this platform. Skipping test.");
326+
}
327+
328+
assert!(matches!(
329+
syscall_info.unwrap().op,
330+
SyscallInfoOp::Entry {
331+
nr: ::libc::SYS_kill,
332+
args: [pid, sig, ..]
333+
} if pid == child.as_raw() as _ && sig == ::libc::SIGTERM as _
334+
));
335+
336+
// kill exit
337+
ptrace::syscall(child, None).unwrap();
338+
assert_eq!(
339+
waitpid(child, None),
340+
Ok(WaitStatus::PtraceSyscall(child))
341+
);
342+
343+
assert_eq!(
344+
ptrace::getsyscallinfo(child).unwrap().op,
345+
SyscallInfoOp::Exit {
346+
ret_val: 0,
347+
is_error: 0
348+
}
349+
);
350+
351+
// resume child
352+
ptrace::detach(child, None).unwrap();
353+
assert_eq!(
354+
waitpid(child, None),
355+
Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false))
356+
);
357+
}
358+
}
359+
}

0 commit comments

Comments
 (0)