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