Skip to content
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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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))
Copy link
Member

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.


### Changed

Expand Down
95 changes: 95 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"))]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curios. Does this really need the target_env = "gnu"? I've just looked into the musl source code and found a definition for struct __ptrace_syscall_info inside include/sys/ptrace.h. Maybe it is even better, to remove the target_enventirely and replace it with a check for kernel 5.3.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Rust libc crate, __ptrace_syscall_info is only available for gnu env. I will work on upstreaming __ptrace_syscall_info into Rust libc musl first.

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,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"))]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, my criticism with the gnu.

PTRACE_GET_SYSCALL_INFO,
}
}

Expand Down Expand Up @@ -152,6 +157,80 @@ libc_enum! {
}
}

#[cfg(all(target_os = "linux", target_env = "gnu"))]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^

#[cfg_attr(docsrs, doc(cfg(all())))]
Copy link
Member

Choose a reason for hiding this comment

The 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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible (and convenient) to reuse the ptrace_syscall_info type from libc, like this:

#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SyscallInfo(ptrace_syscall_info);

If so, then we should switch to it

Copy link
Author

Choose a reason for hiding this comment

The 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"))]

Choose a reason for hiding this comment

The 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.
Expand Down Expand Up @@ -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"))]

Choose a reason for hiding this comment

The 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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pointer type casting, we prefer .cast() over the as keyword

)?;
}
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
93 changes: 93 additions & 0 deletions test/sys/test_ptrace.rs
Expand Up @@ -273,3 +273,96 @@ fn test_ptrace_syscall() {
}
}
}

#[cfg(all(target_os = "linux", target_env = "gnu"))]

Choose a reason for hiding this comment

The 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
}
}
}