1 use nix::errno::Errno;
2 use nix::unistd::getpid;
3 use nix::sys::ptrace;
4 #[cfg(any(target_os = "android", target_os = "linux"))]
5 use nix::sys::ptrace::Options;
6 
7 #[cfg(any(target_os = "android", target_os = "linux"))]
8 use std::mem;
9 
10 use crate::*;
11 
12 #[test]
test_ptrace()13 fn test_ptrace() {
14     // Just make sure ptrace can be called at all, for now.
15     // FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS
16     require_capability!(CAP_SYS_PTRACE);
17     let err = ptrace::attach(getpid()).unwrap_err();
18     assert!(err == Errno::EPERM || err == Errno::EINVAL ||
19             err == Errno::ENOSYS);
20 }
21 
22 // Just make sure ptrace_setoptions can be called at all, for now.
23 #[test]
24 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptrace_setoptions()25 fn test_ptrace_setoptions() {
26     require_capability!(CAP_SYS_PTRACE);
27     let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD).unwrap_err();
28     assert!(err != Errno::EOPNOTSUPP);
29 }
30 
31 // Just make sure ptrace_getevent can be called at all, for now.
32 #[test]
33 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptrace_getevent()34 fn test_ptrace_getevent() {
35     require_capability!(CAP_SYS_PTRACE);
36     let err = ptrace::getevent(getpid()).unwrap_err();
37     assert!(err != Errno::EOPNOTSUPP);
38 }
39 
40 // Just make sure ptrace_getsiginfo can be called at all, for now.
41 #[test]
42 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptrace_getsiginfo()43 fn test_ptrace_getsiginfo() {
44     require_capability!(CAP_SYS_PTRACE);
45     if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) {
46         panic!("ptrace_getsiginfo returns Errno::EOPNOTSUPP!");
47     }
48 }
49 
50 // Just make sure ptrace_setsiginfo can be called at all, for now.
51 #[test]
52 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptrace_setsiginfo()53 fn test_ptrace_setsiginfo() {
54     require_capability!(CAP_SYS_PTRACE);
55     let siginfo = unsafe { mem::zeroed() };
56     if let Err(Errno::EOPNOTSUPP) = ptrace::setsiginfo(getpid(), &siginfo) {
57         panic!("ptrace_setsiginfo returns Errno::EOPNOTSUPP!");
58     }
59 }
60 
61 
62 #[test]
test_ptrace_cont()63 fn test_ptrace_cont() {
64     use nix::sys::ptrace;
65     use nix::sys::signal::{raise, Signal};
66     use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
67     use nix::unistd::fork;
68     use nix::unistd::ForkResult::*;
69 
70     require_capability!(CAP_SYS_PTRACE);
71 
72     let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
73 
74     // FIXME: qemu-user doesn't implement ptrace on all architectures
75     // and retunrs ENOSYS in this case.
76     // We (ab)use this behavior to detect the affected platforms
77     // and skip the test then.
78     // On valid platforms the ptrace call should return Errno::EPERM, this
79     // is already tested by `test_ptrace`.
80     let err = ptrace::attach(getpid()).unwrap_err();
81     if err == Errno::ENOSYS {
82         return;
83     }
84 
85     match unsafe{fork()}.expect("Error: Fork Failed") {
86         Child => {
87             ptrace::traceme().unwrap();
88             // As recommended by ptrace(2), raise SIGTRAP to pause the child
89             // until the parent is ready to continue
90             loop {
91                 raise(Signal::SIGTRAP).unwrap();
92             }
93 
94         },
95         Parent { child } => {
96             assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
97             ptrace::cont(child, None).unwrap();
98             assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
99             ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
100             match waitpid(child, None) {
101                 Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {
102                     // FIXME It's been observed on some systems (apple) the
103                     // tracee may not be killed but remain as a zombie process
104                     // affecting other wait based tests. Add an extra kill just
105                     // to make sure there are no zombies.
106                     let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
107                     while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
108                         let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
109                     }
110                 }
111                 _ => panic!("The process should have been killed"),
112             }
113         },
114     }
115 }
116 
117 // ptrace::{setoptions, getregs} are only available in these platforms
118 #[cfg(all(target_os = "linux",
119           any(target_arch = "x86_64",
120               target_arch = "x86"),
121           target_env = "gnu"))]
122 #[test]
test_ptrace_syscall()123 fn test_ptrace_syscall() {
124     use nix::sys::signal::kill;
125     use nix::sys::ptrace;
126     use nix::sys::signal::Signal;
127     use nix::sys::wait::{waitpid, WaitStatus};
128     use nix::unistd::fork;
129     use nix::unistd::getpid;
130     use nix::unistd::ForkResult::*;
131 
132     require_capability!(CAP_SYS_PTRACE);
133 
134     let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
135 
136     match unsafe{fork()}.expect("Error: Fork Failed") {
137         Child => {
138             ptrace::traceme().unwrap();
139             // first sigstop until parent is ready to continue
140             let pid = getpid();
141             kill(pid, Signal::SIGSTOP).unwrap();
142             kill(pid, Signal::SIGTERM).unwrap();
143             unsafe { ::libc::_exit(0); }
144         },
145 
146         Parent { child } => {
147             assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGSTOP)));
148 
149             // set this option to recognize syscall-stops
150             ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();
151 
152             #[cfg(target_arch = "x86_64")]
153             let get_syscall_id = || ptrace::getregs(child).unwrap().orig_rax as libc::c_long;
154 
155             #[cfg(target_arch = "x86")]
156             let get_syscall_id = || ptrace::getregs(child).unwrap().orig_eax as libc::c_long;
157 
158             // kill entry
159             ptrace::syscall(child, None).unwrap();
160             assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
161             assert_eq!(get_syscall_id(), ::libc::SYS_kill);
162 
163             // kill exit
164             ptrace::syscall(child, None).unwrap();
165             assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
166             assert_eq!(get_syscall_id(), ::libc::SYS_kill);
167 
168             // receive signal
169             ptrace::syscall(child, None).unwrap();
170             assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTERM)));
171 
172             // inject signal
173             ptrace::syscall(child, Signal::SIGTERM).unwrap();
174             assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false)));
175         },
176     }
177 }
178