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 cfg_if::cfg_if! {
37 if #[cfg(target_arch = "x86_64")] {
38 ptrace::getregs(pid)
39 } else {
40 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
74pub unsafe fn read_remote_memory(
80 pid: Pid,
81 remote_addr: AddressType,
82 len: usize,
83 dest: AddressType,
84) -> Result<usize, Errno> {
85 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
102unsafe 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 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
143unsafe 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 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 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 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 SyscallFailure,
204 ReadFailure { errno: Errno, incomplete: Option<T> },
206 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
225pub 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
233pub(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
257pub(crate) trait InspectFromPid {
259 fn inspect_from(pid: Pid, address: AddressType) -> Self;
260}
261
262pub(crate) trait InspectCountedFromPid {
264 fn inspect_from(pid: Pid, address: AddressType, count: usize) -> Self;
265}
266
267pub(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 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
381impl 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}