Skip to content

Commit 3d0d383

Browse files
committed
ptrace: implement getsyscallinfo
1 parent b2318f9 commit 3d0d383

File tree

4 files changed

+184
-1
lines changed

4 files changed

+184
-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: 97 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,10 @@ 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,
129+
PTRACE_GETSIGMASK,
130+
PTRACE_SETSIGMASK,
124131
}
125132
}
126133

@@ -152,6 +159,80 @@ libc_enum! {
152159
}
153160
}
154161

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

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