diff --git a/src/cage/src/memory/memory.rs b/src/cage/src/memory/memory.rs index a09de786b..81f169e47 100644 --- a/src/cage/src/memory/memory.rs +++ b/src/cage/src/memory/memory.rs @@ -124,8 +124,7 @@ pub fn fork_vmmap(parent_cageid: u64, child_cageid: u64) { // entry.flags, // entry.backing, // ); - let hit = addr_st <= 0xffffe000 - && 0xffffe000 < addr_st.wrapping_add(addr_len as u32); + let hit = addr_st <= 0xffffe000 && 0xffffe000 < addr_st.wrapping_add(addr_len as u32); if hit { eprintln!( @@ -140,7 +139,7 @@ pub fn fork_vmmap(parent_cageid: u64, child_cageid: u64) { entry.backing, ); } - + let hits_target = { const TARGET: u64 = 0xffffe000; let start = addr_st as u64; @@ -150,10 +149,8 @@ pub fn fork_vmmap(parent_cageid: u64, child_cageid: u64) { if entry.flags & (MAP_SHARED as i32) != 0 { unsafe { - let parent_value_before = - std::ptr::read_volatile(parent_st as *const u32); - let child_value_before = - std::ptr::read_volatile(child_st as *const u32); + let parent_value_before = std::ptr::read_volatile(parent_st as *const u32); + let child_value_before = std::ptr::read_volatile(child_st as *const u32); if hits_target { eprintln!( @@ -189,10 +186,8 @@ pub fn fork_vmmap(parent_cageid: u64, child_cageid: u64) { let errno = *libc::__errno_location(); - let parent_value_after = - std::ptr::read_volatile(parent_st as *const u32); - let child_value_after = - std::ptr::read_volatile(child_st as *const u32); + let parent_value_after = std::ptr::read_volatile(parent_st as *const u32); + let child_value_after = std::ptr::read_volatile(child_st as *const u32); if hits_target { eprintln!( @@ -242,11 +237,8 @@ pub fn fork_vmmap(parent_cageid: u64, child_cageid: u64) { addr_len, ); if needs_write { - let mret = libc::mprotect( - child_st as *mut libc::c_void, - addr_len, - entry.prot, - ); + let mret = + libc::mprotect(child_st as *mut libc::c_void, addr_len, entry.prot); assert_eq!(mret, 0, "failed to restore child shared mapping protection"); } } @@ -254,7 +246,6 @@ pub fn fork_vmmap(parent_cageid: u64, child_cageid: u64) { } else { let needs_write = entry.prot & PROT_WRITE == 0; - // eprintln!("[fork_vmmap] before writable mprotect"); unsafe { // temporarily enable write on child's memory region to write parent data @@ -303,11 +294,7 @@ pub fn fork_vmmap(parent_cageid: u64, child_cageid: u64) { // println!("[fork_vmmap] before restore mprotect"); // revert child's memory region prot if needs_write { - let ret = libc::mprotect( - child_st as *mut libc::c_void, - addr_len, - entry.prot, - ); + let ret = libc::mprotect(child_st as *mut libc::c_void, addr_len, entry.prot); assert_eq!(ret, 0, "failed to restore child mapping protection"); } }; diff --git a/src/rawposix/src/fs_calls.rs b/src/rawposix/src/fs_calls.rs index ae82eeb50..c466a3ee0 100644 --- a/src/rawposix/src/fs_calls.rs +++ b/src/rawposix/src/fs_calls.rs @@ -1,7 +1,7 @@ use cage::{ - get_cage, get_shm_length, is_mmap_error, new_shm_segment, round_up_page, shmat_helper, - shmdt_helper, signal::signal::lind_send_signal, MemoryBackingType, VmmapOps, HEAP_ENTRY_INDEX, - SHM_METADATA, get_base_address, + get_base_address, get_cage, get_shm_length, is_mmap_error, new_shm_segment, round_up_page, + shmat_helper, shmdt_helper, signal::signal::lind_send_signal, MemoryBackingType, VmmapOps, + HEAP_ENTRY_INDEX, SHM_METADATA, }; use dashmap::mapref::entry::Entry::{Occupied, Vacant}; use fdtables; @@ -1110,8 +1110,7 @@ pub extern "C" fn mmap_syscall( if range_hits_addr(useraddr, rounded_length, FUTEX_GUEST_ADDR) { eprintln!( "[vmmap-add-hit-ffffe000-after] cage={} ret={:?}", - cageid, - add_ret, + cageid, add_ret, ); } } @@ -1160,7 +1159,10 @@ pub extern "C" fn mmap_inner( let is_shared = flags & (MAP_SHARED as i32) != 0; if vfd_arg == -1 && has_anon && has_fixed && (is_private || is_shared) { - println!("[mmap_inner] anonymous fixed mapping, returning addr={:?}", addr); + println!( + "[mmap_inner] anonymous fixed mapping, returning addr={:?}", + addr + ); return addr as usize; } // end diff --git a/src/rawposix/src/sem_calls.rs b/src/rawposix/src/sem_calls.rs index e26801c5c..3e3e70519 100644 --- a/src/rawposix/src/sem_calls.rs +++ b/src/rawposix/src/sem_calls.rs @@ -313,7 +313,11 @@ pub extern "C" fn sem_post_syscall( loop { let v = state.value.load(Ordering::SeqCst); if v >= SEM_VALUE_MAX { - return syscall_error(Errno::EOVERFLOW, "sem_post", "value would exceed SEM_VALUE_MAX"); + return syscall_error( + Errno::EOVERFLOW, + "sem_post", + "value would exceed SEM_VALUE_MAX", + ); } if state .value diff --git a/src/rawposix/src/syscall_table.rs b/src/rawposix/src/syscall_table.rs index 5901c04b3..4a357c2dd 100644 --- a/src/rawposix/src/syscall_table.rs +++ b/src/rawposix/src/syscall_table.rs @@ -20,10 +20,6 @@ use super::fs_calls::{ utimensat_syscall, write_syscall, writev_syscall, }; use super::init::RawCallFunc; -use super::sem_calls::{ - sem_destroy_syscall, sem_getvalue_syscall, sem_init_syscall, sem_post_syscall, - sem_wait_syscall, -}; use super::net_calls::{ accept4_syscall, accept_syscall, bind_syscall, connect_syscall, epoll_create1_syscall, epoll_create_syscall, epoll_ctl_syscall, epoll_wait_syscall, gethostname_syscall, @@ -31,6 +27,9 @@ use super::net_calls::{ ppoll_syscall, recvfrom_syscall, recvmsg_syscall, select_syscall, sendmsg_syscall, sendto_syscall, setsockopt_syscall, shutdown_syscall, socket_syscall, socketpair_syscall, }; +use super::sem_calls::{ + sem_destroy_syscall, sem_getvalue_syscall, sem_init_syscall, sem_post_syscall, sem_wait_syscall, +}; use super::sys_calls::{ exec_syscall, exit_group_syscall, exit_syscall, fork_syscall, getegid_syscall, geteuid_syscall, getgid_syscall, getpgid_syscall, getpid_syscall, getppid_syscall, getuid_syscall, kill_syscall, diff --git a/src/wasmtime/crates/lind-multi-process/src/lib.rs b/src/wasmtime/crates/lind-multi-process/src/lib.rs index 713687f71..63102d4a3 100644 --- a/src/wasmtime/crates/lind-multi-process/src/lib.rs +++ b/src/wasmtime/crates/lind-multi-process/src/lib.rs @@ -5,11 +5,14 @@ use cfg_if::cfg_if; use anyhow::{Context, Result, anyhow}; use std::ffi::c_void; use std::ptr::NonNull; +use sysdefs::constants::fs_const::O_RDONLY; use sysdefs::constants::lind_platform_const::{ UNUSED_ARG, UNUSED_ID, UNUSED_NAME, unset_stack_arena_base, }; -use sysdefs::constants::syscall_const::{EXEC_SYSCALL, EXIT_SYSCALL, FORK_SYSCALL}; -use sysdefs::constants::{Errno, MAX_SHEBANG_DEPTH, MMAP_SYSCALL}; +use sysdefs::constants::syscall_const::{ + CLOSE_SYSCALL, EXEC_SYSCALL, EXIT_SYSCALL, FORK_SYSCALL, OPEN_SYSCALL, READ_SYSCALL, +}; +use sysdefs::constants::{Errno, MAX_SHEBANG_DEPTH, MMAP_SYSCALL, MUNMAP_SYSCALL}; use sysdefs::lind_debug_panic; use sysdefs::lind_log; use sysdefs::{constants::sys_const, data::sys_struct}; @@ -53,6 +56,228 @@ const ASYNCIFY_STOP_REWIND: &str = "asyncify_stop_rewind"; // Binaryen's asyncify uses 32-bit pointers regardless of host word size. const UNWIND_METADATA_SIZE: u64 = 8; +// ---- PR #1285 port: route exec module loading through 3i instead of the host FS ---- +// 3i validates every pointer argument as a guest offset against the calling +// cage's vmmap, so buffers on the runtime heap are invisible to it. All data +// for runtime-originated syscalls is marshaled through scratch space +// allocated inside the cage's own linear memory, which keeps the exec +// loader's open/read/close indistinguishable from guest syscalls: fully +// mediated and interposable by grates, with no trusted host-pointer +// convention that a guest could forge. +const EXEC_SCRATCH_SIZE: usize = 1 << 24; + +fn lind_syscall(cageid: u64, syscall_num: i32, args: [(u64, u64); 6]) -> i32 { + make_syscall( + cageid, + syscall_num as u64, + 0, + cageid, + args[0].0, + args[0].1, + args[1].0, + args[1].1, + args[2].0, + args[2].1, + args[3].0, + args[3].1, + args[4].0, + args[4].1, + args[5].0, + args[5].1, + ) +} + +fn syscall_errno(ret: i32) -> Option { + if ret < 0 { Some(-ret) } else { None } +} + +// mmap returns a guest address whose high bit may be set, so only values in +// (-4096, 0) are errors (Linux convention). +fn mmap_ret_to_guest_addr(ret: i32) -> Result { + if ret < 0 && ret > -4096 { + Err(-ret) + } else { + Ok(ret as u32) + } +} + +/// Scratch buffer inside a cage's linear memory, allocated with an anonymous +/// mmap made on the cage's behalf (like early_init_stack) and unmapped on +/// drop so failed execs don't leak guest address space. +struct CageScratch { + cageid: u64, + guest_addr: u32, + len: usize, + host_base: *mut u8, +} + +impl CageScratch { + fn alloc(cageid: u64, host_base: *mut u8, len: usize) -> Result { + let ret = lind_syscall( + cageid, + MMAP_SYSCALL, + [ + (0, cageid), + (len as u64, cageid), + ((typemap::PROT_READ | typemap::PROT_WRITE) as u64, cageid), + ( + (typemap::MAP_PRIVATE | typemap::MAP_ANONYMOUS) as u64, + cageid, + ), + (u64::MAX, cageid), + (0, cageid), + ], + ); + let guest_addr = mmap_ret_to_guest_addr(ret)?; + Ok(CageScratch { + cageid, + guest_addr, + len, + host_base, + }) + } + + fn host_ptr(&self) -> *mut u8 { + unsafe { self.host_base.add(self.guest_addr as usize) } + } + + // 3i and rawposix receive pointer args pre-translated to host-absolute + // (sys) addresses, exactly as the wasmtime trampoline does for guest + // syscalls; check_addr_read still validates them against the cage vmmap. + fn sys_addr(&self) -> u64 { + self.host_base as u64 + self.guest_addr as u64 + } + + fn write_bytes(&self, data: &[u8]) { + assert!(data.len() <= self.len); + unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), self.host_ptr(), data.len()) }; + } + + fn append_to(&self, out: &mut Vec, n: usize) { + assert!(n <= self.len); + out.extend_from_slice(unsafe { std::slice::from_raw_parts(self.host_ptr(), n) }); + } +} + +impl Drop for CageScratch { + fn drop(&mut self) { + let _ = lind_syscall( + self.cageid, + MUNMAP_SYSCALL, + [ + (self.host_base as u64 + self.guest_addr as u64, self.cageid), + (self.len as u64, self.cageid), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + ], + ); + } +} + +fn close_via_lind_syscall(cageid: u64, fd: i32) -> Result<(), i32> { + let ret = lind_syscall( + cageid, + CLOSE_SYSCALL, + [ + (fd as u64, cageid), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + ], + ); + match syscall_errno(ret) { + Some(errno) => Err(errno), + None => Ok(()), + } +} + +fn read_file_via_lind_syscalls( + cageid: u64, + host_base: *mut u8, + path: &str, +) -> Result, i32> { + let path_bytes = path.as_bytes(); + if path_bytes.contains(&0) || path_bytes.len() + 1 > EXEC_SCRATCH_SIZE { + return Err(Errno::EINVAL as i32); + } + + let scratch = CageScratch::alloc(cageid, host_base, EXEC_SCRATCH_SIZE)?; + + let mut path_nul = Vec::with_capacity(path_bytes.len() + 1); + path_nul.extend_from_slice(path_bytes); + path_nul.push(0); + scratch.write_bytes(&path_nul); + + let fd = lind_syscall( + cageid, + OPEN_SYSCALL, + [ + (scratch.sys_addr(), cageid), + (O_RDONLY as u64, cageid), + (0, cageid), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + ], + ); + eprintln!( + "[instrument] exec-3i open cage={} path={} ret={}", + cageid, path, fd + ); + let fd = match syscall_errno(fd) { + Some(errno) => return Err(errno), + None => fd, + }; + + let mut bytes = Vec::new(); + let mut read_errno = None; + + loop { + let ret = lind_syscall( + cageid, + READ_SYSCALL, + [ + (fd as u64, cageid), + (scratch.sys_addr(), cageid), + (scratch.len as u64, cageid), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + (UNUSED_ARG, UNUSED_ID), + ], + ); + + if let Some(errno) = syscall_errno(ret) { + if errno == Errno::EINTR as i32 { + continue; + } + eprintln!( + "[instrument] exec-3i read FAILED errno={} total={}", + errno, + bytes.len() + ); + read_errno = Some(errno); + break; + } + + let n = ret as usize; + if n == 0 { + break; + } + scratch.append_to(&mut bytes, n); + } + eprintln!("[instrument] exec-3i read done total={} bytes", bytes.len()); + + match (read_errno, close_via_lind_syscall(cageid, fd)) { + (Some(errno), _) => Err(errno), + (None, Err(errno)) => Err(errno), + (None, Ok(())) => Ok(bytes), + } +} + // Define the trait with the required method pub trait LindHost { fn get_ctx(&self) -> LindCtx; @@ -307,6 +532,23 @@ impl, child_cageid: u64) -> Result { + eprintln!( + "[instrument] fork_call entered, parent_cage={}, child_cage={}", + self.cageid, child_cageid + ); + if let Ok(status) = std::fs::read_to_string("/proc/self/status") { + for line in status.lines() { + if line.starts_with("VmRSS:") + || line.starts_with("VmHWM:") + || line.starts_with("VmSize:") + { + eprintln!( + "[instrument] fork_call mem parent_cage={} child_cage={} {}", + self.cageid, child_cageid, line + ); + } + } + } // get the base address of the memory let address = get_memory_base(&mut caller) as *mut u8; @@ -405,19 +647,35 @@ impl::new_inner(&engine, symbol_table) .expect("failed to create store inner"); @@ -445,6 +703,10 @@ impl bytes, + Err(errno) => { + eprintln!( + "[instrument] execve cage={} 3i read FAILED errno={}", + self.cageid, errno + ); + return Ok(-errno); + } + }; + eprintln!( + "[instrument] execve cage={} read {} bytes, deserializing module", + self.cageid, + exec_webasm.len() + ); + if let Ok(status) = std::fs::read_to_string("/proc/self/status") { + for line in status.lines() { + if line.starts_with("VmRSS:") + || line.starts_with("VmHWM:") + || line.starts_with("VmSize:") + { + eprintln!("[instrument] execve mem cage={} {}", self.cageid, line); + } + } + } // let detected = Engine::detect_precompiled_file(exec_file_path); // println!("detect_precompiled_file: {:?}", detected); @@ -1447,6 +1752,11 @@ impl unsafe { Module::deserialize(&engine, &exec_webasm) }, None => Module::from_binary(&engine, &exec_webasm), }; + eprintln!( + "[instrument] execve cage={} module deserialize/from_binary returned, ok={}", + self.cageid, + exec_module.is_ok() + ); // let exec_module = match detected { // Ok(Some(_)) => unsafe { Module::deserialize_file(&engine, exec_file_path) }, @@ -1513,8 +1823,16 @@ impl { // mapping_size // ); - let sys = mmap::Mmap::new(mapping_size).map_err(|e| { - eprintln!("[accessible_reserved new] mmap::Mmap::new ERR: {:#?}", e); - e - }) - .with_context(|| format!("mmap failed to allocate {mapping_size:#x} bytes"))?; + let sys = mmap::Mmap::new(mapping_size) + .map_err(|e| { + eprintln!("[accessible_reserved new] mmap::Mmap::new ERR: {:#?}", e); + e + }) + .with_context(|| format!("mmap failed to allocate {mapping_size:#x} bytes"))?; Ok(Mmap { sys, @@ -141,10 +142,7 @@ impl Mmap { result .make_accessible(HostAlignedByteCount::ZERO, accessible_size) .map_err(|e| { - eprintln!( - "[accessible_reserved new] make_accessible ERR: {:#?}", - e - ); + eprintln!("[accessible_reserved new] make_accessible ERR: {:#?}", e); e }) .with_context(|| { diff --git a/src/wasmtime/crates/wasmtime/src/runtime/vm/sys/unix/mmap.rs b/src/wasmtime/crates/wasmtime/src/runtime/vm/sys/unix/mmap.rs index fd05a4018..e3e831981 100644 --- a/src/wasmtime/crates/wasmtime/src/runtime/vm/sys/unix/mmap.rs +++ b/src/wasmtime/crates/wasmtime/src/runtime/vm/sys/unix/mmap.rs @@ -49,7 +49,7 @@ impl Mmap { size.byte_count(), rustix::mm::ProtFlags::READ | rustix::mm::ProtFlags::WRITE, // rustix::mm::MapFlags::PRIVATE | MMAP_NORESERVE_FLAG, - rustix::mm::MapFlags::PRIVATE, + rustix::mm::MapFlags::PRIVATE, )? }; let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), size.byte_count());