shadow_rs/host/syscall/handler/
mman.rs1use std::os::unix::ffi::OsStrExt;
2use std::path::PathBuf;
3
4use linux_api::errno::Errno;
5use linux_api::fcntl::OFlag;
6use linux_api::mman::{MapFlags, ProtFlags};
7use shadow_shim_helper_rs::syscall_types::ForeignPtr;
8
9use crate::cshadow as c;
10use crate::host::descriptor::{CompatFile, FileState};
11use crate::host::memory_manager::AllocdMem;
12use crate::host::syscall::handler::{SyscallContext, SyscallHandler, ThreadContext};
13use crate::host::syscall::types::SyscallError;
14
15impl SyscallHandler {
16    log_syscall!(
17        brk,
18        std::ffi::c_int,
19        *const std::ffi::c_void,
20    );
21    pub fn brk(
22        ctx: &mut SyscallContext,
23        addr: ForeignPtr<u8>,
24    ) -> Result<ForeignPtr<u8>, SyscallError> {
25        let mut memory_manager = ctx.objs.process.memory_borrow_mut();
27        memory_manager.handle_brk(ctx.objs, addr)
28    }
29
30    log_syscall!(
37        mremap,
38        *const std::ffi::c_void,
39        *const std::ffi::c_void,
40        std::ffi::c_ulong,
41        std::ffi::c_ulong,
42        linux_api::mman::MRemapFlags,
43        *const std::ffi::c_void,
44    );
45    pub fn mremap(
46        ctx: &mut SyscallContext,
47        old_addr: std::ffi::c_ulong,
48        old_size: std::ffi::c_ulong,
49        new_size: std::ffi::c_ulong,
50        flags: std::ffi::c_ulong,
51        new_addr: std::ffi::c_ulong,
52    ) -> Result<ForeignPtr<u8>, SyscallError> {
53        let old_addr: usize = old_addr.try_into().unwrap();
54        let old_size: usize = old_size.try_into().unwrap();
55        let new_size: usize = new_size.try_into().unwrap();
56        let new_addr: usize = new_addr.try_into().unwrap();
57
58        if flags as u32 as u64 != flags {
61            warn_once_then_trace!("Ignoring truncated flags from mremap: {flags}");
62        }
63
64        let flags = flags as i32;
65
66        let old_addr = ForeignPtr::<()>::from(old_addr).cast::<u8>();
67        let new_addr = ForeignPtr::<()>::from(new_addr).cast::<u8>();
68
69        let mut memory_manager = ctx.objs.process.memory_borrow_mut();
71        memory_manager.handle_mremap(ctx.objs, old_addr, old_size, new_size, flags, new_addr)
72    }
73
74    log_syscall!(
79        munmap,
80        std::ffi::c_int,
81        *const std::ffi::c_void,
82        usize,
83    );
84    pub fn munmap(
85        ctx: &mut SyscallContext,
86        addr: std::ffi::c_ulong,
87        len: usize,
88    ) -> Result<(), SyscallError> {
89        let addr: usize = addr.try_into().unwrap();
90        let addr = ForeignPtr::<()>::from(addr).cast::<u8>();
91
92        let mut memory_manager = ctx.objs.process.memory_borrow_mut();
94        memory_manager.handle_munmap(ctx.objs, addr, len)
95    }
96
97    log_syscall!(
102        mprotect,
103        std::ffi::c_int,
104        *const std::ffi::c_void,
105        usize,
106        linux_api::mman::ProtFlags,
107    );
108    pub fn mprotect(
109        ctx: &mut SyscallContext,
110        addr: std::ffi::c_ulong,
111        len: usize,
112        prot: std::ffi::c_ulong,
113    ) -> Result<(), SyscallError> {
114        let addr: usize = addr.try_into().unwrap();
115        let addr = ForeignPtr::<()>::from(addr).cast::<u8>();
116
117        let Some(prot) = ProtFlags::from_bits(prot) else {
118            let unrecognized = ProtFlags::from_bits_retain(prot).difference(ProtFlags::all());
119            log_once_per_value_at_level!(
120                unrecognized,
121                ProtFlags,
122                log::Level::Warn,
123                log::Level::Debug,
124                "Unrecognized prot flag: {:#x}",
125                unrecognized.bits()
126            );
127            return Err(Errno::EINVAL.into());
128        };
129
130        let mut memory_manager = ctx.objs.process.memory_borrow_mut();
132        memory_manager.handle_mprotect(ctx.objs, addr, len, prot)
133    }
134
135    log_syscall!(
142        mmap,
143        *const std::ffi::c_void,
144        *const std::ffi::c_void,
145        usize,
146        linux_api::mman::ProtFlags,
147        linux_api::mman::MapFlags,
148        std::ffi::c_ulong,
149        std::ffi::c_ulong,
150    );
151    pub fn mmap(
152        ctx: &mut SyscallContext,
153        addr: std::ffi::c_ulong,
154        len: std::ffi::c_ulong,
155        prot: std::ffi::c_ulong,
156        flags: std::ffi::c_ulong,
157        fd: std::ffi::c_ulong,
158        offset: std::ffi::c_ulong,
159    ) -> Result<ForeignPtr<u8>, Errno> {
160        log::trace!("mmap called on fd {fd} for {len} bytes");
161
162        let addr: usize = addr.try_into().unwrap();
163        let addr = ForeignPtr::<()>::from(addr).cast::<u8>();
164
165        let len: usize = len.try_into().unwrap();
166
167        let offset = offset as i64;
168
169        let Some(prot) = ProtFlags::from_bits(prot) else {
170            let unrecognized = ProtFlags::from_bits_retain(prot).difference(ProtFlags::all());
171            log_once_per_value_at_level!(
172                unrecognized,
173                ProtFlags,
174                log::Level::Warn,
175                log::Level::Debug,
176                "Unrecognized prot flag: {:#x}",
177                unrecognized.bits()
178            );
179            return Err(Errno::EINVAL);
180        };
181        let Some(flags) = MapFlags::from_bits(flags) else {
182            let unrecognized = MapFlags::from_bits_retain(flags).difference(MapFlags::all());
183            log_once_per_value_at_level!(
184                unrecognized,
185                MapFlags,
186                log::Level::Warn,
187                log::Level::Debug,
188                "Unrecognized map flag: {:#x}",
189                unrecognized.bits()
190            );
191            return Err(Errno::EINVAL);
192        };
193
194        let required_flags =
196            MapFlags::MAP_PRIVATE | MapFlags::MAP_SHARED | MapFlags::MAP_SHARED_VALIDATE;
197
198        if len == 0 || !required_flags.intersects(flags) {
200            log::debug!("Invalid len ({len}), prot ({prot:?}), or flags ({flags:?})");
201            return Err(Errno::EINVAL);
202        }
203
204        if fd <= 2 && !flags.contains(MapFlags::MAP_ANONYMOUS) {
207            log::debug!("Invalid fd {fd} and MAP_ANONYMOUS is not set in flags {flags:?}");
208            return Err(Errno::EBADF);
209        }
210
211        let file = if flags.contains(MapFlags::MAP_ANONYMOUS) {
213            None
214        } else {
215            let file = {
216                let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
218                let desc = Self::get_descriptor(&desc_table, fd)?;
219
220                let CompatFile::Legacy(file) = desc.file() else {
221                    return Err(Errno::EINVAL);
223                };
224
225                file.ptr()
226            };
227
228            assert!(!file.is_null());
229
230            if unsafe { c::legacyfile_getStatus(file) }.contains(FileState::CLOSED) {
231                log::warn!("File {file:p} (fd={fd}) is closed");
237                return Err(Errno::EBADF);
238            }
239
240            if unsafe { c::legacyfile_getType(file) } != c::_LegacyFileType_DT_FILE {
241                log::debug!("Descriptor exists for fd {fd}, but is not a regular file type");
242                return Err(Errno::EACCES);
243            }
244
245            Some(file as *mut c::RegularFile)
247        };
248
249        let plugin_fd = file.map(|file| Self::open_plugin_file(ctx.objs, fd, file));
252
253        let Ok(plugin_fd) = plugin_fd.transpose() else {
255            log::warn!("mmap on fd {fd} for {len} bytes failed");
256            return Err(Errno::EACCES);
257        };
258
259        let mut memory_manager = ctx.objs.process.memory_borrow_mut();
261        let mmap_result = memory_manager.do_mmap(
262            ctx.objs,
263            addr,
264            len,
265            prot,
266            flags,
267            plugin_fd.unwrap_or(-1),
268            offset,
269        );
270
271        log::trace!(
272            "Plugin-native mmap syscall at plugin addr {addr:p} with plugin fd {fd} for \
273            {len} bytes returned {mmap_result:?}"
274        );
275
276        if let Some(plugin_fd) = plugin_fd {
278            Self::close_plugin_file(ctx.objs, plugin_fd);
279        }
280
281        mmap_result
282    }
283
284    fn open_plugin_file(
285        ctx: &ThreadContext,
286        fd: std::ffi::c_ulong,
287        file: *mut c::RegularFile,
288    ) -> Result<i32, ()> {
289        assert!(!file.is_null());
290
291        log::trace!("Trying to open file {fd} in the plugin");
292
293        let file_type = unsafe { c::regularfile_getType(file) };
297        if file_type != c::_FileType_FILE_TYPE_REGULAR
298            && file_type != c::_FileType_FILE_TYPE_LOCALTIME
299        {
300            warn_once_then_debug!("Tried to mmap a non-regular non-localtime file");
301            return Err(());
302        }
303
304        let native_fd = unsafe { c::regularfile_getOSBackedFD(file) };
305
306        let Some(path) = Self::create_persistent_mmap_path(native_fd) else {
308            log::trace!("RegularFile {fd} has a NULL path");
309            return Err(());
310        };
311
312        let path_bytes = path.as_os_str().as_bytes();
313
314        let path_len = std::cmp::min(path_bytes.len(), libc::PATH_MAX as usize - 1);
319        assert!(path_len > 0);
320
321        let path_bytes = &path_bytes[..path_len];
322
323        log::trace!("Opening path '{}' in plugin", path.display());
324
325        let plugin_buffer = AllocdMem::<u8>::new(ctx, path_len + 1);
328
329        {
330            let mut mem = ctx.process.memory_borrow_mut();
331
332            if let Err(e) = mem.copy_to_ptr(plugin_buffer.ptr().slice(..path_len), path_bytes) {
334                log::warn!("Unable to write string to allocated buffer: {e}");
335                std::mem::drop(mem);
336                plugin_buffer.free(ctx);
337                return Err(());
338            }
339
340            if let Err(e) = mem.copy_to_ptr(plugin_buffer.ptr().slice(path_len..), &[0]) {
342                log::warn!("Unable to write NUL to allocated buffer: {e}");
343                std::mem::drop(mem);
344                plugin_buffer.free(ctx);
345                return Err(());
346            }
347        }
348
349        let creation_flags = OFlag::empty()
354            | OFlag::O_CLOEXEC
355            | OFlag::O_CREAT
356            | OFlag::O_DIRECTORY
357            | OFlag::O_EXCL
358            | OFlag::O_NOCTTY
359            | OFlag::O_NOFOLLOW
360            | OFlag::O_TMPFILE
361            | OFlag::O_TRUNC;
362
363        let native_flags = OFlag::from_bits_retain(unsafe {
365            libc::fcntl(c::regularfile_getOSBackedFD(file), libc::F_GETFL)
366        });
367
368        let mut flags = OFlag::from_bits_retain(unsafe { c::regularfile_getFlagsAtOpen(file) });
370        flags &= creation_flags.difference(OFlag::O_CLOEXEC);
372        flags |= native_flags.difference(OFlag::from_bits_retain(unsafe { c::SHADOW_FLAG_MASK }));
374        flags |= OFlag::from_bits_retain(unsafe { c::regularfile_getShadowFlags(file) });
376        flags -= OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_TMPFILE | OFlag::O_TRUNC;
378        flags -= OFlag::O_NOFOLLOW;
381
382        let mode = unsafe { c::regularfile_getModeAtOpen(file) };
383
384        let (process_ctx, thread) = ctx.split_thread();
386        let open_result = thread.native_open(
387            &process_ctx,
388            plugin_buffer.ptr().ptr(),
389            flags.bits() as i32,
390            mode as i32,
391        );
392
393        plugin_buffer.free(ctx);
394
395        let open_result = match open_result {
396            Ok(x) => x,
397            Err(e) => {
398                log::trace!(
399                    "Failed to open path '{}' in plugin, error {e}",
400                    path.display()
401                );
402                return Err(());
403            }
404        };
405
406        log::trace!(
407            "Successfully opened path '{}' in plugin, got plugin fd {open_result}",
408            path.display(),
409        );
410
411        Ok(open_result)
412    }
413
414    fn close_plugin_file(ctx: &ThreadContext, plugin_fd: i32) {
416        let (ctx, thread) = ctx.split_thread();
417        let result = thread.native_close(&ctx, plugin_fd);
418
419        if let Err(e) = result {
420            log::trace!("Failed to close file at fd {plugin_fd} in plugin, error {e}");
421        } else {
422            log::trace!("Successfully closed file at fd {plugin_fd} in plugin");
423        }
424    }
425
426    fn create_persistent_mmap_path(native_fd: std::ffi::c_int) -> Option<PathBuf> {
430        assert!(native_fd >= 0);
431
432        let pid_string = std::process::id().to_string();
444        let native_fd_string = native_fd.to_string();
445
446        let path: PathBuf = ["/proc", &pid_string, "fd", &native_fd_string]
449            .iter()
450            .collect();
451
452        if !path.exists() {
454            log::warn!(
455                "Unable to produce a persistent mmap path for file (linux file {native_fd})"
456            );
457            return None;
458        }
459
460        log::trace!(
461            "RegularFile (linux file {native_fd}) is persistent in procfs at {}",
462            path.display()
463        );
464
465        Some(path)
466    }
467}