Skip to main content

process_backend/local/
mod.rs

1use crate::regs::*;
2use core::{
3    cell::RefCell,
4    ffi::{CStr, c_int, c_long, c_void},
5    mem, ptr,
6};
7use libc::pid_t;
8use syscall_invoker::SyscallInvoker;
9
10pub use self::{error::Error, module_reader::MappedModuleMemoryReader};
11
12mod error;
13mod module_reader;
14mod syscall_invoker;
15
16#[cfg(target_env = "gnu")]
17type PtraceRequestType = core::ffi::c_uint;
18
19#[cfg(not(target_env = "gnu"))]
20type PtraceRequestType = core::ffi::c_int;
21
22#[derive(Debug)]
23pub struct Backend {
24    pid: pid_t,
25    syscall_invoker: RefCell<SyscallInvoker>,
26}
27
28impl Backend {
29    pub fn new(pid: libc::pid_t) -> Self {
30        Self {
31            pid,
32            syscall_invoker: Default::default(),
33        }
34    }
35    pub fn process_reader(&self) -> ProcessReader {
36        ProcessReader(process_reader::ProcessReader::new(self.pid))
37    }
38    pub fn stop_process(&self) -> Result<(), Error> {
39        self.standard_syscall(|| unsafe { libc::kill(self.pid, libc::SIGSTOP) })
40            .map_err(Error::SigStopFailed)?;
41        Ok(())
42    }
43
44    pub fn continue_process(&self) -> Result<(), Error> {
45        self.standard_syscall(|| unsafe { libc::kill(self.pid, libc::SIGCONT) })
46            .map_err(Error::SigContFailed)?;
47        Ok(())
48    }
49
50    pub fn suspend_thread(&self, tid: libc::pid_t) -> Result<(), Error> {
51        self.standard_syscall(|| unsafe {
52            ptrace(libc::PTRACE_ATTACH, tid, ptr::null_mut(), ptr::null_mut())
53        })
54        .map_err(Error::PtraceAttachFailed)?;
55
56        loop {
57            let mut status = 0;
58            if let Err(e) =
59                self.standard_syscall(|| unsafe { libc::waitpid(tid, &mut status, libc::__WALL) })
60            {
61                if e == libc::EINTR {
62                    continue;
63                }
64                self.ptrace_detach(tid)?;
65                Err(Error::WaitPidFailed(e))?;
66            }
67
68            if !libc::WIFSTOPPED(status) {
69                Err(Error::UnexpectedStatus(status))?;
70            }
71
72            let signal = libc::WSTOPSIG(status);
73
74            // Any signal will stop the thread, make sure it is SIGSTOP. Otherwise, this
75            // signal will be delivered after PTRACE_DETACH, and the thread will enter
76            // the "T (stopped)" state.
77            if signal == libc::SIGSTOP {
78                break;
79            }
80
81            // Signals other than SIGSTOP that are received need to be reinjected,
82            // or they will otherwise get lost.
83            self.standard_syscall(|| unsafe {
84                ptrace(libc::PTRACE_CONT, tid, ptr::null_mut(), signal as *mut _)
85            })
86            .map_err(|e| Error::ReinjectFailed(signal, e))?;
87        }
88
89        Ok(())
90    }
91
92    pub fn resume_thread(&self, tid: libc::pid_t) -> Result<(), Error> {
93        self.ptrace_detach(tid)
94    }
95
96    pub fn map_module_into_memory(
97        &self,
98        path: &CStr,
99        offset: u64,
100    ) -> Result<MappedModuleMemoryReader, Error> {
101        MappedModuleMemoryReader::new(&mut self.syscall_invoker.borrow_mut(), path, offset)
102    }
103
104    pub fn stat_file(&self, path: &CStr) -> Result<libc::stat, Error> {
105        let mut output = unsafe { mem::zeroed::<libc::stat>() };
106        self.standard_syscall(|| unsafe { libc::stat(path.as_ptr(), &mut output) })
107            .map_err(Error::StatFailed)?;
108        Ok(output)
109    }
110
111    pub fn read_file(&self, path: &CStr) -> Result<FileReader, Error> {
112        self.open_file(path).map(FileReader)
113    }
114
115    pub fn read_dir(&self, path: &CStr) -> Result<DirReader, Error> {
116        self.special_syscall(|| unsafe {
117            let dirp = libc::opendir(path.as_ptr());
118            if dirp.is_null() {
119                return Err(());
120            }
121            Ok(dirp)
122        })
123        .map(|dirp| DirReader { dirp, eof: false })
124        .map_err(Error::OpenDirFailed)
125    }
126
127    pub fn read_link(&self, path: &CStr, buf: &mut [u8]) -> Result<usize, Error> {
128        let bytes_read = self
129            .standard_syscall(|| unsafe {
130                libc::readlink(path.as_ptr(), buf.as_mut_ptr().cast(), buf.len())
131            })
132            .map_err(Error::ReadLinkFailed)?;
133
134        let bytes_read = usize::try_from(bytes_read).unwrap();
135        if bytes_read == buf.len() {
136            Err(Error::BufferTooSmall)?;
137        }
138
139        Ok(bytes_read)
140    }
141
142    pub fn get_gen_regs(&self, tid: libc::pid_t) -> Result<GenRegs, Error> {
143        self.getregset(tid).or_else(|_| self.getregs(tid))
144    }
145
146    pub fn get_fp_regs(&self, tid: libc::pid_t) -> Result<FpRegs, Error> {
147        self.getfpregset(tid).or_else(|_| self.getfpregs(tid))
148    }
149
150    #[cfg(target_arch = "x86")]
151    pub fn get_fpx_regs(&self, tid: libc::pid_t) -> Result<FpxRegs, Error> {
152        const PTRACE_GETFPXREGS: PtraceRequestType = 18;
153        unsafe { self.ptrace_getregs::<FpxRegs>(PTRACE_GETFPXREGS, tid) }
154    }
155
156    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
157    pub fn ptrace_peekuser(
158        &self,
159        pid: libc::pid_t,
160        addr: usize,
161    ) -> Result<[u8; mem::size_of::<libc::c_long>()], Error> {
162        self.special_syscall(|| unsafe {
163            set_errno(0);
164            let rv = ptrace(
165                libc::PTRACE_PEEKUSER,
166                pid,
167                addr as *mut _,
168                core::ptr::null_mut(),
169            );
170            if rv == -1 && errno() != 0 {
171                return Err(());
172            }
173            Ok(rv.to_ne_bytes())
174        })
175        .map_err(Error::PtracePeekUserFailed)
176    }
177
178    pub fn process_reader_for_virtual_mem(&self) -> ProcessReader {
179        ProcessReader(process_reader::ProcessReader::for_virtual_mem(self.pid))
180    }
181
182    pub fn process_reader_for_file(&self) -> Result<ProcessReader, Error> {
183        process_reader::ProcessReader::for_file(self.pid)
184            .map(ProcessReader)
185            .map_err(Error::ProcessReader)
186    }
187
188    pub fn process_reader_for_ptrace(&self) -> ProcessReader {
189        ProcessReader(process_reader::ProcessReader::for_ptrace(self.pid))
190    }
191
192    fn open_file(&self, path: &CStr) -> Result<OwnedFd, Error> {
193        self.standard_syscall(|| unsafe {
194            libc::open(path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC, 0)
195        })
196        .map(|fd| unsafe { OwnedFd::new(fd) })
197        .map_err(Error::OpenFileFailed)
198    }
199
200    fn getregset(&self, _pid: libc::pid_t) -> Result<GenRegs, Error> {
201        #[cfg(target_arch = "arm")]
202        {
203            Err(Error::NotSupported)
204        }
205        #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
206        {
207            const NT_PRSTATUS: usize = 1;
208            self.ptrace_getregset(NT_PRSTATUS, _pid)
209        }
210    }
211
212    fn getregs(&self, pid: libc::pid_t) -> Result<GenRegs, Error> {
213        const PTRACE_GETREGS: PtraceRequestType = 12;
214        unsafe { self.ptrace_getregs::<GenRegs>(PTRACE_GETREGS, pid) }
215    }
216
217    fn getfpregset(&self, pid: libc::pid_t) -> Result<FpRegs, Error> {
218        #[cfg(target_arch = "arm")]
219        {
220            const NT_ARM_VFP: usize = 0x400;
221            self.ptrace_getregset(NT_ARM_VFP, pid)
222        }
223        #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
224        {
225            const NT_PRFPREGSET: usize = 2;
226            self.ptrace_getregset(NT_PRFPREGSET, pid)
227        }
228    }
229
230    fn getfpregs(&self, _pid: libc::pid_t) -> Result<FpRegs, Error> {
231        #[cfg(target_arch = "arm")]
232        {
233            Err(Error::NotSupported)
234        }
235        #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
236        {
237            const PTRACE_GETFPREGS: PtraceRequestType = 14;
238            unsafe { self.ptrace_getregs::<FpRegs>(PTRACE_GETFPREGS, _pid) }
239        }
240    }
241
242    /// Safety: RequestType and T must agree on the size of the returned type
243    unsafe fn ptrace_getregs<T>(
244        &self,
245        request: PtraceRequestType,
246        pid: libc::pid_t,
247    ) -> Result<T, Error> {
248        let mut output = mem::MaybeUninit::<T>::uninit();
249        self.standard_syscall(|| unsafe {
250            ptrace(
251                request,
252                pid,
253                core::ptr::null_mut(),
254                output.as_mut_ptr().cast(),
255            )
256        })
257        .map_err(Error::GetRegistersFailed)?;
258        Ok(unsafe { output.assume_init() })
259    }
260
261    fn ptrace_getregset<T>(&self, regset_type: usize, pid: libc::pid_t) -> Result<T, Error> {
262        let mut output = mem::MaybeUninit::<T>::uninit();
263        let mut io = libc::iovec {
264            iov_base: output.as_mut_ptr().cast(),
265            iov_len: mem::size_of::<T>(),
266        };
267
268        self.standard_syscall(|| unsafe {
269            ptrace(
270                libc::PTRACE_GETREGSET,
271                pid,
272                regset_type as *mut _,
273                (&raw mut io).cast(),
274            )
275        })
276        .map_err(Error::GetRegistersFailed)?;
277
278        // PTRACE_GETREGSET returns the number of bytes actually read in iov_len. Need to ensure
279        // all bytes of T are actually initialized
280        if io.iov_len != mem::size_of::<T>() {
281            Err(Error::GetRegistersFailed(libc::EINVAL))?;
282        }
283
284        Ok(unsafe { output.assume_init() })
285    }
286
287    fn ptrace_detach(&self, tid: libc::pid_t) -> Result<(), Error> {
288        self.standard_syscall(|| unsafe {
289            ptrace(libc::PTRACE_DETACH, tid, ptr::null_mut(), ptr::null_mut())
290        })
291        .map_err(Error::PtraceDetachFailed)?;
292        Ok(())
293    }
294
295    fn standard_syscall<T, F>(&self, f: F) -> Result<T, c_int>
296    where
297        F: FnOnce() -> T,
298        T: From<i8> + core::cmp::PartialEq,
299    {
300        self.syscall_invoker.borrow_mut().invoke_standard(f)
301    }
302
303    fn special_syscall<T, F>(&self, f: F) -> Result<T, c_int>
304    where
305        F: FnOnce() -> Result<T, ()>,
306    {
307        self.syscall_invoker.borrow_mut().invoke(f)
308    }
309
310    #[cfg(feature = "testing")]
311    pub fn fail_one_syscall_with(&self, errno: c_int) {
312        self.syscall_invoker
313            .borrow_mut()
314            .fail_one_syscall_with(errno);
315    }
316}
317
318#[derive(Debug)]
319pub struct FileReader(OwnedFd);
320
321impl FileReader {
322    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
323        let rv = unsafe { libc::read(self.0.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) };
324        if rv == -1 {
325            return Err(Error::ReadFileFailed(errno()));
326        }
327        Ok(rv.try_into().unwrap())
328    }
329    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize, Error> {
330        let rv = unsafe {
331            libc::pread(
332                self.0.as_raw_fd(),
333                buf.as_mut_ptr().cast(),
334                buf.len(),
335                offset.try_into().unwrap(),
336            )
337        };
338        if rv == -1 {
339            return Err(Error::ReadFileFailed(errno()));
340        }
341        Ok(rv.try_into().unwrap())
342    }
343}
344
345#[derive(Debug)]
346pub struct DirReader {
347    dirp: *mut libc::DIR,
348    eof: bool,
349}
350
351impl DirReader {
352    pub fn read_name(&mut self) -> Result<Option<&[u8]>, Error> {
353        if self.eof {
354            return Ok(None);
355        }
356
357        loop {
358            set_errno(0);
359            let dirent = unsafe { libc::readdir(self.dirp) };
360            if dirent.is_null() {
361                if errno() == 0 {
362                    self.eof = true;
363                    return Ok(None);
364                }
365                return Err(Error::ReadDirFailed(errno()));
366            }
367
368            // The dirent structure is not guaranteed to be fully initialized, so it's only safe to
369            // read it through pointers
370            //
371            // SAFETY: the dirent structure is guaranteed to exist until we call readdir() again
372            // or closedir(), which we prevent by holding `&mut self` while `&[u8]` is alive.
373            let name_bytes =
374                unsafe { CStr::from_ptr((&raw const (*dirent).d_name).cast()).to_bytes() };
375
376            if name_bytes == b"." || name_bytes == b".." {
377                continue;
378            }
379
380            return Ok(Some(name_bytes));
381        }
382    }
383}
384
385impl Drop for DirReader {
386    fn drop(&mut self) {
387        let rv = unsafe { libc::closedir(self.dirp) };
388        if rv == -1 {
389            log::debug!("failed to close directory: {}", errno());
390        }
391    }
392}
393
394#[derive(Debug)]
395pub struct ProcessReader(process_reader::ProcessReader);
396
397impl ProcessReader {
398    pub fn read_at(&self, address: usize, buf: &mut [u8]) -> Result<usize, Error> {
399        self.0.read_at(address, buf).map_err(Error::ProcessReader)
400    }
401}
402
403#[derive(Debug)]
404struct OwnedFd(c_int);
405
406impl OwnedFd {
407    // SAFETY: Must be a valid fd
408    pub unsafe fn new(fd: c_int) -> Self {
409        Self(fd)
410    }
411    pub fn as_raw_fd(&self) -> c_int {
412        self.0
413    }
414}
415
416impl Drop for OwnedFd {
417    fn drop(&mut self) {
418        let rv = unsafe { libc::close(self.0) };
419        if rv == -1 {
420            log::error!("failed to close file: {}", errno());
421        }
422    }
423}
424
425/// This is just a typesafe wrapper around ptrace(), which is vararg... But this is Rust, and
426/// playing loosey-goosey with types is really more of a C thing ;)
427unsafe fn ptrace(
428    request: PtraceRequestType,
429    pid: libc::pid_t,
430    addr: *mut c_void,
431    data: *mut c_void,
432) -> c_long {
433    unsafe { libc::ptrace(request, pid, addr, data) }
434}
435
436fn errno() -> c_int {
437    unsafe { *errno_location() }
438}
439
440fn set_errno(value: c_int) {
441    unsafe {
442        *errno_location() = value;
443    }
444}
445
446#[cfg(target_os = "android")]
447fn errno_location() -> *mut c_int {
448    unsafe { libc::__errno() }
449}
450
451#[cfg(not(target_os = "android"))]
452fn errno_location() -> *mut c_int {
453    unsafe { libc::__errno_location() }
454}