summaryrefslogtreecommitdiffstats
path: root/third_party/rust/nix/test/sys/test_ptrace.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/nix/test/sys/test_ptrace.rs')
-rw-r--r--third_party/rust/nix/test/sys/test_ptrace.rs275
1 files changed, 275 insertions, 0 deletions
diff --git a/third_party/rust/nix/test/sys/test_ptrace.rs b/third_party/rust/nix/test/sys/test_ptrace.rs
new file mode 100644
index 0000000000..530560fe17
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_ptrace.rs
@@ -0,0 +1,275 @@
+#[cfg(all(
+ target_os = "linux",
+ any(target_arch = "x86_64", target_arch = "x86"),
+ target_env = "gnu"
+))]
+use memoffset::offset_of;
+use nix::errno::Errno;
+use nix::sys::ptrace;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use nix::sys::ptrace::Options;
+use nix::unistd::getpid;
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use std::mem;
+
+use crate::*;
+
+#[test]
+fn test_ptrace() {
+ // Just make sure ptrace can be called at all, for now.
+ // FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS
+ require_capability!("test_ptrace", CAP_SYS_PTRACE);
+ let err = ptrace::attach(getpid()).unwrap_err();
+ assert!(
+ err == Errno::EPERM || err == Errno::EINVAL || err == Errno::ENOSYS
+ );
+}
+
+// Just make sure ptrace_setoptions can be called at all, for now.
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptrace_setoptions() {
+ require_capability!("test_ptrace_setoptions", CAP_SYS_PTRACE);
+ let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD)
+ .unwrap_err();
+ assert_ne!(err, Errno::EOPNOTSUPP);
+}
+
+// Just make sure ptrace_getevent can be called at all, for now.
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptrace_getevent() {
+ require_capability!("test_ptrace_getevent", CAP_SYS_PTRACE);
+ let err = ptrace::getevent(getpid()).unwrap_err();
+ assert_ne!(err, Errno::EOPNOTSUPP);
+}
+
+// Just make sure ptrace_getsiginfo can be called at all, for now.
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptrace_getsiginfo() {
+ require_capability!("test_ptrace_getsiginfo", CAP_SYS_PTRACE);
+ if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) {
+ panic!("ptrace_getsiginfo returns Errno::EOPNOTSUPP!");
+ }
+}
+
+// Just make sure ptrace_setsiginfo can be called at all, for now.
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptrace_setsiginfo() {
+ require_capability!("test_ptrace_setsiginfo", CAP_SYS_PTRACE);
+ let siginfo = unsafe { mem::zeroed() };
+ if let Err(Errno::EOPNOTSUPP) = ptrace::setsiginfo(getpid(), &siginfo) {
+ panic!("ptrace_setsiginfo returns Errno::EOPNOTSUPP!");
+ }
+}
+
+#[test]
+fn test_ptrace_cont() {
+ use nix::sys::ptrace;
+ use nix::sys::signal::{raise, Signal};
+ use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
+ use nix::unistd::fork;
+ use nix::unistd::ForkResult::*;
+
+ require_capability!("test_ptrace_cont", CAP_SYS_PTRACE);
+
+ let _m = crate::FORK_MTX.lock();
+
+ // FIXME: qemu-user doesn't implement ptrace on all architectures
+ // and returns ENOSYS in this case.
+ // We (ab)use this behavior to detect the affected platforms
+ // and skip the test then.
+ // On valid platforms the ptrace call should return Errno::EPERM, this
+ // is already tested by `test_ptrace`.
+ let err = ptrace::attach(getpid()).unwrap_err();
+ if err == Errno::ENOSYS {
+ return;
+ }
+
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => {
+ ptrace::traceme().unwrap();
+ // As recommended by ptrace(2), raise SIGTRAP to pause the child
+ // until the parent is ready to continue
+ loop {
+ raise(Signal::SIGTRAP).unwrap();
+ }
+ }
+ Parent { child } => {
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
+ );
+ ptrace::cont(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
+ );
+ ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
+ match waitpid(child, None) {
+ Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
+ if pid == child =>
+ {
+ // FIXME It's been observed on some systems (apple) the
+ // tracee may not be killed but remain as a zombie process
+ // affecting other wait based tests. Add an extra kill just
+ // to make sure there are no zombies.
+ let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
+ while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
+ let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
+ }
+ }
+ _ => panic!("The process should have been killed"),
+ }
+ }
+ }
+}
+
+#[cfg(target_os = "linux")]
+#[test]
+fn test_ptrace_interrupt() {
+ use nix::sys::ptrace;
+ use nix::sys::signal::Signal;
+ use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
+ use nix::unistd::fork;
+ use nix::unistd::ForkResult::*;
+ use std::thread::sleep;
+ use std::time::Duration;
+
+ require_capability!("test_ptrace_interrupt", CAP_SYS_PTRACE);
+
+ let _m = crate::FORK_MTX.lock();
+
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => loop {
+ sleep(Duration::from_millis(1000));
+ },
+ Parent { child } => {
+ ptrace::seize(child, ptrace::Options::PTRACE_O_TRACESYSGOOD)
+ .unwrap();
+ ptrace::interrupt(child).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceEvent(child, Signal::SIGTRAP, 128))
+ );
+ ptrace::syscall(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceSyscall(child))
+ );
+ ptrace::detach(child, Some(Signal::SIGKILL)).unwrap();
+ match waitpid(child, None) {
+ Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
+ if pid == child =>
+ {
+ let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
+ while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
+ let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
+ }
+ }
+ _ => panic!("The process should have been killed"),
+ }
+ }
+ }
+}
+
+// ptrace::{setoptions, getregs} are only available in these platforms
+#[cfg(all(
+ target_os = "linux",
+ any(target_arch = "x86_64", target_arch = "x86"),
+ target_env = "gnu"
+))]
+#[test]
+fn test_ptrace_syscall() {
+ use nix::sys::ptrace;
+ 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_syscall", 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();
+ kill(pid, Signal::SIGTERM).unwrap();
+ unsafe {
+ ::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)
+ .unwrap();
+
+ #[cfg(target_arch = "x86_64")]
+ let get_syscall_id =
+ || ptrace::getregs(child).unwrap().orig_rax as libc::c_long;
+
+ #[cfg(target_arch = "x86")]
+ let get_syscall_id =
+ || ptrace::getregs(child).unwrap().orig_eax as libc::c_long;
+
+ // this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`.
+ #[cfg(target_arch = "x86_64")]
+ let rax_offset = offset_of!(libc::user_regs_struct, orig_rax);
+ #[cfg(target_arch = "x86")]
+ let rax_offset = offset_of!(libc::user_regs_struct, orig_eax);
+
+ let get_syscall_from_user_area = || {
+ // Find the offset of `user.regs.rax` (or `user.regs.eax` for x86)
+ let rax_offset = offset_of!(libc::user, regs) + rax_offset;
+ ptrace::read_user(child, rax_offset as _).unwrap()
+ as libc::c_long
+ };
+
+ // kill entry
+ ptrace::syscall(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceSyscall(child))
+ );
+ assert_eq!(get_syscall_id(), ::libc::SYS_kill);
+ assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);
+
+ // kill exit
+ ptrace::syscall(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceSyscall(child))
+ );
+ assert_eq!(get_syscall_id(), ::libc::SYS_kill);
+ assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);
+
+ // receive signal
+ ptrace::syscall(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Stopped(child, Signal::SIGTERM))
+ );
+
+ // inject signal
+ ptrace::syscall(child, Signal::SIGTERM).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false))
+ );
+ }
+ }
+}