shadow_rs/host/memory_manager/
memory_copier.rs

1use std::fmt::Debug;
2
3use linux_api::errno::Errno;
4use linux_api::posix_types::Pid;
5use log::*;
6use shadow_pod::Pod;
7
8use crate::core::worker::Worker;
9use crate::host::memory_manager::page_size;
10use crate::host::syscall::types::ForeignArrayPtr;
11
12/// A utility for copying data to and from a process's memory.
13#[derive(Debug, Clone)]
14pub struct MemoryCopier {
15    pid: Pid,
16}
17
18impl MemoryCopier {
19    pub fn new(pid: Pid) -> Self {
20        Self { pid }
21    }
22
23    /// Copy the region.
24    /// SAFETY: A mutable reference to the process memory must not exist.
25    #[allow(clippy::uninit_vec)]
26    pub unsafe fn clone_mem<T: Pod + Debug>(
27        &self,
28        ptr: ForeignArrayPtr<T>,
29    ) -> Result<Vec<T>, Errno> {
30        let mut v = Vec::with_capacity(ptr.len());
31        unsafe { v.set_len(v.capacity()) };
32        unsafe { self.copy_from_ptr(&mut v, ptr)? };
33        Ok(v)
34    }
35
36    /// Copy the readable prefix of the region.
37    /// SAFETY: A mutable reference to the process memory must not exist.
38    #[allow(clippy::uninit_vec)]
39    pub unsafe fn clone_mem_prefix<T: Pod + Debug>(
40        &self,
41        ptr: ForeignArrayPtr<T>,
42    ) -> Result<Vec<T>, Errno> {
43        let mut v = Vec::with_capacity(ptr.len());
44        unsafe { v.set_len(v.capacity()) };
45        let copied = unsafe { self.copy_prefix_from_ptr(&mut v, ptr)? };
46        v.truncate(copied);
47        Ok(v)
48    }
49
50    // Read as much of `ptr` as is accessible into `dst`.
51    /// SAFETY: A mutable reference to the process memory must not exist.
52    pub unsafe fn copy_prefix_from_ptr<T>(
53        &self,
54        dst: &mut [T],
55        src: ForeignArrayPtr<T>,
56    ) -> Result<usize, Errno>
57    where
58        T: Pod + Debug,
59    {
60        // Convert to u8
61        // SAFETY: We do not write uninitialized data into `buf`.
62        let buf: &mut [std::mem::MaybeUninit<u8>] = unsafe { shadow_pod::to_u8_slice_mut(dst) };
63        // SAFETY: this buffer is write-only.
64        // TODO: Fix or move away from nix's process_vm_readv wrapper so that we
65        // don't need to construct this slice, and can instead only ever operate
66        // on the pointer itself.
67        let mut buf: &mut [u8] =
68            unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()) };
69
70        let ptr = src.cast_u8();
71
72        // Split at page boundaries to allow partial reads.
73        let mut slices = Vec::with_capacity(buf.len().div_ceil(page_size()) + 1);
74        let mut total_bytes_toread = std::cmp::min(buf.len(), ptr.len());
75
76        // First chunk to read is from pointer to beginning of next page.
77        let prev_page_boundary = usize::from(ptr.ptr()) / page_size() * page_size();
78        let next_page_boundary = prev_page_boundary + page_size();
79        let mut next_bytes_toread = std::cmp::min(
80            next_page_boundary - usize::from(ptr.ptr()),
81            total_bytes_toread,
82        );
83
84        while next_bytes_toread > 0 {
85            // Add the next chunk to read.
86            let (prefix, suffix) = buf.split_at_mut(next_bytes_toread);
87            buf = suffix;
88            slices.push(prefix);
89            total_bytes_toread -= next_bytes_toread;
90
91            // Reads should now be page-aligned. Read a whole page at a time,
92            // up to however much is left.
93            next_bytes_toread = std::cmp::min(total_bytes_toread, page_size());
94        }
95        let bytes_read = unsafe { self.readv_ptrs(&mut slices, &[ptr])? };
96        Ok(bytes_read / std::mem::size_of::<T>())
97    }
98
99    // Copy `dst` into `src`.
100    /// SAFETY: A mutable reference to the process memory must not exist.
101    pub unsafe fn copy_from_ptr<T: Pod + Debug>(
102        &self,
103        dst: &mut [T],
104        src: ForeignArrayPtr<T>,
105    ) -> Result<(), Errno> {
106        assert_eq!(dst.len(), src.len());
107        // SAFETY: We do not write uninitialized data into `buf`.
108        let buf = unsafe { shadow_pod::to_u8_slice_mut(dst) };
109        // SAFETY: this buffer is write-only.
110        // TODO: Fix or move away from nix's process_vm_readv wrapper so that we
111        // don't need to construct this slice, and can instead only ever operate
112        // on the pointer itself.
113        let buf: &mut [u8] =
114            unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()) };
115        let ptr = src.cast_u8();
116        let bytes_read = unsafe { self.readv_ptrs(&mut [buf], &[ptr])? };
117        if bytes_read != buf.len() {
118            warn!(
119                "Tried to read {} bytes but only got {}",
120                buf.len(),
121                bytes_read
122            );
123            return Err(Errno::EFAULT);
124        }
125        Ok(())
126    }
127
128    // Low level helper for reading directly from `srcs` to `dsts`.
129    // Returns the number of bytes read. Panics if the
130    // MemoryManager's process isn't currently active.
131    /// SAFETY: A mutable reference to the process memory must not exist.
132    unsafe fn readv_ptrs(
133        &self,
134        dsts: &mut [&mut [u8]],
135        srcs: &[ForeignArrayPtr<u8>],
136    ) -> Result<usize, Errno> {
137        let srcs: Vec<_> = srcs
138            .iter()
139            .map(|src| nix::sys::uio::RemoteIoVec {
140                base: usize::from(src.ptr()),
141                len: src.len(),
142            })
143            .collect();
144        let mut dsts: Vec<_> = dsts
145            .iter_mut()
146            .map(|dst: &mut &mut [u8]| -> std::io::IoSliceMut { std::io::IoSliceMut::new(dst) })
147            .collect();
148
149        unsafe { self.readv_iovecs(&mut dsts, &srcs) }
150    }
151
152    // Low level helper for reading directly from `srcs` to `dsts`.
153    // Returns the number of bytes read. Panics if the
154    // MemoryManager's process isn't currently active.
155    /// SAFETY: A mutable reference to the process memory must not exist.
156    unsafe fn readv_iovecs(
157        &self,
158        dsts: &mut [std::io::IoSliceMut],
159        srcs: &[nix::sys::uio::RemoteIoVec],
160    ) -> Result<usize, Errno> {
161        trace!(
162            "Reading from srcs of len {}",
163            srcs.iter().map(|s| s.len).sum::<usize>()
164        );
165        trace!(
166            "Reading to dsts of len {}",
167            dsts.iter().map(|d| d.len()).sum::<usize>()
168        );
169
170        // While the documentation for process_vm_readv says to use the pid, in
171        // practice it needs to be the tid of a still-running thread. i.e. using the
172        // pid after the thread group leader has exited will fail.
173        let tid = Worker::with_active_host(|host| {
174            Worker::with_active_process(|process| {
175                // Don't access another process's memory.
176                assert_eq!(process.native_pid(), self.pid);
177                let thread = process.first_live_thread_borrow(host.root()).unwrap();
178                let thread = thread.borrow(host.root());
179                thread.native_tid()
180            })
181            .unwrap()
182        })
183        .unwrap();
184
185        let nread = nix::sys::uio::process_vm_readv(
186            nix::unistd::Pid::from_raw(tid.as_raw_nonzero().get()),
187            dsts,
188            srcs,
189        )
190        .map_err(|e| Errno::try_from(e as i32).unwrap())?;
191
192        Ok(nread)
193    }
194
195    // Low level helper for writing directly to `dst`. Panics if the
196    // MemoryManager's process isn't currently active.
197    /// SAFETY: A reference to the process memory must not exist.
198    pub unsafe fn copy_to_ptr<T: Pod + Debug>(
199        &self,
200        dst: ForeignArrayPtr<T>,
201        src: &[T],
202    ) -> Result<(), Errno> {
203        let dst = dst.cast_u8();
204        let src: &[std::mem::MaybeUninit<u8>] = shadow_pod::to_u8_slice(src);
205        // SAFETY: We *should* never actually read from this buffer in this process;
206        // ultimately its pointer will be passed to the process_vm_writev syscall,
207        // for which unitialized data is ok.
208        // TODO: Fix or move away from nix's process_vm_writev wrapper so that we
209        // don't need to construct this slice, and can instead only ever operate
210        // on the pointer itself.
211        let src: &[u8] =
212            unsafe { std::slice::from_raw_parts(src.as_ptr() as *const u8, src.len()) };
213        assert_eq!(src.len(), dst.len());
214
215        let towrite = src.len();
216        trace!("write_ptr writing {} bytes", towrite);
217        let local = [std::io::IoSlice::new(src)];
218        let remote = [nix::sys::uio::RemoteIoVec {
219            base: usize::from(dst.ptr()),
220            len: towrite,
221        }];
222
223        // While the documentation for process_vm_writev says to use the pid, in
224        // practice it needs to be the tid of a still-running thread. i.e. using the
225        // pid after the thread group leader has exited will fail.
226        //
227        // TODO: get this explicitly from the caller instead of reaching out to
228        // the global Worker.
229        let tid = Worker::with_active_host(|host| {
230            Worker::with_active_process(|process| {
231                if process.native_pid() != self.pid {
232                    // This currently only happens in the clone syscall handler
233                    // if we need to write to the child's memory, and the child
234                    // is a new process. In this case the thread group leader should
235                    // be alive, so the pid will work.
236                    //
237                    // TODO: as above, this hack can be avoided by getting a live tid
238                    // explicitly from the caller.
239                    self.pid
240                } else {
241                    let thread = process.first_live_thread_borrow(host.root()).unwrap();
242                    let thread = thread.borrow(host.root());
243                    thread.native_tid()
244                }
245            })
246            .unwrap()
247        })
248        .unwrap();
249
250        let nwritten = nix::sys::uio::process_vm_writev(
251            nix::unistd::Pid::from_raw(tid.as_raw_nonzero().get()),
252            &local,
253            &remote,
254        )
255        .map_err(|e| Errno::try_from(e as i32).unwrap())?;
256        // There shouldn't be any partial writes with a single remote iovec.
257        assert_eq!(nwritten, towrite);
258        Ok(())
259    }
260}