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