Skip to content

Commit eba6282

Browse files
committed
ptrace: implement getsyscallinfo
1 parent b2318f9 commit eba6282

File tree

4 files changed

+182
-1
lines changed

4 files changed

+182
-1
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: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ pub type AddressType = *mut ::libc::c_void;
2222
))]
2323
use libc::user_regs_struct;
2424

25+
#[cfg(all(target_os = "linux", target_env = "gnu"))]
26+
use libc::ptrace_syscall_info;
27+
2528
cfg_if! {
2629
if #[cfg(any(all(target_os = "linux", target_arch = "s390x"),
2730
all(target_os = "linux", target_env = "gnu"),
@@ -121,6 +124,8 @@ libc_enum! {
121124
#[cfg(all(target_os = "linux", target_env = "gnu",
122125
any(target_arch = "x86", target_arch = "x86_64")))]
123126
PTRACE_SYSEMU_SINGLESTEP,
127+
#[cfg(all(target_os = "linux", target_env = "gnu"))]
128+
PTRACE_GET_SYSCALL_INFO,
124129
}
125130
}
126131

@@ -152,6 +157,80 @@ libc_enum! {
152157
}
153158
}
154159

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

374+
/// Get ptrace syscall info as with `ptrace(PTRACE_GET_SYSCALL_INFO,...)`
375+
/// Only available on Linux 5.3+
376+
#[cfg(all(target_os = "linux", target_env = "gnu"))]
377+
pub fn getsyscallinfo(pid: Pid) -> Result<SyscallInfo> {
378+
let mut data = mem::MaybeUninit::uninit();
379+
unsafe {
380+
ptrace_other(
381+
Request::PTRACE_GET_SYSCALL_INFO,
382+
pid,
383+
mem::size_of::<ptrace_syscall_info>() as *mut c_void,
384+
data.as_mut_ptr() as *mut _ as *mut c_void,
385+
)?;
386+
}
387+
SyscallInfo::from_raw(unsafe { data.assume_init() })
388+
}
389+
295390
/// Set siginfo as with `ptrace(PTRACE_SETSIGINFO,...)`
296391
pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> {
297392
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(all(target_os = "linux", target_env = "gnu"))]
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,
332+
args: [pid, sig, ..]
333+
} if nr == ::libc::SYS_kill as _ && 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)