Skip to main content

shadow_rs/host/memory_manager/
mod.rs

1//! Access and manage memory of a plugin process.
2//!
3//! The starting point for the public API is [`MemoryManager`].
4//! [`MemoryManager`] can be used to:
5//!
6//! * Directly read or write process memory
7//! * Obtain cursors to process memory implementing `std::io::Seek` and either
8//!   `std::io::Read` or `std::io::Write` ([`MemoryReaderCursor`] and
9//!   [`MemoryWriterCursor`])
10
11use std::fmt::Debug;
12use std::mem::MaybeUninit;
13use std::os::raw::c_void;
14
15use linux_api::errno::Errno;
16use linux_api::mman::{MapFlags, ProtFlags};
17use linux_api::posix_types::Pid;
18use log::*;
19use memory_copier::MemoryCopier;
20use shadow_pod::Pod;
21use shadow_shim_helper_rs::syscall_types::ForeignPtr;
22
23use super::context::ThreadContext;
24use crate::host::syscall::types::{ForeignArrayPtr, SyscallError};
25
26mod memory_copier;
27
28/// An object implementing std::io::Read and std::io::Seek for
29/// a range of plugin memory.
30pub struct MemoryReaderCursor<'a> {
31    memory_manager: &'a MemoryManager,
32    ptr: ForeignArrayPtr<u8>,
33    offset: usize,
34}
35
36impl std::io::Read for MemoryReaderCursor<'_> {
37    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
38        let ptr = self.ptr.slice(self.offset..);
39        let toread = std::cmp::min(buf.len(), ptr.len());
40        if toread == 0 {
41            return Ok(0);
42        }
43        self.memory_manager
44            .copy_from_ptr(&mut buf[..toread], ptr.slice(..toread))?;
45        self.offset += toread;
46        Ok(toread)
47    }
48}
49
50/// Shared implementation of seek for both MemoryReaderCursor and MemoryWriterCursor.
51fn seek_helper(offset: &mut usize, len: usize, pos: std::io::SeekFrom) -> std::io::Result<u64> {
52    use std::io::SeekFrom;
53    let new_offset = match pos {
54        SeekFrom::Current(x) => *offset as i64 + x,
55        SeekFrom::End(x) => len as i64 + x,
56        SeekFrom::Start(x) => x as i64,
57    };
58    // Seeking before the beginning is an error (but seeking to or past the
59    // end isn't).
60    if new_offset < 0 {
61        return Err(Errno::EFAULT.into());
62    }
63    *offset = new_offset as usize;
64    Ok(new_offset as u64)
65}
66
67impl std::io::Seek for MemoryReaderCursor<'_> {
68    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
69        seek_helper(&mut self.offset, self.ptr.len(), pos)
70    }
71}
72
73/// An object implementing std::io::Write and std::io::Seek for
74/// a range of plugin memory.
75pub struct MemoryWriterCursor<'a> {
76    memory_manager: &'a mut MemoryManager,
77    ptr: ForeignArrayPtr<u8>,
78    offset: usize,
79}
80
81impl std::io::Write for MemoryWriterCursor<'_> {
82    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
83        let ptr = self.ptr.slice(self.offset..);
84        let towrite = std::cmp::min(buf.len(), ptr.len());
85        if towrite == 0 {
86            return Ok(0);
87        }
88        self.memory_manager
89            .copy_to_ptr(ptr.slice(..towrite), &buf[..towrite])?;
90        self.offset += towrite;
91        Ok(towrite)
92    }
93
94    fn flush(&mut self) -> std::io::Result<()> {
95        Ok(())
96    }
97}
98
99impl std::io::Seek for MemoryWriterCursor<'_> {
100    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
101        seek_helper(&mut self.offset, self.ptr.len(), pos)
102    }
103}
104
105fn page_size() -> usize {
106    nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)
107        .unwrap()
108        .unwrap()
109        .try_into()
110        .unwrap()
111}
112
113/// Provides accessors for reading and writing another process's memory.
114///
115/// When in use, any operation that touches that process's memory must go
116/// through the MemoryManager to ensure soundness. See MemoryManager::new.
117//
118// The MemoryManager is the Rust representation of a plugin process's address
119// space.  For every access it tries to go through the more-efficient
120// MemoryMapper helper first, and falls back to the MemoryCopier if it hasn't
121// been initialized yet, or the access isn't contained entirely within a region
122// that's been remapped.
123#[derive(Debug)]
124pub struct MemoryManager {
125    // Memory accessor that works by copying data to and from process memory.
126    // This is the most robust mechanism, but requires some syscalls, and in
127    // some cases extra copies of the referenced data.
128    memory_copier: MemoryCopier,
129
130    // Native pid of the plugin process.
131    pid: Pid,
132}
133
134impl MemoryManager {
135    pub fn new(pid: Pid) -> Self {
136        Self {
137            pid,
138            memory_copier: MemoryCopier::new(pid),
139        }
140    }
141
142    /// Copy data from the beginning of the given
143    /// pointer to the last address in the pointer that's accessible. Useful for
144    /// accessing string data of unknown size.
145    ///
146    /// The returned Vec will have capacity for the full `ptr`; caller might
147    /// want to call `shrink_to_fit` if it will be long-lived.
148    pub fn read_prefix<T: Pod>(&self, ptr: ForeignArrayPtr<T>) -> Result<Vec<T>, Errno> {
149        let ptr = ptr.cast::<MaybeUninit<T>>().unwrap();
150        let mut values = Vec::with_capacity(ptr.len());
151        let copied = self
152            .memory_copier
153            .copy_prefix_from_ptr(values.spare_capacity_mut(), ptr)?;
154        // SAFETY: we just initialized these bytes.
155        unsafe { values.set_len(copied) };
156        Ok(values)
157    }
158
159    /// Creates a std::io::Read accessor for the specified plugin memory. Useful
160    /// for handing off the ability to read process memory to non-Shadow APIs,
161    /// without copying it to local memory first.
162    pub fn reader(&self, ptr: ForeignArrayPtr<u8>) -> MemoryReaderCursor<'_> {
163        MemoryReaderCursor {
164            memory_manager: self,
165            ptr,
166            offset: 0,
167        }
168    }
169
170    /// Reads the memory into a local copy.
171    ///
172    /// Examples:
173    ///
174    /// ```no_run
175    /// # use shadow_shim_helper_rs::syscall_types::ForeignPtr;
176    /// # use shadow_rs::host::memory_manager::MemoryManager;
177    /// # use linux_api::errno::Errno;
178    /// # fn foo() -> Result<(), Errno> {
179    /// # let memory_manager: MemoryManager = todo!();
180    /// let ptr: ForeignPtr<u32> = todo!();
181    /// let val: u32 = memory_manager.read(ptr)?;
182    /// # Ok(())
183    /// # }
184    /// ```
185    ///
186    /// ```no_run
187    /// # use shadow_shim_helper_rs::syscall_types::ForeignPtr;
188    /// # use shadow_rs::host::memory_manager::MemoryManager;
189    /// # use linux_api::errno::Errno;
190    /// # fn foo() -> Result<(), Errno> {
191    /// # let memory_manager: MemoryManager = todo!();
192    /// let ptr: ForeignPtr<[u32; 2]> = todo!();
193    /// let val: [u32; 2] = memory_manager.read(ptr)?;
194    /// # Ok(())
195    /// # }
196    /// ```
197    pub fn read<T: Pod>(&self, ptr: ForeignPtr<T>) -> Result<T, Errno> {
198        let ptr = ptr.cast::<MaybeUninit<T>>();
199        let mut res: MaybeUninit<T> = MaybeUninit::uninit();
200
201        self.copy_from_ptr(std::slice::from_mut(&mut res), ForeignArrayPtr::new(ptr, 1))?;
202        // SAFETY: any values are valid for Pod
203        Ok(unsafe { res.assume_init() })
204    }
205
206    /// Read an array of `T` into a `Vec`. If you already have storage allocated,
207    /// consider `copy_from_ptr` instead.
208    pub fn read_vec<T: Pod>(&self, ptr: ForeignArrayPtr<T>) -> Result<Vec<T>, Errno> {
209        let mut values = Box::<[T]>::new_uninit_slice(ptr.len());
210        let ptr = ptr.cast::<MaybeUninit<T>>().unwrap();
211        self.copy_from_ptr(&mut values, ptr)?;
212        // SAFETY: we've initialized the data.
213        let value = unsafe { values.assume_init() };
214        Ok(Vec::from(value))
215    }
216
217    /// Writes a local value `val` into the memory at `ptr`.
218    ///
219    /// ```no_run
220    /// # use shadow_shim_helper_rs::syscall_types::ForeignPtr;
221    /// # use shadow_rs::host::memory_manager::MemoryManager;
222    /// # use linux_api::errno::Errno;
223    /// # fn foo() -> Result<(), Errno> {
224    /// # let mut memory_manager: MemoryManager = todo!();
225    /// let ptr: ForeignPtr<u32> = todo!();
226    /// let val = 5;
227    /// memory_manager.write(ptr, &val)?;
228    /// # Ok(())
229    /// # }
230    /// ```
231    // take a `&T` rather than a `T` since all `Pod` types are `Copy`, and it's probably more
232    // performant to accept a reference than copying the type here if `T` is large
233    pub fn write<T: Pod>(&mut self, ptr: ForeignPtr<T>, val: &T) -> Result<(), Errno> {
234        self.copy_to_ptr(ForeignArrayPtr::new(ptr, 1), std::slice::from_ref(val))
235    }
236
237    /// Similar to `read`, but saves a copy if you already have a `dst` to copy the data into.
238    pub fn copy_from_ptr<T: Pod>(
239        &self,
240        dst: &mut [T],
241        src: ForeignArrayPtr<T>,
242    ) -> Result<(), Errno> {
243        self.memory_copier.copy_from_ptr(dst, src)
244    }
245
246    // Copies memory from the beginning of the given pointer to the last address
247    // in the pointer that's accessible. Not exposed as a public interface
248    // because this is generally only useful for strings, and
249    // `copy_str_from_ptr` provides a more convenient interface.
250    fn copy_prefix_from_ptr<T: Pod>(
251        &self,
252        buf: &mut [T],
253        ptr: ForeignArrayPtr<T>,
254    ) -> Result<usize, Errno> {
255        self.memory_copier.copy_prefix_from_ptr(buf, ptr)
256    }
257
258    /// Copies a NULL-terminated string starting from the beginning of `src` and
259    /// contained completely within `src`. Still works if some of `src` isn't
260    /// readable, as long as a NULL-terminated-string is contained in the
261    /// readable prefix.
262    ///
263    /// If holding a reference to the MemoryManager for the lifetime of the
264    /// string is acceptable, use `memory_ref_prefix` and
265    /// `ProcessMemoryRef::get_str` to potentially avoid an extra copy.
266    pub fn copy_str_from_ptr<'a>(
267        &self,
268        dst: &'a mut [u8],
269        src: ForeignArrayPtr<u8>,
270    ) -> Result<&'a std::ffi::CStr, Errno> {
271        let nread = self.copy_prefix_from_ptr(dst, src)?;
272        let dst = &dst[..nread];
273        std::ffi::CStr::from_bytes_until_nul(dst).or(Err(Errno::ENAMETOOLONG))
274    }
275
276    /// Writes the memory from a local copy. If `src` doesn't already exist,
277    /// using `memory_ref_mut_uninit` and initializing the data in that
278    /// reference saves a copy.
279    pub fn copy_to_ptr<T: Pod>(&mut self, dst: ForeignArrayPtr<T>, src: &[T]) -> Result<(), Errno> {
280        self.memory_copier.copy_to_ptr(dst, src)
281    }
282
283    /// Which process's address space this MemoryManager manages.
284    pub fn pid(&self) -> Pid {
285        self.pid
286    }
287
288    /// Create a write accessor for the specified plugin memory.
289    pub fn writer(&mut self, ptr: ForeignArrayPtr<u8>) -> MemoryWriterCursor<'_> {
290        MemoryWriterCursor {
291            memory_manager: self,
292            ptr,
293            offset: 0,
294        }
295    }
296
297    pub fn handle_brk(
298        &mut self,
299        _ctx: &ThreadContext,
300        _ptr: ForeignPtr<u8>,
301    ) -> Result<ForeignPtr<u8>, SyscallError> {
302        Err(SyscallError::Native)
303    }
304
305    pub fn do_mmap(
306        &mut self,
307        ctx: &ThreadContext,
308        addr: ForeignPtr<u8>,
309        length: usize,
310        prot: ProtFlags,
311        flags: MapFlags,
312        fd: i32,
313        offset: i64,
314    ) -> Result<ForeignPtr<u8>, Errno> {
315        let addr = {
316            let (ctx, thread) = ctx.split_thread();
317            thread.native_mmap(&ctx, addr, length, prot, flags, fd, offset)?
318        };
319        Ok(addr)
320    }
321
322    pub fn handle_munmap(
323        &mut self,
324        _ctx: &ThreadContext,
325        _addr: ForeignPtr<u8>,
326        _length: usize,
327    ) -> Result<(), SyscallError> {
328        // We don't need to know the result, and it's more efficient to let
329        // the original syscall complete than to do it ourselves.
330        Err(SyscallError::Native)
331    }
332
333    fn do_munmap(
334        &mut self,
335        ctx: &ThreadContext,
336        addr: ForeignPtr<u8>,
337        length: usize,
338    ) -> Result<(), Errno> {
339        let (ctx, thread) = ctx.split_thread();
340        thread.native_munmap(&ctx, addr, length)?;
341        Ok(())
342    }
343
344    pub fn handle_mremap(
345        &mut self,
346        _ctx: &ThreadContext,
347        _old_address: ForeignPtr<u8>,
348        _old_size: usize,
349        _new_size: usize,
350        _flags: i32,
351        _new_address: ForeignPtr<u8>,
352    ) -> Result<ForeignPtr<u8>, SyscallError> {
353        Err(SyscallError::Native)
354    }
355
356    pub fn handle_mprotect(
357        &mut self,
358        _ctx: &ThreadContext,
359        _addr: ForeignPtr<u8>,
360        _size: usize,
361        _prot: ProtFlags,
362    ) -> Result<(), SyscallError> {
363        Err(SyscallError::Native)
364    }
365}
366
367/// Memory allocated by Shadow, in a remote address space.
368pub struct AllocdMem<T>
369where
370    T: Pod,
371{
372    ptr: ForeignArrayPtr<T>,
373    // Whether the pointer has been freed.
374    freed: bool,
375}
376
377impl<T> AllocdMem<T>
378where
379    T: Pod,
380{
381    /// Allocate memory in the current active process.
382    /// Must be freed explicitly via `free`.
383    pub fn new(ctx: &ThreadContext, len: usize) -> Self {
384        let prot = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE;
385
386        // Allocate through the MemoryManager, so that it knows about this region.
387        let ptr = ctx
388            .process
389            .memory_borrow_mut()
390            .do_mmap(
391                ctx,
392                ForeignPtr::null(),
393                len * std::mem::size_of::<T>(),
394                prot,
395                MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE,
396                -1,
397                0,
398            )
399            .unwrap();
400
401        Self {
402            ptr: ForeignArrayPtr::new(ptr.cast::<T>(), len),
403            freed: false,
404        }
405    }
406
407    /// Pointer to the allocated memory.
408    pub fn ptr(&self) -> ForeignArrayPtr<T> {
409        self.ptr
410    }
411
412    pub fn free(mut self, ctx: &ThreadContext) {
413        ctx.process
414            .memory_borrow_mut()
415            .do_munmap(
416                ctx,
417                self.ptr.ptr().cast::<u8>(),
418                self.ptr.len() * std::mem::size_of::<T>(),
419            )
420            .unwrap();
421        self.freed = true;
422    }
423}
424
425impl<T> Drop for AllocdMem<T>
426where
427    T: Pod,
428{
429    fn drop(&mut self) {
430        // We need the thread context to free the memory. Nothing to do now but
431        // complain.
432        if !self.freed {
433            warn!("Memory leak: failed to free {:?}", self.ptr)
434        }
435        debug_assert!(self.freed);
436    }
437}
438
439mod export {
440    use shadow_shim_helper_rs::notnull::*;
441    use shadow_shim_helper_rs::syscall_types::UntypedForeignPtr;
442
443    use super::*;
444
445    /// Copy `n` bytes from `src` to `dst`. Returns 0 on success or -EFAULT if any of the specified
446    /// range couldn't be accessed. Always succeeds with n==0.
447    #[unsafe(no_mangle)]
448    pub extern "C-unwind" fn memorymanager_readPtr(
449        mem: *const MemoryManager,
450        dst: *mut c_void,
451        src: UntypedForeignPtr,
452        n: usize,
453    ) -> i32 {
454        let mem = unsafe { mem.as_ref() }.unwrap();
455        let src = ForeignArrayPtr::new(src.cast::<u8>(), n);
456        let dst = unsafe { std::slice::from_raw_parts_mut(notnull_mut_debug(dst) as *mut u8, n) };
457
458        match mem.copy_from_ptr(dst, src) {
459            Ok(_) => 0,
460            Err(e) => {
461                trace!("Couldn't read {src:?} into {dst:?}: {e:?}");
462                e.to_negated_i32()
463            }
464        }
465    }
466
467    /// Copy `n` bytes from `src` to `dst`. Returns 0 on success or -EFAULT if any of the specified
468    /// range couldn't be accessed. The write is flushed immediately.
469    #[unsafe(no_mangle)]
470    pub unsafe extern "C-unwind" fn memorymanager_writePtr(
471        mem: *mut MemoryManager,
472        dst: UntypedForeignPtr,
473        src: *const c_void,
474        n: usize,
475    ) -> i32 {
476        let mem = unsafe { mem.as_mut() }.unwrap();
477        let dst = ForeignArrayPtr::new(dst.cast::<u8>(), n);
478        let src = unsafe { std::slice::from_raw_parts(notnull_debug(src) as *const u8, n) };
479        match mem.copy_to_ptr(dst, src) {
480            Ok(_) => 0,
481            Err(e) => {
482                trace!("Couldn't write {src:?} into {dst:?}: {e:?}");
483                e.to_negated_i32()
484            }
485        }
486    }
487}