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