ptrace_syscalls/
inspect.rs

1use std::{
2  ffi::{CString, OsString},
3  mem::{size_of, MaybeUninit},
4  ops::Not,
5  os::{raw::c_void, unix::prelude::OsStringExt},
6  path::PathBuf,
7  sync::atomic::{AtomicBool, Ordering},
8};
9
10use nix::{
11  errno::Errno,
12  libc::{
13    c_long, c_ulong, clone_args, epoll_event, fd_set, iocb, iovec, itimerspec, itimerval, memcpy, mmsghdr, mq_attr,
14    msghdr, msqid_ds, open_how, pollfd, rlimit, rlimit64, rusage, sched_attr, sched_param, sembuf, shmid_ds, sigaction,
15    sigevent, siginfo_t, sigset_t, sockaddr, stack_t, stat, statfs, statx, sysinfo, timespec, timeval, timex, tms,
16    utimbuf, utsname,
17  },
18  sys::ptrace::{self, AddressType},
19  unistd::{sysconf, Pid, SysconfVar},
20};
21use once_cell::sync::OnceCell;
22
23use crate::{
24  arch::PtraceRegisters,
25  types::{
26    __aio_sigset, __mount_arg, cachestat, cachestat_range, cap_user_data, cap_user_header, futex_waitv, io_event,
27    io_uring_params, kexec_segment, landlock_ruleset_attr, linux_dirent, linux_dirent64, mnt_id_req, mount_attr,
28    timezone, ustat,
29  },
30};
31
32pub fn ptrace_getregs(pid: Pid) -> Result<PtraceRegisters, Errno> {
33  // Don't use GETREGSET on x86_64.
34  // In some cases(it usually happens several times at and after exec syscall exit),
35  // we only got 68/216 bytes into `regs`, which seems unreasonable. Not sure why.
36  cfg_if::cfg_if! {
37      if #[cfg(target_arch = "x86_64")] {
38          ptrace::getregs(pid)
39      } else {
40          // https://github.com/torvalds/linux/blob/v6.9/include/uapi/linux/elf.h#L378
41          // libc crate doesn't provide this constant when using musl libc.
42          const NT_PRSTATUS: std::ffi::c_int	= 1;
43
44          use nix::sys::ptrace::AddressType;
45
46          let mut regs = std::mem::MaybeUninit::<PtraceRegisters>::uninit();
47          let iovec = nix::libc::iovec {
48              iov_base: regs.as_mut_ptr() as AddressType,
49              iov_len: std::mem::size_of::<PtraceRegisters>(),
50          };
51          let ptrace_result = unsafe {
52              nix::libc::ptrace(
53                  nix::libc::PTRACE_GETREGSET,
54                  pid.as_raw(),
55                  NT_PRSTATUS,
56                  &iovec as *const _ as *const nix::libc::c_void,
57              )
58          };
59          let regs = if -1 == ptrace_result {
60              let errno = nix::errno::Errno::last();
61              return Err(errno);
62          } else {
63              assert_eq!(iovec.iov_len, std::mem::size_of::<PtraceRegisters>());
64              unsafe { regs.assume_init() }
65          };
66          Ok(regs)
67      }
68  }
69}
70
71static PAGE_SIZE: OnceCell<usize> = OnceCell::new();
72static SHOULD_USE_PROCESS_VM_READV: AtomicBool = AtomicBool::new(true);
73
74/// Read a remote memory buffer and put it into dest.
75///
76/// # Safety
77///
78/// The caller must ensure that the dest buffer is large enough to hold the data.
79pub unsafe fn read_remote_memory(
80  pid: Pid,
81  remote_addr: AddressType,
82  len: usize,
83  dest: AddressType,
84) -> Result<usize, Errno> {
85  // if the length is less than 2 words, use ptrace peek
86  // TODO: This is heuristic and a benchmark is needed to determine the threshold.
87  if len < WORD_SIZE * 2 {
88    read_by_ptrace_peek(pid, remote_addr, len, dest)
89  } else if SHOULD_USE_PROCESS_VM_READV.load(Ordering::Relaxed) {
90    let result = read_by_process_vm_readv(pid, remote_addr, len, dest);
91    result
92      .map_err(|e| {
93        SHOULD_USE_PROCESS_VM_READV.store(false, Ordering::SeqCst);
94        e
95      })
96      .or_else(|_| read_by_ptrace_peek(pid, remote_addr, len, dest))
97  } else {
98    read_by_ptrace_peek(pid, remote_addr, len, dest)
99  }
100}
101
102/// Read a remote memory buffer by ptrace peek and put it into dest.
103unsafe fn read_by_ptrace_peek(
104  pid: Pid,
105  mut remote_addr: AddressType,
106  mut len: usize,
107  mut dest: AddressType,
108) -> Result<usize, Errno> {
109  // Check for address overflow.
110  if (remote_addr as usize).checked_add(len).is_none() {
111    return Err(Errno::EFAULT);
112  }
113  let mut total_read = 0;
114  let align_bytes = (remote_addr as usize) & (WORD_SIZE - 1);
115  if align_bytes != 0 {
116    let aligned_addr = ((remote_addr as usize) & (WORD_SIZE - 1).not()) as AddressType;
117    let word = ptrace::read(pid, aligned_addr)?;
118    let copy_len = len.min(remote_addr as usize - align_bytes);
119    memcpy(dest, (&word as *const c_long as *const c_void).byte_add(align_bytes), copy_len);
120    remote_addr = remote_addr.byte_add(copy_len);
121    len -= copy_len;
122    total_read += copy_len;
123    dest = dest.byte_add(copy_len);
124  }
125
126  for _ in 0..(len / WORD_SIZE) {
127    let word = ptrace::read(pid, remote_addr)?;
128    memcpy(dest, &word as *const c_long as *const c_void, WORD_SIZE);
129    dest = dest.byte_add(WORD_SIZE);
130    remote_addr = remote_addr.byte_add(WORD_SIZE);
131    total_read += WORD_SIZE;
132  }
133
134  let left_over = len & (WORD_SIZE - 1);
135  if left_over > 0 {
136    let word = ptrace::read(pid, remote_addr)?;
137    memcpy(dest, &word as *const c_long as *const c_void, left_over);
138    total_read += left_over;
139  }
140  Ok(total_read)
141}
142
143/// Read a remote memory buffer by process_vm_readv and put it into dest.
144unsafe fn read_by_process_vm_readv(
145  pid: Pid,
146  remote_addr: AddressType,
147  mut len: usize,
148  dest: AddressType,
149) -> Result<usize, Errno> {
150  // liovcnt and riovcnt must be <= IOV_MAX
151  const IOV_MAX: usize = nix::libc::_SC_IOV_MAX as usize;
152  let mut riovs = [MaybeUninit::<nix::libc::iovec>::uninit(); IOV_MAX];
153  let mut cur = remote_addr;
154  let mut total_read = 0;
155  while len > 0 {
156    let dst_iov = iovec {
157      iov_base: dest.byte_add(total_read),
158      iov_len: len,
159    };
160    let mut riov_used = 0;
161    while len > 0 {
162      if riov_used == IOV_MAX {
163        break;
164      }
165
166      // struct iovec uses void* for iov_base.
167      if cur >= usize::MAX as AddressType {
168        return Err(Errno::EFAULT);
169      }
170      riovs[riov_used].assume_init_mut().iov_base = cur;
171      let page_size = *PAGE_SIZE.get_or_init(|| {
172        sysconf(SysconfVar::PAGE_SIZE)
173          .expect("Failed to get page size")
174          .unwrap() as usize
175      });
176      let misalignment = (cur as usize) & (page_size - 1);
177      let iov_len = (page_size - misalignment).min(len);
178      len -= iov_len;
179      // pointer types don't have checked_add ???
180      cur = (cur as usize).checked_add(iov_len).ok_or(Errno::EFAULT)? as AddressType;
181      riovs[riov_used].assume_init_mut().iov_len = iov_len;
182      riov_used += 1;
183    }
184    let read = nix::libc::process_vm_readv(
185      pid.into(),
186      &dst_iov as *const _,
187      1,
188      &riovs as *const _ as *const iovec,
189      riov_used as c_ulong,
190      0,
191    );
192    if read == -1 {
193      return Err(Errno::last());
194    }
195    total_read += read as usize;
196  }
197  Ok(total_read)
198}
199
200#[derive(Debug, Clone, PartialEq)]
201pub enum InspectError<T: Clone + PartialEq> {
202  /// The syscall failed thus the sysexit-stop inspection is not done.
203  SyscallFailure,
204  /// failed when trying to inspect the tracee memory.
205  ReadFailure { errno: Errno, incomplete: Option<T> },
206  /// A dependency inspection of this inspection failed.
207  DependencyInspectFailure { field: &'static str },
208}
209
210pub type InspectResult<T> = Result<T, InspectError<T>>;
211
212impl<T: Clone + PartialEq> InspectError<T> {
213  pub fn map_ptrace_failure<U: Clone + PartialEq, F: FnOnce(T) -> U>(self, f: F) -> InspectError<U> {
214    match self {
215      InspectError::SyscallFailure => InspectError::SyscallFailure,
216      InspectError::ReadFailure { errno, incomplete } => InspectError::ReadFailure {
217        errno,
218        incomplete: incomplete.map(f),
219      },
220      InspectError::DependencyInspectFailure { field } => InspectError::DependencyInspectFailure { field },
221    }
222  }
223}
224
225/// Inspect the arguments and results on sysenter/sysexit stops based on register values captured on sysenter.
226pub trait SyscallStopInspect: Copy {
227  type Args;
228  type Result;
229  fn inspect_sysenter(self, inspectee_pid: Pid) -> Self::Args;
230  fn inspect_sysexit(self, inspectee_pid: Pid, regs: &PtraceRegisters) -> Self::Result;
231}
232
233/// Marker trait for sized repr(C) structs
234///
235/// # Safety
236///
237/// This trait should only be implemented for Sized repr(C) structs. Implementing this trait for other types will lead to undefined behavior.
238pub(crate) unsafe trait ReprCMarker {}
239
240macro_rules! impl_marker {
241  ($marker:ty => $($ty:ty),*) => {
242    $(unsafe impl $marker for $ty {})*
243  };
244}
245
246impl_marker! {
247  ReprCMarker =>
248  u8, i32, u32, i64, u64, sockaddr, timex, cap_user_data, cap_user_header, timespec, stack_t, mnt_id_req,
249  shmid_ds, cachestat, cachestat_range, statx, utimbuf, ustat, utsname, itimerspec, tms,
250  sysinfo, clone_args, AddressType, sched_attr, sembuf, sched_param, sigaction, epoll_event, stat,
251  statfs, futex_waitv, itimerval, iocb, __aio_sigset, io_uring_params, io_event, kexec_segment,
252  rlimit, rusage, timezone, linux_dirent, linux_dirent64, landlock_ruleset_attr, __mount_arg,
253  timeval, mount_attr, mq_attr, iovec, rlimit64, siginfo_t, pollfd, fd_set, open_how, msqid_ds,
254  sigevent, mmsghdr, msghdr, sigset_t
255}
256
257/// Use ptrace to inspect the process with the given pid and return the inspection result.
258pub(crate) trait InspectFromPid {
259  fn inspect_from(pid: Pid, address: AddressType) -> Self;
260}
261
262/// Use ptrace to inspect the process with the given pid and return the inspection result.
263pub(crate) trait InspectCountedFromPid {
264  fn inspect_from(pid: Pid, address: AddressType, count: usize) -> Self;
265}
266
267/// Use ptrace to inspect the process with the given pid and return the inspection result.
268pub(crate) trait InspectDynSizedFromPid {
269  fn inspect_from(pid: Pid, address: AddressType, size: usize) -> Self;
270}
271
272const WORD_SIZE: usize = size_of::<c_long>();
273
274impl<T: Clone + PartialEq + ReprCMarker> InspectFromPid for InspectResult<T> {
275  fn inspect_from(pid: Pid, address: AddressType) -> Self {
276    let mut buf = MaybeUninit::<T>::uninit();
277    unsafe {
278      read_remote_memory(pid, address, size_of::<T>(), buf.as_mut_ptr() as AddressType).map_err(|errno| {
279        InspectError::ReadFailure {
280          errno,
281          incomplete: None,
282        }
283      })?;
284      Ok(buf.assume_init())
285    }
286  }
287}
288
289impl InspectFromPid for InspectResult<CString> {
290  fn inspect_from(pid: Pid, address: AddressType) -> Self {
291    read_cstring(pid, address)
292  }
293}
294
295fn read_generic_string<TString: Clone + PartialEq>(
296  pid: Pid,
297  address: AddressType,
298  ctor: impl Fn(Vec<u8>) -> TString,
299) -> InspectResult<TString> {
300  let mut buf = Vec::new();
301  let mut address = address;
302  loop {
303    let word = match ptrace::read(pid, address) {
304      Err(e) => {
305        return Err(InspectError::ReadFailure {
306          errno: e,
307          incomplete: Some(ctor(buf)),
308        });
309      }
310      Ok(word) => word,
311    };
312    let word_bytes = word.to_ne_bytes();
313    for &byte in word_bytes.iter() {
314      if byte == 0 {
315        return Ok(ctor(buf));
316      }
317      buf.push(byte);
318    }
319    address = unsafe { address.add(WORD_SIZE) };
320  }
321}
322
323#[allow(unused)]
324fn read_cstring(pid: Pid, address: AddressType) -> InspectResult<CString> {
325  read_generic_string(pid, address, |x| CString::new(x).unwrap())
326}
327
328fn read_pathbuf(pid: Pid, address: AddressType) -> InspectResult<PathBuf> {
329  read_generic_string(pid, address, |x| PathBuf::from(OsString::from_vec(x)))
330}
331
332fn read_lossy_string(pid: Pid, address: AddressType) -> InspectResult<String> {
333  // Waiting on https://github.com/rust-lang/libs-team/issues/116
334  read_generic_string(pid, address, |x| String::from_utf8_lossy(&x).into_owned())
335}
336
337fn read_null_ended_array<TItem: Clone + PartialEq>(pid: Pid, mut address: AddressType) -> InspectResult<Vec<TItem>>
338where
339  InspectResult<TItem>: InspectFromPid,
340{
341  let mut res = Vec::new();
342  const WORD_SIZE: usize = size_of::<c_long>();
343  loop {
344    let ptr = match ptrace::read(pid, address) {
345      Err(errno) => {
346        return Err(InspectError::ReadFailure {
347          errno,
348          incomplete: Some(res),
349        });
350      }
351      Ok(ptr) => ptr,
352    };
353    if ptr == 0 {
354      return Ok(res);
355    } else {
356      match InspectResult::<TItem>::inspect_from(pid, ptr as AddressType) {
357        Ok(item) => res.push(item),
358        Err(e) => return Err(e.map_ptrace_failure(|_| res)),
359      };
360    }
361    address = unsafe { address.add(WORD_SIZE) };
362  }
363}
364
365impl InspectFromPid for InspectResult<PathBuf> {
366  fn inspect_from(pid: Pid, address: AddressType) -> Self {
367    read_pathbuf(pid, address)
368  }
369}
370
371#[cfg(target_arch = "x86_64")]
372impl_marker! {
373  ReprCMarker => crate::types::user_desc
374}
375
376#[cfg(target_arch = "riscv64")]
377impl_marker! {
378  ReprCMarker => crate::types::riscv_hwprobe
379}
380
381// TODO: speed up the read of Vec<u8>
382// FIXME: some Vec are not null-terminated
383impl InspectFromPid for InspectResult<Vec<u8>> {
384  fn inspect_from(pid: Pid, address: AddressType) -> Self {
385    read_null_ended_array::<u8>(pid, address)
386  }
387}
388
389impl InspectFromPid for InspectResult<Vec<CString>> {
390  fn inspect_from(pid: Pid, address: AddressType) -> Self {
391    read_null_ended_array::<CString>(pid, address)
392  }
393}
394
395impl<T: Clone + PartialEq> InspectCountedFromPid for InspectResult<Vec<T>>
396where
397  InspectResult<T>: InspectFromPid,
398{
399  fn inspect_from(pid: Pid, address: AddressType, count: usize) -> Self {
400    let mut res = Vec::with_capacity(count);
401    for i in 0..count {
402      let item_address = unsafe { address.byte_add(i * size_of::<T>()) };
403      let item = match InspectResult::<T>::inspect_from(pid, item_address) {
404        Ok(item) => item,
405        Err(e) => {
406          return Err(e.map_ptrace_failure(|incomplete| {
407            res.push(incomplete);
408            res
409          }));
410        }
411      };
412      res.push(item);
413    }
414    Ok(res)
415  }
416}
417
418impl<T: Clone + PartialEq> InspectFromPid for Result<[T; 2], InspectError<Vec<T>>>
419where
420  InspectResult<T>: InspectFromPid,
421{
422  fn inspect_from(pid: Pid, address: AddressType) -> Self {
423    let item1 = InspectResult::<T>::inspect_from(pid, address)
424      .map_err(|e| e.map_ptrace_failure(|incomplete| vec![incomplete]))?;
425    let item2 = match InspectResult::<T>::inspect_from(pid, unsafe { address.add(size_of::<T>()) }) {
426      Ok(t) => t,
427      Err(e) => return Err(e.map_ptrace_failure(|incomplete| vec![item1, incomplete])),
428    };
429    Ok([item1, item2])
430  }
431}
432
433impl<T: Clone + PartialEq + ReprCMarker> InspectFromPid for InspectResult<Option<T>> {
434  fn inspect_from(pid: Pid, address: AddressType) -> Self {
435    if address.is_null() {
436      Ok(None)
437    } else {
438      Ok(Some(InspectResult::<T>::inspect_from(pid, address).map_err(|e| e.map_ptrace_failure(Some))?))
439    }
440  }
441}
442
443macro_rules! impl_inspect_from_pid_for_option {
444  ($($ty:ty),*) => {
445    $(
446      impl InspectFromPid for InspectResult<Option<$ty>> {
447        fn inspect_from(pid: Pid, address: AddressType) -> Self {
448          if address.is_null() {
449            Ok(None)
450          } else {
451            Ok(Some(
452              <InspectResult::<$ty> as InspectFromPid>::inspect_from(pid, address).map_err(|e| e.map_ptrace_failure(Some))?,
453            ))
454          }
455        }
456      }
457    )*
458  };
459}
460
461macro_rules! impl_inspect_counted_from_pid_for_option {
462  ($($ty:ty),*) => {
463    $(
464      impl InspectCountedFromPid for InspectResult<Option<$ty>> {
465        fn inspect_from(pid: Pid, address: AddressType, count: usize) -> Self {
466          if address.is_null() {
467            Ok(None)
468          } else {
469            Ok(Some(
470              <InspectResult::<$ty> as InspectCountedFromPid>::inspect_from(pid, address, count).map_err(|e| e.map_ptrace_failure(Some))?,
471            ))
472          }
473        }
474      }
475    )*
476  };
477}
478
479impl_inspect_from_pid_for_option! {
480  PathBuf, CString, Vec<CString>
481}
482
483impl_inspect_counted_from_pid_for_option! {
484  Vec<u64>, Vec<u32>
485}
486
487impl<T: Clone + PartialEq> InspectFromPid for Result<Option<[T; 2]>, InspectError<Vec<T>>>
488where
489  Result<[T; 2], InspectError<Vec<T>>>: InspectFromPid,
490{
491  fn inspect_from(pid: Pid, address: AddressType) -> Self {
492    if address.is_null() {
493      Ok(None)
494    } else {
495      Ok(Some(
496        Result::<[T; 2], InspectError<Vec<T>>>::inspect_from(pid, address)
497          .map_err(|e| e.map_ptrace_failure(|incomplete| incomplete))?,
498      ))
499    }
500  }
501}