memflow_native/linux/
mem.rs

1use memflow::os::process::*;
2use memflow::prelude::v1::*;
3
4use libc::{iovec, pid_t, sysconf, _SC_IOV_MAX};
5use std::ffi::c_void;
6
7#[derive(Clone, Copy)]
8#[repr(transparent)]
9struct IoSendVec(iovec);
10
11unsafe impl Send for IoSendVec {}
12
13#[derive(Clone)]
14pub struct ProcessVirtualMemory {
15    pid: pid_t,
16    temp_iov: Box<[IoSendVec]>,
17    temp_meta: Box<[Address]>,
18}
19
20impl ProcessVirtualMemory {
21    pub fn new(info: &ProcessInfo) -> Self {
22        let iov_max = unsafe { sysconf(_SC_IOV_MAX) } as usize;
23
24        Self {
25            pid: info.pid as pid_t,
26            temp_iov: vec![
27                IoSendVec(iovec {
28                    iov_base: std::ptr::null_mut::<c_void>(),
29                    iov_len: 0
30                });
31                iov_max * 2
32            ]
33            .into_boxed_slice(),
34            temp_meta: vec![Address::INVALID; iov_max].into_boxed_slice(),
35        }
36    }
37
38    fn vm_error() -> Option<ErrorKind> {
39        let ret = match unsafe { *libc::__errno_location() } {
40            libc::EFAULT => return None,
41            libc::EINVAL => ErrorKind::ArgValidation,
42            libc::ENOMEM => return None,
43            libc::EPERM => ErrorKind::NotSupported, // ErrorKind::Permissions
44            libc::ESRCH => ErrorKind::ProcessNotFound,
45            _ => ErrorKind::Unknown,
46        };
47
48        Some(ret)
49    }
50}
51
52// Helper trait for `process_rw` to be generic.
53trait RWSlice: core::ops::Deref<Target = [u8]> {
54    /// Pass the iovecs to appropriate system call.
55    unsafe fn do_rw(
56        pid: pid_t,
57        iov_local: *const iovec,
58        iov_remote: *const iovec,
59        cnt: usize,
60    ) -> isize;
61
62    /// Convert local iovec to itself.
63    unsafe fn from_iovec(liov: iovec) -> Self;
64}
65
66impl<'a> RWSlice for CSliceRef<'a, u8> {
67    unsafe fn do_rw(
68        pid: pid_t,
69        iov_local: *const iovec,
70        iov_remote: *const iovec,
71        cnt: usize,
72    ) -> isize {
73        libc::process_vm_writev(pid, iov_local, cnt as _, iov_remote, cnt as _, 0)
74    }
75
76    unsafe fn from_iovec(liov: iovec) -> Self {
77        #[allow(clippy::unnecessary_cast)]
78        core::slice::from_raw_parts(liov.iov_base as *const _, liov.iov_len as usize).into()
79    }
80}
81
82impl<'a> RWSlice for CSliceMut<'a, u8> {
83    unsafe fn do_rw(
84        pid: pid_t,
85        iov_local: *const iovec,
86        iov_remote: *const iovec,
87        cnt: usize,
88    ) -> isize {
89        libc::process_vm_readv(pid, iov_local, cnt as _, iov_remote, cnt as _, 0)
90    }
91
92    unsafe fn from_iovec(liov: iovec) -> Self {
93        #[allow(clippy::unnecessary_cast)]
94        core::slice::from_raw_parts_mut(liov.iov_base as *mut _, liov.iov_len as usize).into()
95    }
96}
97
98impl ProcessVirtualMemory {
99    /// Generic read/write implementation for linux.
100    fn process_rw<T: RWSlice>(
101        &mut self,
102        MemOps {
103            mut inp,
104            mut out,
105            mut out_fail,
106        }: MemOps<CTup3<Address, Address, T>, CTup2<Address, T>>,
107    ) -> Result<()> {
108        let max_iov = self.temp_iov.len() / 2;
109        let (iov_local, iov_remote) = self.temp_iov.split_at_mut(max_iov);
110
111        let mut iov_iter = iov_local
112            .iter_mut()
113            .zip(iov_remote.iter_mut().zip(self.temp_meta.iter_mut()))
114            .enumerate();
115        let mut iov_next = iov_iter.next();
116
117        let mut elem = inp.next();
118
119        'exit: while let Some(CTup3(a, m, b)) = elem {
120            let (cnt, (liov, (riov, meta))) = iov_next.unwrap();
121
122            let iov_len = b.len();
123
124            liov.0 = iovec {
125                iov_base: b.as_ptr() as *mut c_void,
126                iov_len,
127            };
128
129            riov.0 = iovec {
130                iov_base: a.to_umem() as *mut c_void,
131                iov_len,
132            };
133
134            *meta = m;
135
136            iov_next = iov_iter.next();
137            elem = inp.next();
138
139            if elem.is_none() || iov_next.is_none() {
140                let mut offset = 0;
141
142                // Process all iovecs, but skip one by one if we get partial results
143                loop {
144                    let cnt = cnt + 1 - offset;
145
146                    if cnt == 0 {
147                        break;
148                    }
149
150                    let libcret = unsafe {
151                        T::do_rw(
152                            self.pid,
153                            iov_local.as_ptr().add(offset).cast(),
154                            iov_remote.as_ptr().add(offset).cast(),
155                            cnt,
156                        )
157                    };
158
159                    let vm_err = if libcret == -1 {
160                        Self::vm_error()
161                    } else {
162                        None
163                    };
164
165                    match vm_err {
166                        Some(err) => return Err(Error(ErrorOrigin::OsLayer, err)),
167                        _ => {
168                            let mut remaining_written = if libcret == -1 {
169                                0
170                            } else {
171                                libcret as usize + 1
172                            };
173
174                            for (liof, (_, meta)) in iov_local
175                                .iter()
176                                .take(cnt)
177                                .zip(iov_remote.iter().zip(self.temp_meta.iter()))
178                            {
179                                offset += 1;
180                                let to_write = remaining_written;
181
182                                remaining_written =
183                                    remaining_written.saturating_sub(liof.0.iov_len);
184
185                                if to_write > 0 {
186                                    if !opt_call(
187                                        out.as_deref_mut(),
188                                        CTup2(*meta, unsafe { T::from_iovec(liof.0) }),
189                                    ) {
190                                        break 'exit;
191                                    }
192                                } else {
193                                    // This will take only the first unread element and write it to the
194                                    // failed list, because it could be that only it is invalid.
195                                    if !opt_call(
196                                        out_fail.as_deref_mut(),
197                                        CTup2(*meta, unsafe { T::from_iovec(liof.0) }),
198                                    ) {
199                                        break 'exit;
200                                    }
201                                    break;
202                                }
203                            }
204                        }
205                    }
206                }
207
208                iov_iter = iov_local
209                    .iter_mut()
210                    .zip(iov_remote.iter_mut().zip(self.temp_meta.iter_mut()))
211                    .enumerate();
212                iov_next = iov_iter.next();
213            }
214        }
215
216        Ok(())
217    }
218}
219
220impl MemoryView for ProcessVirtualMemory {
221    fn read_raw_iter<'a>(&mut self, data: ReadRawMemOps) -> Result<()> {
222        self.process_rw(data)
223    }
224
225    fn write_raw_iter<'a>(&mut self, data: WriteRawMemOps) -> Result<()> {
226        self.process_rw(data)
227    }
228
229    fn metadata(&self) -> MemoryViewMetadata {
230        MemoryViewMetadata {
231            arch_bits: if cfg!(target_pointer_width = "64") {
232                64
233            } else {
234                32
235            },
236            little_endian: cfg!(target_endian = "little"),
237            max_address: Address::invalid(),
238            readonly: false,
239            real_size: 0,
240        }
241    }
242}