-
Notifications
You must be signed in to change notification settings - Fork 679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ptrace: implement getsyscallinfo #2006
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,9 @@ pub type AddressType = *mut ::libc::c_void; | |
))] | ||
use libc::user_regs_struct; | ||
|
||
#[cfg(all(target_os = "linux", target_env = "gnu"))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curios. Does this really need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Rust libc crate, |
||
use libc::ptrace_syscall_info; | ||
|
||
cfg_if! { | ||
if #[cfg(any(all(target_os = "linux", target_arch = "s390x"), | ||
all(target_os = "linux", target_env = "gnu"), | ||
|
@@ -121,6 +124,8 @@ 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"))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, my criticism with the |
||
PTRACE_GET_SYSCALL_INFO, | ||
} | ||
} | ||
|
||
|
@@ -152,6 +157,80 @@ libc_enum! { | |
} | ||
} | ||
|
||
#[cfg(all(target_os = "linux", target_env = "gnu"))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^^ |
||
#[cfg_attr(docsrs, doc(cfg(all())))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove this attribute and the one in line 175 as they are useless now |
||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] | ||
pub struct SyscallInfo { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible (and convenient) to reuse the #[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SyscallInfo(ptrace_syscall_info); If so, then we should switch to it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. libc ptrace_syscall_info has a union, which is not safe to use. So the safe SyscallInfo here is probably preferred. |
||
/// 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: u64, | ||
/// 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: u64, | ||
/// 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"))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^^ |
||
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::EINVAL), | ||
}?; | ||
|
||
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. | ||
|
@@ -292,6 +371,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"))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^^ |
||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For pointer type casting, we prefer |
||
)?; | ||
} | ||
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 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -273,3 +273,96 @@ fn test_ptrace_syscall() { | |
} | ||
} | ||
} | ||
|
||
#[cfg(all(target_os = "linux", target_env = "gnu"))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^^ |
||
#[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(); | ||
mbyzhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unsafe { | ||
// make a test syscall that can be intercepted by the tracer | ||
::libc::syscall( | ||
::libc::SYS_kill, | ||
pid.as_raw(), | ||
::libc::SIGKILL, | ||
); | ||
::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 | ||
| ptrace::Options::PTRACE_O_EXITKILL, | ||
) | ||
.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)) | ||
); | ||
mbyzhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have changed our changelog mode, please see
CONTRIBUTING.md
on how to add a changelog.