Skip to main content

process_backend/local/
module_reader.rs

1use {
2    super::{Error, OwnedFd, SyscallInvoker, errno},
3    core::{
4        ffi::{CStr, c_void},
5        mem, ptr,
6    },
7};
8
9#[derive(Debug)]
10pub struct MappedModuleMemoryReader {
11    mapped: Mapped,
12    ptr: *mut u8,
13    len: usize,
14}
15
16impl MappedModuleMemoryReader {
17    pub fn new(
18        syscall_invoker: &mut SyscallInvoker,
19        path: &CStr,
20        start_position: u64,
21    ) -> Result<Self, Error> {
22        let fd = Self::open_file(syscall_invoker, path)?;
23
24        // So far, we only ever map files from the start position to EOF - We never specify a
25        // max length anywhere.
26        let end_position = Self::get_file_size(syscall_invoker, &fd)?;
27
28        if start_position > end_position {
29            Err(Error::StartPositionPastEnd)?;
30        }
31
32        // a mmap() mapping must start on a page-aligned offset within the file
33        let page_size = Self::get_page_size();
34        let offset_into_page = start_position % page_size;
35        let aligned_start_position = start_position - offset_into_page;
36        let mmap_length = usize::try_from(end_position - aligned_start_position)
37            .map_err(|_| Error::MappingTooLarge)?;
38
39        let mapped = Self::map_memory(syscall_invoker, &fd, aligned_start_position, mmap_length)?;
40
41        // Contrary to what you might expect, it's fine to close the fd once the mapping has
42        // been established
43        drop(fd);
44
45        // Now that we have our page-aligned memory mapped, back-calculate the (ptr, len) pair
46        // for the actual slice the user asked for.
47
48        let slice_offset_into_mapping = usize::try_from(offset_into_page).unwrap();
49        let ptr = unsafe { mapped.ptr.cast::<u8>().add(slice_offset_into_mapping) };
50        let len = mmap_length - slice_offset_into_mapping;
51
52        Ok(MappedModuleMemoryReader { mapped, ptr, len })
53    }
54    pub fn read(&self, offset: u64, length: u64) -> Result<&[u8], Error> {
55        (|| {
56            let offset = usize::try_from(offset).ok()?;
57            let length = usize::try_from(length).ok()?;
58            let end = offset.checked_add(length)?;
59            self.as_slice().get(offset..end)
60        })()
61        .ok_or(Error::IndexOutOfBounds)
62    }
63    pub fn len(&self) -> Result<usize, Error> {
64        Ok(self.as_slice().len())
65    }
66    pub fn is_empty(&self) -> Result<bool, Error> {
67        self.len().map(|l| l == 0)
68    }
69    fn open_file(syscall_invoker: &mut SyscallInvoker, path: &CStr) -> Result<OwnedFd, Error> {
70        syscall_invoker
71            .invoke_standard(|| unsafe {
72                libc::open(path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC, 0)
73            })
74            .map(|fd| unsafe { OwnedFd::new(fd) })
75            .map_err(Error::OpenFileFailed)
76    }
77    fn get_file_size(syscall_invoker: &mut SyscallInvoker, fd: &OwnedFd) -> Result<u64, Error> {
78        let mut stat: libc::stat = unsafe { mem::zeroed() };
79
80        syscall_invoker
81            .invoke_standard(|| unsafe { libc::fstat(fd.as_raw_fd(), &mut stat) })
82            .map_err(Error::StatFailed)?;
83
84        Ok(u64::try_from(stat.st_size).unwrap())
85    }
86    fn get_page_size() -> u64 {
87        let page_size = u64::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }).unwrap();
88        assert!(page_size > 0);
89        page_size
90    }
91    fn map_memory(
92        syscall_invoker: &mut SyscallInvoker,
93        fd: &OwnedFd,
94        page_aligned_start_position: u64,
95        len: usize,
96    ) -> Result<Mapped, Error> {
97        // Linux requires the mapping length to be non-zero, even though we want to support
98        // zero-length mappings -- So we just make it a one-byte mapping (and ignore the byte).
99        let len = usize::max(len, 1);
100
101        // Rust/LLVM cannot support a single object larger than `isize::MAX`, which is
102        // 2GiB on 32-bit systems. It is possible to map files larger than that, but there is a
103        // bunch of special handling that needs to be done to avoid accidentally telling LLVM that
104        // the mapped memory might be a single object with the same provenance.
105        //
106        // Luckily, we won't be accessing files that are larger than 2GiB, so we can skip all that
107        // nastiness by disallowing a mapping larger than `isize::MAX`.
108        //
109        // See https://doc.rust-lang.org/stable/std/ptr/index.html#allocation and
110        // https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.offset
111
112        if len > isize::MAX as usize {
113            Err(Error::MappingTooLarge)?;
114        }
115
116        syscall_invoker
117            .invoke(|| unsafe {
118                let ptr = libc::mmap(
119                    ptr::null_mut(),
120                    len,
121                    libc::PROT_READ,
122                    libc::MAP_SHARED,
123                    fd.as_raw_fd(),
124                    page_aligned_start_position.try_into().unwrap(),
125                );
126                if ptr == libc::MAP_FAILED {
127                    return Err(());
128                }
129                Ok(Mapped { ptr, len })
130            })
131            .map_err(Error::MMapfailed)
132    }
133    fn as_slice(&self) -> &[u8] {
134        // The compiler will warn that we're not using `mapped` at all, but technically this
135        // function does use it -- it just isn't captured by the semantics. This is basically
136        // a no-op just to show that we do, in fact, use it.
137        let _mapped_used = &self.mapped;
138        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
139    }
140}
141
142#[derive(Debug)]
143struct Mapped {
144    ptr: *mut c_void,
145    len: usize,
146}
147
148impl Drop for Mapped {
149    fn drop(&mut self) {
150        let rv = unsafe { libc::munmap(self.ptr, self.len) };
151        if rv == -1 {
152            log::error!("failed to unmap memory: {}", errno());
153        }
154    }
155}