From cb1997445d1bbc18d5a899bbd492fab753815e52 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sat, 13 Aug 2022 18:37:59 -0400 Subject: [PATCH] mmap/munmap/mremamp shims --- demo.rs | 14 ++ src/concurrency/data_race.rs | 3 +- src/intptrcast.rs | 25 ++- src/machine.rs | 62 ++++++- src/shims/unix/foreign_items.rs | 26 ++- src/shims/unix/macos/foreign_items.rs | 10 - src/shims/unix/mem.rs | 244 +++++++++++++++++++++++++ src/shims/unix/mod.rs | 1 + tests/fail/mmap_invalid_dealloc.rs | 18 ++ tests/fail/mmap_invalid_dealloc.stderr | 15 ++ tests/fail/mmap_prot_none.rs | 18 ++ tests/fail/mmap_prot_none.stderr | 15 ++ tests/fail/mmap_prot_read.rs | 23 +++ tests/fail/mmap_prot_read.stderr | 15 ++ tests/fail/mmap_prot_write.rs | 26 +++ tests/fail/mmap_prot_write.stderr | 15 ++ tests/pass-dep/shims/mmap.rs | 43 +++++ 17 files changed, 559 insertions(+), 14 deletions(-) create mode 100644 demo.rs create mode 100644 src/shims/unix/mem.rs create mode 100644 tests/fail/mmap_invalid_dealloc.rs create mode 100644 tests/fail/mmap_invalid_dealloc.stderr create mode 100644 tests/fail/mmap_prot_none.rs create mode 100644 tests/fail/mmap_prot_none.stderr create mode 100644 tests/fail/mmap_prot_read.rs create mode 100644 tests/fail/mmap_prot_read.stderr create mode 100644 tests/fail/mmap_prot_write.rs create mode 100644 tests/fail/mmap_prot_write.stderr create mode 100644 tests/pass-dep/shims/mmap.rs diff --git a/demo.rs b/demo.rs new file mode 100644 index 0000000000..a811f68fe0 --- /dev/null +++ b/demo.rs @@ -0,0 +1,14 @@ +fn main() { + unsafe { + let a: u8 = 0; + let b: u8 = 1; + + let addr_a = &a as *const u8 as usize; + let addr_b = &b as *const u8 as usize; + + let ptr = addr_a as *const u8; + dbg!(*ptr); // ptr must have selected the provenance for a + let ptr = ptr.wrapping_offset(addr_b.wrapping_sub(addr_a) as isize); + dbg!(*ptr); // Oops, we can also use it to read b + } +} diff --git a/src/concurrency/data_race.rs b/src/concurrency/data_race.rs index 9646327966..aafe0ec5bb 100644 --- a/src/concurrency/data_race.rs +++ b/src/concurrency/data_race.rs @@ -697,7 +697,8 @@ impl VClockAlloc { MiriMemoryKind::Rust | MiriMemoryKind::Miri | MiriMemoryKind::C - | MiriMemoryKind::WinHeap, + | MiriMemoryKind::WinHeap + | MiriMemoryKind::Mmap, ) | MemoryKind::Stack => { let (alloc_index, clocks) = global.current_thread_state(thread_mgr); diff --git a/src/intptrcast.rs b/src/intptrcast.rs index 618cf9df7f..c3a5f504ca 100644 --- a/src/intptrcast.rs +++ b/src/intptrcast.rs @@ -163,6 +163,12 @@ impl<'mir, 'tcx> GlobalStateInner { } fn alloc_base_addr(ecx: &MiriInterpCx<'mir, 'tcx>, alloc_id: AllocId) -> u64 { + for (offset, map) in ecx.machine.mappings.maps() { + if map.alloc_id == alloc_id { + return offset.bytes(); + } + } + let mut global_state = ecx.machine.intptrcast.borrow_mut(); let global_state = &mut *global_state; @@ -180,6 +186,10 @@ impl<'mir, 'tcx> GlobalStateInner { // This means that `(global_state.next_base_addr + slack) % 16` is uniformly distributed. rng.gen_range(0..16) }; + + // If this would collide with a Mapping, bump the next_base_addr up until it + // doesn't. + // From next_base_addr + slack, round up to adjust for alignment. let base_addr = global_state.next_base_addr.checked_add(slack).unwrap(); let base_addr = Self::align_addr(base_addr, align.bytes()); @@ -210,7 +220,20 @@ impl<'mir, 'tcx> GlobalStateInner { /// Convert a relative (tcx) pointer to an absolute address. pub fn rel_ptr_to_addr(ecx: &MiriInterpCx<'mir, 'tcx>, ptr: Pointer) -> u64 { let (alloc_id, offset) = ptr.into_parts(); // offset is relative (AllocId provenance) - let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id); + + // If this AllocId is for a Mapping, return the base address of that, otherwise look up + // this allocation in the normal way. + let base_addr = ecx + .machine + .mappings + .maps() + .find_map( + |(map_start, map)| { + if map.alloc_id == alloc_id { Some(map_start.bytes()) } else { None } + }, + ) + .or_else(|| ecx.machine.mappings.pending.as_ref().map(|r| r.start.bytes())) + .unwrap_or_else(|| GlobalStateInner::alloc_base_addr(ecx, alloc_id)); // Add offset with the right kind of pointer-overflowing arithmetic. let dl = ecx.data_layout(); diff --git a/src/machine.rs b/src/machine.rs index 01a3d7550e..ff377b712a 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::cell::RefCell; use std::fmt; +use std::ops::Range; use rand::rngs::StdRng; use rand::SeedableRng; @@ -104,6 +105,8 @@ pub enum MiriMemoryKind { /// Memory for thread-local statics. /// This memory may leak. Tls, + /// Memory mapped directly by the program + Mmap, } impl From for MemoryKind { @@ -119,7 +122,7 @@ impl MayLeak for MiriMemoryKind { use self::MiriMemoryKind::*; match self { Rust | Miri | C | WinHeap | Runtime => false, - Machine | Global | ExternStatic | Tls => true, + Machine | Global | ExternStatic | Tls | Mmap => true, } } } @@ -137,6 +140,7 @@ impl fmt::Display for MiriMemoryKind { Global => write!(f, "global (static or const)"), ExternStatic => write!(f, "extern static"), Tls => write!(f, "thread-local static"), + Mmap => write!(f, "mmap"), } } } @@ -342,6 +346,39 @@ impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Mapping { + pub alloc_id: AllocId, + pub can_read: bool, + pub can_write: bool, +} + +#[derive(Debug)] +pub struct Mappings { + all_memory: RangeMap>, + pub pending: Option>, +} + +impl Mappings { + fn new() -> Self { + Mappings { all_memory: RangeMap::new(Size::from_bytes(u64::MAX), None), pending: None } + } + + pub fn iter_mut( + &mut self, + start: Size, + end: Size, + ) -> impl Iterator)> { + self.all_memory.iter_mut(start, end) + } + + pub fn maps(&self) -> impl Iterator { + self.all_memory.iter_all().filter_map(|(offset, chunk)| { + chunk.as_ref().map(|c| (Size::from_bytes(offset.start), c)) + }) + } +} + /// The machine itself. /// /// If you add anything here that stores machine values, remember to update @@ -376,6 +413,9 @@ pub struct MiriMachine<'mir, 'tcx> { /// TLS state. pub(crate) tls: TlsData<'tcx>, + /// Mappings established through mmap + pub(crate) mappings: Mappings, + /// What should Miri do when an op requires communicating with the host, /// such as accessing host env vars, random number generation, and /// file system access. @@ -518,6 +558,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { argv: None, cmd_line: None, tls: TlsData::default(), + mappings: Mappings::new(), isolated_op: config.isolated_op, validate: config.validate, enforce_abi: config.check_abi, @@ -674,6 +715,14 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { let def_id = frame.instance.def_id(); def_id.is_local() || self.local_crates.contains(&def_id.krate) } + + pub(crate) fn get_mapping(&self, alloc_id: AllocId) -> Option<&Mapping> { + self.mappings + .all_memory + .iter_all() + .filter_map(|(_offset, map)| map.as_ref()) + .find(|m| m.alloc_id == alloc_id) + } } impl VisitTags for MiriMachine<'_, '_> { @@ -722,6 +771,7 @@ impl VisitTags for MiriMachine<'_, '_> { page_size: _, stack_addr: _, stack_size: _, + mappings: _, } = self; threads.visit_tags(visit); @@ -1046,6 +1096,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { (alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra), range: AllocRange, ) -> InterpResult<'tcx> { + if let Some(map) = machine.get_mapping(alloc_id) { + if !map.can_read { + throw_ub_format!("{alloc_id:?} is a mapping that does not allow reads"); + } + } if let Some(data_race) = &alloc_extra.data_race { data_race.read(alloc_id, range, machine)?; } @@ -1066,6 +1121,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { (alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra), range: AllocRange, ) -> InterpResult<'tcx> { + if let Some(map) = machine.get_mapping(alloc_id) { + if !map.can_write { + throw_ub_format!("{alloc_id:?} is a mapping that does not allow writes"); + } + } if let Some(data_race) = &mut alloc_extra.data_race { data_race.write(alloc_id, range, machine)?; } diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index d018a7ea25..17fbc11db8 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -10,6 +10,7 @@ use rustc_target::spec::abi::Abi; use crate::*; use shims::foreign_items::EmulateByNameResult; use shims::unix::fs::EvalContextExt as _; +use shims::unix::mem::EvalContextExt as _; use shims::unix::sync::EvalContextExt as _; use shims::unix::thread::EvalContextExt as _; @@ -213,6 +214,30 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } + "mmap" => { + let [addr, length, prot, flags, fd, offset] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?; + if let Some(ptr) = this.mmap(addr, length, prot, flags, fd, offset)? { + this.write_pointer(ptr, dest)?; + } else { + this.write_null(dest)?; + } + } + "mremap" => { + let [old_address, old_size, new_size, flags] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?; + let result = this.mremap(old_address, old_size, new_size, flags)?; + this.write_pointer(result, dest)?; + } + "munmap" => { + let [addr, length] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?; + let result = this.munmap(addr, length)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "mprotect" => { + let [addr, length, prot] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?; + let result = this.mprotect(addr, length, prot)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + // Dynamic symbol loading "dlsym" => { let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -544,7 +569,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_null(dest)?; } | "sigaction" - | "mprotect" if this.frame_in_std() => { let [_, _, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; this.write_null(dest)?; diff --git a/src/shims/unix/macos/foreign_items.rs b/src/shims/unix/macos/foreign_items.rs index a55b0ee523..07dbfbc819 100644 --- a/src/shims/unix/macos/foreign_items.rs +++ b/src/shims/unix/macos/foreign_items.rs @@ -197,16 +197,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(res, dest)?; } - // Incomplete shims that we "stub out" just to get pre-main initialization code to work. - // These shims are enabled only when the caller is in the standard library. - "mmap" if this.frame_in_std() => { - // This is a horrible hack, but since the guard page mechanism calls mmap and expects a particular return value, we just give it that value. - let [addr, _, _, _, _, _] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let addr = this.read_scalar(addr)?; - this.write_scalar(addr, dest)?; - } - _ => return Ok(EmulateByNameResult::NotSupported), }; diff --git a/src/shims/unix/mem.rs b/src/shims/unix/mem.rs new file mode 100644 index 0000000000..d48e74d7e5 --- /dev/null +++ b/src/shims/unix/mem.rs @@ -0,0 +1,244 @@ +use crate::machine::Mapping; +use crate::*; +use rustc_target::abi::{Align, Size}; + +const PAGE_SIZE: u64 = 4096; + +fn round_up_to_multiple_of_page_size(length: u64) -> Option { + #[allow(clippy::integer_arithmetic)] // PAGE_SIZE is nonzero + (length.checked_add(PAGE_SIZE - 1)? / PAGE_SIZE).checked_mul(PAGE_SIZE) +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn mmap( + &mut self, + addr: &OpTy<'tcx, Provenance>, + length: &OpTy<'tcx, Provenance>, + prot: &OpTy<'tcx, Provenance>, + flags: &OpTy<'tcx, Provenance>, + fd: &OpTy<'tcx, Provenance>, + offset: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Option>> { + let this = self.eval_context_mut(); + + let addr = this.read_pointer(addr)?.into_parts().1; + let length = this.read_scalar(length)?.to_machine_usize(this)?; + let prot = this.read_scalar(prot)?.to_i32()?; + let flags = this.read_scalar(flags)?.to_i32()?; + let fd = this.read_scalar(fd)?.to_i32()?; + let offset = this.read_scalar(offset)?.to_machine_usize(this)?; + + let prot_read = this.eval_libc_i32("PROT_READ"); + let prot_write = this.eval_libc_i32("PROT_WRITE"); + let map_private = this.eval_libc_i32("MAP_PRIVATE"); + let map_anonymous = this.eval_libc_i32("MAP_ANONYMOUS"); + let map_fixed = this.eval_libc_i32("MAP_FIXED"); + + // Only one of MAP_PRIVATE, MAP_SHARED, or MAP_SHARED_VALIDATE may be passed + if flags & this.eval_libc_i32("MAP_PRIVATE") == 0 { + throw_unsup_format!("Miri does not support MAP_SHARED or MAP_SHARED_VALIDATE"); + } + + // MacOS does not have MAP_STACK + if this.tcx.sess.target.os != "macos" { + if flags & this.eval_libc_i32("MAP_STACK") > 0 { + throw_unsup_format!("Miri does not support MAP_STACK"); + } + } + + if prot & this.eval_libc_i32("PROT_EXEC") > 0 { + throw_unsup_format!("Miri does not support mapping executable pages"); + } + + if offset != 0 { + throw_unsup_format!("Miri does not support non-zero offsets to mmap (yet)"); + } + + if fd != -1 { + throw_unsup_format!("Miri does not support file-backed memory mappings"); + } + + if length == 0 { + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + return Ok(None); + } + + let align = Align::from_bytes(PAGE_SIZE).unwrap(); + let map_length = round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX); + + if (flags == map_private | map_anonymous) + || (flags == map_private | map_anonymous | map_fixed) + { + this.machine.mappings.pending = if flags & map_fixed > 0 { + Some(addr..addr + Size::from_bytes(map_length)) + } else { + None + }; + + // mmap as a memory allocator + let ptr = this.allocate_ptr( + Size::from_bytes(map_length), + align, + MiriMemoryKind::Mmap.into(), + )?; + + let (prov, mapped_addr) = ptr.into_parts(); + let alloc_id = match prov { + Provenance::Concrete { alloc_id, .. } => alloc_id, + Provenance::Wildcard => + unreachable!("allocate_ptr should not return a Wildcard pointer"), + }; + + this.machine.mappings.iter_mut(mapped_addr, Size::from_bytes(map_length)).for_each( + |m| { + *m.1 = Some(Mapping { alloc_id, can_read: true, can_write: true }); + }, + ); + + this.machine.mappings.pending = None; + + // We just allocated this, the access is definitely in-bounds and fits into our address space. + // mmap guarantees new mappings are zero-init. + this.write_bytes_ptr( + ptr.into(), + std::iter::repeat(0u8).take(usize::try_from(map_length).unwrap()), + ) + .unwrap(); + + this.machine.mappings.iter_mut(mapped_addr, Size::from_bytes(map_length)).for_each( + |(_offset, map)| { + if let Some(m) = map { + m.can_read = prot & prot_read > 0; + m.can_write = prot & prot_write > 0; + } + }, + ); + + Ok(Some(ptr)) + } else { + let addr = addr.bytes(); + throw_unsup_format!( + "mmap is not supported with arguments: (addr: {addr}, length: {length}, prot: {prot:x}, flags: {flags:0x}, fd: {fd}, offset: {offset})" + ); + } + } + + fn mremap( + &mut self, + old_address: &OpTy<'tcx, Provenance>, + old_size: &OpTy<'tcx, Provenance>, + new_size: &OpTy<'tcx, Provenance>, + flags: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + + // This is actually a pointer but we really do not care about its provenance + let old_address = this.read_scalar(old_address)?.to_machine_usize(this)?; + let old_address = Size::from_bytes(old_address); + + let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?; + let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?; + let flags = this.read_scalar(flags)?.to_i32()?; + + if flags & this.eval_libc_i32("MREMAP_FIXED") > 0 { + throw_unsup_format!("Miri does not support mremap wth MREMAP_FIXED"); + } + + if flags & this.eval_libc_i32("MREMAP_DONTUNMAP") > 0 { + throw_unsup_format!("Miri does not support mremap wth MREMAP_DONTUNMAP"); + } + + if flags & this.eval_libc_i32("MREMAP_MAYMOVE") == 0 { + // We only support MREMAP_MAYMOVE, so not passing the flag is just a failure + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + return Ok(Pointer::null()); + } + + // Remove all mappings associated with the range specified by old_address and old_size + this.machine.mappings.iter_mut(old_address, Size::from_bytes(old_size)).for_each( + |(_offset, map)| { + *map = None; + }, + ); + + let pointer = + this.realloc(Pointer::new(None, old_address), new_size, MiriMemoryKind::Mmap)?; + + let (prov, offset) = pointer.into_parts(); + let alloc_id = match prov { + Some(Provenance::Concrete { alloc_id, tag: _ }) => alloc_id, + _ => panic!("realloc should return a pointer with provenance"), + }; + let start = + intptrcast::GlobalStateInner::rel_ptr_to_addr(this, Pointer::new(alloc_id, offset)); + this.machine + .mappings + .iter_mut(Size::from_bytes(start), Size::from_bytes(new_size)) + .take(1) // "an existing mapping" meaning only one mapping starting at old_address? + .for_each(|(_offset, map)| { + if let Some(m) = map { + m.alloc_id = alloc_id; + } + }); + + Ok(pointer) + } + + fn munmap( + &mut self, + addr: &OpTy<'tcx, Provenance>, + length: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let addr = this.read_scalar(addr)?.to_machine_usize(this)?; + let length = this.read_scalar(length)?.to_machine_usize(this)?; + + // The address addr must be a multiple of the page size (but length need not be). + #[allow(clippy::integer_arithmetic)] // PAGE_SIZE is nonzero + if addr % PAGE_SIZE != 0 { + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + return Ok(-1); + } + + // All pages containing a part of the indicated range are unmapped. + let length = round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX); + + for (_offset, map) in + this.machine.mappings.iter_mut(Size::from_bytes(addr), Size::from_bytes(length)) + { + *map = None; + } + Ok(0) + } + + fn mprotect( + &mut self, + addr: &OpTy<'tcx, Provenance>, + length: &OpTy<'tcx, Provenance>, + prot: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + let addr = this.read_scalar(addr)?.to_machine_usize(this)?; + let length = this.read_scalar(length)?.to_machine_usize(this)?; + let prot = this.read_scalar(prot)?.to_i32()?; + + let prot_read = this.eval_libc_i32("PROT_READ"); + let prot_write = this.eval_libc_i32("PROT_WRITE"); + + // All pages containing a part of the indicated range are modified + let length = round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX); + + for (_offset, map) in + this.machine.mappings.iter_mut(Size::from_bytes(addr), Size::from_bytes(length)) + { + if let Some(m) = map { + m.can_read = prot & prot_read > 0; + m.can_write = prot & prot_write > 0; + } + } + + Ok(0) + } +} diff --git a/src/shims/unix/mod.rs b/src/shims/unix/mod.rs index 6fefb054f3..a8ebd369ab 100644 --- a/src/shims/unix/mod.rs +++ b/src/shims/unix/mod.rs @@ -2,6 +2,7 @@ pub mod dlsym; pub mod foreign_items; mod fs; +mod mem; mod sync; mod thread; diff --git a/tests/fail/mmap_invalid_dealloc.rs b/tests/fail/mmap_invalid_dealloc.rs new file mode 100644 index 0000000000..70f7a6a7ce --- /dev/null +++ b/tests/fail/mmap_invalid_dealloc.rs @@ -0,0 +1,18 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target-windows: No libc on Windows + +#![feature(rustc_private)] + +fn main() { + unsafe { + let ptr = libc::mmap( + std::ptr::null_mut(), + 4096, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + libc::free(ptr); //~ ERROR: which is mmap memory, using C heap deallocation operation + } +} diff --git a/tests/fail/mmap_invalid_dealloc.stderr b/tests/fail/mmap_invalid_dealloc.stderr new file mode 100644 index 0000000000..54e0cd5275 --- /dev/null +++ b/tests/fail/mmap_invalid_dealloc.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: deallocating ALLOC, which is mmap memory, using C heap deallocation operation + --> $DIR/mmap_invalid_dealloc.rs:LL:CC + | +LL | libc::free(ptr); + | ^^^^^^^^^^^^^^^ deallocating ALLOC, which is mmap memory, using C heap deallocation operation + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/mmap_invalid_dealloc.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/tests/fail/mmap_prot_none.rs b/tests/fail/mmap_prot_none.rs new file mode 100644 index 0000000000..f48b89adec --- /dev/null +++ b/tests/fail/mmap_prot_none.rs @@ -0,0 +1,18 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target-windows: No libc on Windows + +#![feature(rustc_private)] + +fn main() { + unsafe { + let ptr = libc::mmap( + std::ptr::null_mut(), + 4096, + libc::PROT_NONE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) as *mut u8; + let _byte = *ptr; //~ ERROR: is a mapping that does not allow reads + } +} diff --git a/tests/fail/mmap_prot_none.stderr b/tests/fail/mmap_prot_none.stderr new file mode 100644 index 0000000000..2524d99258 --- /dev/null +++ b/tests/fail/mmap_prot_none.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: ALLOC is a mapping that does not allow reads + --> $DIR/mmap_prot_none.rs:LL:CC + | +LL | let _byte = *ptr; + | ^^^^ ALLOC is a mapping that does not allow reads + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/mmap_prot_none.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/tests/fail/mmap_prot_read.rs b/tests/fail/mmap_prot_read.rs new file mode 100644 index 0000000000..dc1d27f8ae --- /dev/null +++ b/tests/fail/mmap_prot_read.rs @@ -0,0 +1,23 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target-windows: No libc on Windows + +#![feature(rustc_private)] + +fn main() { + unsafe { + let ptr = libc::mmap( + std::ptr::null_mut(), + 4096, + libc::PROT_READ, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) as *mut u8; + // Reads succeed + for offset in 0..4096 { + let _byte = *ptr.add(offset); + } + // But writes fail + *ptr = 1; //~ ERROR: is a mapping that does not allow writes + } +} diff --git a/tests/fail/mmap_prot_read.stderr b/tests/fail/mmap_prot_read.stderr new file mode 100644 index 0000000000..78daab08d5 --- /dev/null +++ b/tests/fail/mmap_prot_read.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: ALLOC is a mapping that does not allow writes + --> $DIR/mmap_prot_read.rs:LL:CC + | +LL | *ptr = 1; + | ^^^^^^^^ ALLOC is a mapping that does not allow writes + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/mmap_prot_read.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/tests/fail/mmap_prot_write.rs b/tests/fail/mmap_prot_write.rs new file mode 100644 index 0000000000..4d30fcd69a --- /dev/null +++ b/tests/fail/mmap_prot_write.rs @@ -0,0 +1,26 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target-windows: No libc on Windows + +#![feature(rustc_private)] + +fn main() { + unsafe { + let ptr = libc::mmap( + std::ptr::null_mut(), + 4096, + libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) as *mut u8; + + // FIXME: We need to use ptr::write_bytes or ptr::copy because those do untyped copies, and + // thus do not make Miri want to do validatio of the written bytes, which ICEs the + // compiler, because it can't read from the allocation. + + // Writes succeed + std::ptr::write_bytes(ptr, 1u8, 4096); + // But reads fail + let _byte = *ptr; //~ ERROR: is a mapping that does not allow reads + } +} diff --git a/tests/fail/mmap_prot_write.stderr b/tests/fail/mmap_prot_write.stderr new file mode 100644 index 0000000000..fc3cfc1c7e --- /dev/null +++ b/tests/fail/mmap_prot_write.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: ALLOC is a mapping that does not allow reads + --> $DIR/mmap_prot_write.rs:LL:CC + | +LL | let _byte = *ptr; + | ^^^^ ALLOC is a mapping that does not allow reads + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/mmap_prot_write.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/tests/pass-dep/shims/mmap.rs b/tests/pass-dep/shims/mmap.rs new file mode 100644 index 0000000000..7fab671027 --- /dev/null +++ b/tests/pass-dep/shims/mmap.rs @@ -0,0 +1,43 @@ +//@ignore-target-windows: No libc on Windows +//@compile-flags: -Zmiri-disable-isolation +#![feature(strict_provenance)] + +use std::{ptr, slice}; + +fn test_mmap() { + // Standard mmap to allocate memory + let ptr = unsafe { + libc::mmap( + std::ptr::null_mut(), + 4096, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) + }; + assert!(!ptr.is_null()); + let slice = unsafe { slice::from_raw_parts(ptr as *const u8, 4096) }; + assert_eq!(&[0u8; 4096], slice); + + let res = unsafe { libc::munmap(ptr, 4096) }; + assert_eq!(res, 0); + + // MAP_FIXED + let high_addr = 4096 * 4096; // A high page we are unlikely to collide with + let ptr = unsafe { + libc::mmap( + ptr::invalid_mut(high_addr), + 4096, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED, + -1, + 0, + ) + }; + assert_eq!(ptr.addr(), high_addr); +} + +fn main() { + test_mmap(); +}