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