1 #[cfg(all(
2     target_os = "linux",
3     any(target_arch = "x86_64", target_arch = "x86"),
4     target_env = "gnu"
5 ))]
6 use memoffset::offset_of;
7 use nix::errno::Errno;
8 use nix::sys::ptrace;
9 #[cfg(linux_android)]
10 use nix::sys::ptrace::Options;
11 use nix::unistd::getpid;
12 
13 #[cfg(linux_android)]
14 use std::mem;
15 
16 use crate::*;
17 
18 #[test]
test_ptrace()19 fn test_ptrace() {
20     // Just make sure ptrace can be called at all, for now.
21     // FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS
22     require_capability!("test_ptrace", CAP_SYS_PTRACE);
23     let err = ptrace::attach(getpid()).unwrap_err();
24     assert!(
25         err == Errno::EPERM || err == Errno::EINVAL || err == Errno::ENOSYS
26     );
27 }
28 
29 // Just make sure ptrace_setoptions can be called at all, for now.
30 #[test]
31 #[cfg(linux_android)]
test_ptrace_setoptions()32 fn test_ptrace_setoptions() {
33     require_capability!("test_ptrace_setoptions", CAP_SYS_PTRACE);
34     let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD)
35         .unwrap_err();
36     assert_ne!(err, Errno::EOPNOTSUPP);
37 }
38 
39 // Just make sure ptrace_getevent can be called at all, for now.
40 #[test]
41 #[cfg(linux_android)]
test_ptrace_getevent()42 fn test_ptrace_getevent() {
43     require_capability!("test_ptrace_getevent", CAP_SYS_PTRACE);
44     let err = ptrace::getevent(getpid()).unwrap_err();
45     assert_ne!(err, Errno::EOPNOTSUPP);
46 }
47 
48 // Just make sure ptrace_getsiginfo can be called at all, for now.
49 #[test]
50 #[cfg(linux_android)]
test_ptrace_getsiginfo()51 fn test_ptrace_getsiginfo() {
52     require_capability!("test_ptrace_getsiginfo", CAP_SYS_PTRACE);
53     if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) {
54         panic!("ptrace_getsiginfo returns Errno::EOPNOTSUPP!");
55     }
56 }
57 
58 // Just make sure ptrace_setsiginfo can be called at all, for now.
59 #[test]
60 #[cfg(linux_android)]
test_ptrace_setsiginfo()61 fn test_ptrace_setsiginfo() {
62     require_capability!("test_ptrace_setsiginfo", CAP_SYS_PTRACE);
63     let siginfo = unsafe { mem::zeroed() };
64     if let Err(Errno::EOPNOTSUPP) = ptrace::setsiginfo(getpid(), &siginfo) {
65         panic!("ptrace_setsiginfo returns Errno::EOPNOTSUPP!");
66     }
67 }
68 
69 #[test]
test_ptrace_cont()70 fn test_ptrace_cont() {
71     use nix::sys::ptrace;
72     use nix::sys::signal::{raise, Signal};
73     use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
74     use nix::unistd::fork;
75     use nix::unistd::ForkResult::*;
76 
77     require_capability!("test_ptrace_cont", CAP_SYS_PTRACE);
78 
79     let _m = crate::FORK_MTX.lock();
80 
81     // FIXME: qemu-user doesn't implement ptrace on all architectures
82     // and returns ENOSYS in this case.
83     // We (ab)use this behavior to detect the affected platforms
84     // and skip the test then.
85     // On valid platforms the ptrace call should return Errno::EPERM, this
86     // is already tested by `test_ptrace`.
87     let err = ptrace::attach(getpid()).unwrap_err();
88     if err == Errno::ENOSYS {
89         return;
90     }
91 
92     match unsafe { fork() }.expect("Error: Fork Failed") {
93         Child => {
94             ptrace::traceme().unwrap();
95             // As recommended by ptrace(2), raise SIGTRAP to pause the child
96             // until the parent is ready to continue
97             loop {
98                 raise(Signal::SIGTRAP).unwrap();
99             }
100         }
101         Parent { child } => {
102             assert_eq!(
103                 waitpid(child, None),
104                 Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
105             );
106             ptrace::cont(child, None).unwrap();
107             assert_eq!(
108                 waitpid(child, None),
109                 Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
110             );
111             ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
112             match waitpid(child, None) {
113                 Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
114                     if pid == child =>
115                 {
116                     // FIXME It's been observed on some systems (apple) the
117                     // tracee may not be killed but remain as a zombie process
118                     // affecting other wait based tests. Add an extra kill just
119                     // to make sure there are no zombies.
120                     let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
121                     while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
122                         let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
123                     }
124                 }
125                 _ => panic!("The process should have been killed"),
126             }
127         }
128     }
129 }
130 
131 #[cfg(target_os = "linux")]
132 #[test]
test_ptrace_interrupt()133 fn test_ptrace_interrupt() {
134     use nix::sys::ptrace;
135     use nix::sys::signal::Signal;
136     use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
137     use nix::unistd::fork;
138     use nix::unistd::ForkResult::*;
139     use std::thread::sleep;
140     use std::time::Duration;
141 
142     require_capability!("test_ptrace_interrupt", CAP_SYS_PTRACE);
143 
144     let _m = crate::FORK_MTX.lock();
145 
146     match unsafe { fork() }.expect("Error: Fork Failed") {
147         Child => loop {
148             sleep(Duration::from_millis(1000));
149         },
150         Parent { child } => {
151             ptrace::seize(child, ptrace::Options::PTRACE_O_TRACESYSGOOD)
152                 .unwrap();
153             ptrace::interrupt(child).unwrap();
154             assert_eq!(
155                 waitpid(child, None),
156                 Ok(WaitStatus::PtraceEvent(child, Signal::SIGTRAP, 128))
157             );
158             ptrace::syscall(child, None).unwrap();
159             assert_eq!(
160                 waitpid(child, None),
161                 Ok(WaitStatus::PtraceSyscall(child))
162             );
163             ptrace::detach(child, Some(Signal::SIGKILL)).unwrap();
164             match waitpid(child, None) {
165                 Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
166                     if pid == child =>
167                 {
168                     let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
169                     while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
170                         let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
171                     }
172                 }
173                 _ => panic!("The process should have been killed"),
174             }
175         }
176     }
177 }
178 
179 // ptrace::{setoptions, getregs} are only available in these platforms
180 #[cfg(all(
181     target_os = "linux",
182     any(target_arch = "x86_64", target_arch = "x86"),
183     target_env = "gnu"
184 ))]
185 #[test]
test_ptrace_syscall()186 fn test_ptrace_syscall() {
187     use nix::sys::ptrace;
188     use nix::sys::signal::kill;
189     use nix::sys::signal::Signal;
190     use nix::sys::wait::{waitpid, WaitStatus};
191     use nix::unistd::fork;
192     use nix::unistd::getpid;
193     use nix::unistd::ForkResult::*;
194 
195     require_capability!("test_ptrace_syscall", CAP_SYS_PTRACE);
196 
197     let _m = crate::FORK_MTX.lock();
198 
199     match unsafe { fork() }.expect("Error: Fork Failed") {
200         Child => {
201             ptrace::traceme().unwrap();
202             // first sigstop until parent is ready to continue
203             let pid = getpid();
204             kill(pid, Signal::SIGSTOP).unwrap();
205             kill(pid, Signal::SIGTERM).unwrap();
206             unsafe {
207                 ::libc::_exit(0);
208             }
209         }
210 
211         Parent { child } => {
212             assert_eq!(
213                 waitpid(child, None),
214                 Ok(WaitStatus::Stopped(child, Signal::SIGSTOP))
215             );
216 
217             // set this option to recognize syscall-stops
218             ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD)
219                 .unwrap();
220 
221             #[cfg(target_arch = "x86_64")]
222             let get_syscall_id =
223                 || ptrace::getregs(child).unwrap().orig_rax as libc::c_long;
224 
225             #[cfg(target_arch = "x86")]
226             let get_syscall_id =
227                 || ptrace::getregs(child).unwrap().orig_eax as libc::c_long;
228 
229             // this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`.
230             #[cfg(target_arch = "x86_64")]
231             let rax_offset = offset_of!(libc::user_regs_struct, orig_rax);
232             #[cfg(target_arch = "x86")]
233             let rax_offset = offset_of!(libc::user_regs_struct, orig_eax);
234 
235             let get_syscall_from_user_area = || {
236                 // Find the offset of `user.regs.rax` (or `user.regs.eax` for x86)
237                 let rax_offset = offset_of!(libc::user, regs) + rax_offset;
238                 ptrace::read_user(child, rax_offset as _).unwrap()
239                     as libc::c_long
240             };
241 
242             // kill entry
243             ptrace::syscall(child, None).unwrap();
244             assert_eq!(
245                 waitpid(child, None),
246                 Ok(WaitStatus::PtraceSyscall(child))
247             );
248             assert_eq!(get_syscall_id(), ::libc::SYS_kill);
249             assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);
250 
251             // kill exit
252             ptrace::syscall(child, None).unwrap();
253             assert_eq!(
254                 waitpid(child, None),
255                 Ok(WaitStatus::PtraceSyscall(child))
256             );
257             assert_eq!(get_syscall_id(), ::libc::SYS_kill);
258             assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);
259 
260             // receive signal
261             ptrace::syscall(child, None).unwrap();
262             assert_eq!(
263                 waitpid(child, None),
264                 Ok(WaitStatus::Stopped(child, Signal::SIGTERM))
265             );
266 
267             // inject signal
268             ptrace::syscall(child, Signal::SIGTERM).unwrap();
269             assert_eq!(
270                 waitpid(child, None),
271                 Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false))
272             );
273         }
274     }
275 }
276