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!("test_ptrace", 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!("test_ptrace_setoptions", 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!("test_ptrace_getevent", 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!("test_ptrace_getsiginfo", 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!("test_ptrace_setsiginfo", 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!("test_ptrace_cont", CAP_SYS_PTRACE);
71
72 let _m = crate::FORK_MTX.lock();
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 #[cfg(target_os = "linux")]
118 #[test]
test_ptrace_interrupt()119 fn test_ptrace_interrupt() {
120 use nix::sys::ptrace;
121 use nix::sys::signal::Signal;
122 use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
123 use nix::unistd::fork;
124 use nix::unistd::ForkResult::*;
125 use std::thread::sleep;
126 use std::time::Duration;
127
128 require_capability!("test_ptrace_interrupt", CAP_SYS_PTRACE);
129
130 let _m = crate::FORK_MTX.lock();
131
132 match unsafe{fork()}.expect("Error: Fork Failed") {
133 Child => {
134 loop {
135 sleep(Duration::from_millis(1000));
136 }
137
138 },
139 Parent { child } => {
140 ptrace::seize(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();
141 ptrace::interrupt(child).unwrap();
142 assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, Signal::SIGTRAP, 128)));
143 ptrace::syscall(child, None).unwrap();
144 assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
145 ptrace::detach(child, Some(Signal::SIGKILL)).unwrap();
146 match waitpid(child, None) {
147 Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {
148 let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
149 while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
150 let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
151 }
152 }
153 _ => panic!("The process should have been killed"),
154 }
155 },
156 }
157 }
158
159 // ptrace::{setoptions, getregs} are only available in these platforms
160 #[cfg(all(target_os = "linux",
161 any(target_arch = "x86_64",
162 target_arch = "x86"),
163 target_env = "gnu"))]
164 #[test]
test_ptrace_syscall()165 fn test_ptrace_syscall() {
166 use nix::sys::signal::kill;
167 use nix::sys::ptrace;
168 use nix::sys::signal::Signal;
169 use nix::sys::wait::{waitpid, WaitStatus};
170 use nix::unistd::fork;
171 use nix::unistd::getpid;
172 use nix::unistd::ForkResult::*;
173
174 require_capability!("test_ptrace_syscall", CAP_SYS_PTRACE);
175
176 let _m = crate::FORK_MTX.lock();
177
178 match unsafe{fork()}.expect("Error: Fork Failed") {
179 Child => {
180 ptrace::traceme().unwrap();
181 // first sigstop until parent is ready to continue
182 let pid = getpid();
183 kill(pid, Signal::SIGSTOP).unwrap();
184 kill(pid, Signal::SIGTERM).unwrap();
185 unsafe { ::libc::_exit(0); }
186 },
187
188 Parent { child } => {
189 assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGSTOP)));
190
191 // set this option to recognize syscall-stops
192 ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();
193
194 #[cfg(target_arch = "x86_64")]
195 let get_syscall_id = || ptrace::getregs(child).unwrap().orig_rax as libc::c_long;
196
197 #[cfg(target_arch = "x86")]
198 let get_syscall_id = || ptrace::getregs(child).unwrap().orig_eax as libc::c_long;
199
200 // kill entry
201 ptrace::syscall(child, None).unwrap();
202 assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
203 assert_eq!(get_syscall_id(), ::libc::SYS_kill);
204
205 // kill exit
206 ptrace::syscall(child, None).unwrap();
207 assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
208 assert_eq!(get_syscall_id(), ::libc::SYS_kill);
209
210 // receive signal
211 ptrace::syscall(child, None).unwrap();
212 assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTERM)));
213
214 // inject signal
215 ptrace::syscall(child, Signal::SIGTERM).unwrap();
216 assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false)));
217 },
218 }
219 }
220