1#[cfg(all(
2 target_os = "linux",
3 any(target_arch = "x86_64", target_arch = "x86"),
4 target_env = "gnu"
5))]
6use memoffset::offset_of;
7use nix::errno::Errno;
8use nix::sys::ptrace;
9#[cfg(any(target_os = "android", target_os = "linux"))]
10use nix::sys::ptrace::Options;
11use nix::unistd::getpid;
12
13#[cfg(any(target_os = "android", target_os = "linux"))]
14use std::mem;
15
16use crate::*;
17
18#[test]
19fn 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(any(target_os = "android", target_os = "linux"))]
32fn 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(any(target_os = "android", target_os = "linux"))]
42fn 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(any(target_os = "android", target_os = "linux"))]
51fn 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(any(target_os = "android", target_os = "linux"))]
61fn 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]
70fn 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]
133fn 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]
186fn 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