about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/src/concurrency/data_race.rs3
-rw-r--r--src/tools/miri/src/machine.rs14
-rw-r--r--src/tools/miri/src/shims/unix/foreign_items.rs12
-rw-r--r--src/tools/miri/src/shims/unix/linux/foreign_items.rs7
-rw-r--r--src/tools/miri/src/shims/unix/linux/mem.rs67
-rw-r--r--src/tools/miri/src/shims/unix/linux/mod.rs1
-rw-r--r--src/tools/miri/src/shims/unix/macos/foreign_items.rs10
-rw-r--r--src/tools/miri/src/shims/unix/mem.rs158
-rw-r--r--src/tools/miri/src/shims/unix/mod.rs1
-rw-r--r--src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.rs18
-rw-r--r--src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.stderr15
-rw-r--r--src/tools/miri/tests/fail/shims/mmap_use_after_munmap.rs19
-rw-r--r--src/tools/miri/tests/fail/shims/mmap_use_after_munmap.stderr30
-rw-r--r--src/tools/miri/tests/fail/shims/munmap.rs22
-rw-r--r--src/tools/miri/tests/fail/shims/munmap.stderr39
-rw-r--r--src/tools/miri/tests/fail/shims/munmap_partial.rs18
-rw-r--r--src/tools/miri/tests/fail/shims/munmap_partial.stderr29
-rw-r--r--src/tools/miri/tests/pass-dep/shims/mmap.rs70
18 files changed, 521 insertions, 12 deletions
diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs
index f6252c43f9f..84ef27f7365 100644
--- a/src/tools/miri/src/concurrency/data_race.rs
+++ b/src/tools/miri/src/concurrency/data_race.rs
@@ -714,7 +714,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/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index 0ba7dad5649..f88ad040e05 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -112,6 +112,8 @@ pub enum MiriMemoryKind {
     /// Memory for thread-local statics.
     /// This memory may leak.
     Tls,
+    /// Memory mapped directly by the program
+    Mmap,
 }
 
 impl From<MiriMemoryKind> for MemoryKind<MiriMemoryKind> {
@@ -127,7 +129,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,
         }
     }
 }
@@ -145,6 +147,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"),
         }
     }
 }
@@ -726,6 +729,15 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
         // will panic when given the file.
         drop(self.profiler.take());
     }
+
+    pub(crate) fn round_up_to_multiple_of_page_size(&self, length: u64) -> Option<u64> {
+        #[allow(clippy::arithmetic_side_effects)] // page size is nonzero
+        (length.checked_add(self.page_size - 1)? / self.page_size).checked_mul(self.page_size)
+    }
+
+    pub(crate) fn page_align(&self) -> Align {
+        Align::from_bytes(self.page_size).unwrap()
+    }
 }
 
 impl VisitTags for MiriMachine<'_, '_> {
diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs
index d2b9430872c..4931f368857 100644
--- a/src/tools/miri/src/shims/unix/foreign_items.rs
+++ b/src/tools/miri/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,17 @@ 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)?;
+                let ptr = this.mmap(addr, length, prot, flags, fd, offset)?;
+                this.write_scalar(ptr, 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(result, dest)?;
+            }
+
             // Dynamic symbol loading
             "dlsym" => {
                 let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
index 5985d29fabd..a97cc49d88e 100644
--- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
@@ -7,6 +7,7 @@ use crate::*;
 use shims::foreign_items::EmulateByNameResult;
 use shims::unix::fs::EvalContextExt as _;
 use shims::unix::linux::fd::EvalContextExt as _;
+use shims::unix::linux::mem::EvalContextExt as _;
 use shims::unix::linux::sync::futex;
 use shims::unix::sync::EvalContextExt as _;
 use shims::unix::thread::EvalContextExt as _;
@@ -68,6 +69,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 let result = this.eventfd(val, flag)?;
                 this.write_scalar(result, dest)?;
             }
+            "mremap" => {
+                let [old_address, old_size, new_size, flags] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let ptr = this.mremap(old_address, old_size, new_size, flags)?;
+                this.write_scalar(ptr, dest)?;
+            }
             "socketpair" => {
                 let [domain, type_, protocol, sv] =
                     this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
diff --git a/src/tools/miri/src/shims/unix/linux/mem.rs b/src/tools/miri/src/shims/unix/linux/mem.rs
new file mode 100644
index 00000000000..026fd6ae5e7
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/linux/mem.rs
@@ -0,0 +1,67 @@
+//! This follows the pattern in src/shims/unix/mem.rs: We only support uses of mremap that would
+//! correspond to valid uses of realloc.
+
+use crate::*;
+use rustc_target::abi::Size;
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    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, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let old_address = this.read_target_usize(old_address)?;
+        let old_size = this.read_target_usize(old_size)?;
+        let new_size = this.read_target_usize(new_size)?;
+        let flags = this.read_scalar(flags)?.to_i32()?;
+
+        // old_address must be a multiple of the page size
+        #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
+        if old_address % this.machine.page_size != 0 || new_size == 0 {
+            this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
+            return Ok(this.eval_libc("MAP_FAILED"));
+        }
+
+        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(Scalar::from_maybe_pointer(Pointer::null(), this));
+        }
+
+        let old_address = Machine::ptr_from_addr_cast(this, old_address)?;
+        let align = this.machine.page_align();
+        let ptr = this.reallocate_ptr(
+            old_address,
+            Some((Size::from_bytes(old_size), align)),
+            Size::from_bytes(new_size),
+            align,
+            MiriMemoryKind::Mmap.into(),
+        )?;
+        if let Some(increase) = new_size.checked_sub(old_size) {
+            // 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.offset(Size::from_bytes(old_size), this).unwrap().into(),
+                std::iter::repeat(0u8).take(usize::try_from(increase).unwrap()),
+            )
+            .unwrap();
+        }
+        // Memory mappings are always exposed
+        Machine::expose_ptr(this, ptr)?;
+
+        Ok(Scalar::from_pointer(ptr, this))
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/linux/mod.rs b/src/tools/miri/src/shims/unix/linux/mod.rs
index 437764c3824..856ec226de8 100644
--- a/src/tools/miri/src/shims/unix/linux/mod.rs
+++ b/src/tools/miri/src/shims/unix/linux/mod.rs
@@ -1,4 +1,5 @@
 pub mod dlsym;
 pub mod fd;
 pub mod foreign_items;
+pub mod mem;
 pub mod sync;
diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
index 1271788a97e..85b950da4fe 100644
--- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs
+++ b/src/tools/miri/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/tools/miri/src/shims/unix/mem.rs b/src/tools/miri/src/shims/unix/mem.rs
new file mode 100644
index 00000000000..a33d784d166
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/mem.rs
@@ -0,0 +1,158 @@
+//! This is an incomplete implementation of mmap/munmap which is restricted in order to be
+//! implementable on top of the existing memory system. The point of these function as-written is
+//! to allow memory allocators written entirely in Rust to be executed by Miri. This implementation
+//! does not support other uses of mmap such as file mappings.
+//!
+//! mmap/munmap behave a lot like alloc/dealloc, and for simple use they are exactly
+//! equivalent. That is the only part we support: no MAP_FIXED or MAP_SHARED or anything
+//! else that goes beyond a basic allocation API.
+
+use crate::*;
+use rustc_target::abi::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, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        // We do not support MAP_FIXED, so the addr argument is always ignored (except for the MacOS hack)
+        let addr = this.read_target_usize(addr)?;
+        let length = this.read_target_usize(length)?;
+        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_target_usize(offset)?;
+
+        let map_private = this.eval_libc_i32("MAP_PRIVATE");
+        let map_anonymous = this.eval_libc_i32("MAP_ANONYMOUS");
+        let map_shared = this.eval_libc_i32("MAP_SHARED");
+        let map_fixed = this.eval_libc_i32("MAP_FIXED");
+
+        // This is a horrible hack, but on MacOS the guard page mechanism uses mmap
+        // in a way we do not support. We just give it the return value it expects.
+        if this.frame_in_std() && this.tcx.sess.target.os == "macos" && (flags & map_fixed) != 0 {
+            return Ok(Scalar::from_maybe_pointer(Pointer::from_addr_invalid(addr), this));
+        }
+
+        let prot_read = this.eval_libc_i32("PROT_READ");
+        let prot_write = this.eval_libc_i32("PROT_WRITE");
+
+        // First, we do some basic argument validation as required by mmap
+        if (flags & (map_private | map_shared)).count_ones() != 1 {
+            this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
+            return Ok(Scalar::from_maybe_pointer(Pointer::null(), this));
+        }
+        if length == 0 {
+            this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
+            return Ok(Scalar::from_maybe_pointer(Pointer::null(), this));
+        }
+
+        // If a user tries to map a file, we want to loudly inform them that this is not going
+        // to work. It is possible that POSIX gives us enough leeway to return an error, but the
+        // outcome for the user (I need to add cfg(miri)) is the same, just more frustrating.
+        if fd != -1 {
+            throw_unsup_format!("Miri does not support file-backed memory mappings");
+        }
+
+        // POSIX says:
+        // [ENOTSUP]
+        // * MAP_FIXED or MAP_PRIVATE was specified in the flags argument and the implementation
+        // does not support this functionality.
+        // * The implementation does not support the combination of accesses requested in the
+        // prot argument.
+        //
+        // Miri doesn't support MAP_FIXED or any any protections other than PROT_READ|PROT_WRITE.
+        if flags & map_fixed != 0 || prot != prot_read | prot_write {
+            this.set_last_error(Scalar::from_i32(this.eval_libc_i32("ENOTSUP")))?;
+            return Ok(Scalar::from_maybe_pointer(Pointer::null(), this));
+        }
+
+        // Miri does not support shared mappings, or any of the other extensions that for example
+        // Linux has added to the flags arguments.
+        if flags != map_private | map_anonymous {
+            throw_unsup_format!(
+                "Miri only supports calls to mmap which set the flags argument to MAP_PRIVATE|MAP_ANONYMOUS"
+            );
+        }
+
+        // This is only used for file mappings, which we don't support anyway.
+        if offset != 0 {
+            throw_unsup_format!("Miri does not support non-zero offsets to mmap");
+        }
+
+        let align = this.machine.page_align();
+        let map_length = this.machine.round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX);
+
+        let ptr =
+            this.allocate_ptr(Size::from_bytes(map_length), align, MiriMemoryKind::Mmap.into())?;
+        // 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();
+        // Memory mappings don't use provenance, and are always exposed.
+        Machine::expose_ptr(this, ptr)?;
+
+        Ok(Scalar::from_pointer(ptr, this))
+    }
+
+    fn munmap(
+        &mut self,
+        addr: &OpTy<'tcx, Provenance>,
+        length: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let addr = this.read_target_usize(addr)?;
+        let length = this.read_target_usize(length)?;
+
+        // addr must be a multiple of the page size
+        #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
+        if addr % this.machine.page_size != 0 {
+            this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
+            return Ok(Scalar::from_i32(-1));
+        }
+
+        let length = this.machine.round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX);
+
+        let ptr = Machine::ptr_from_addr_cast(this, addr)?;
+
+        let Ok(ptr) = ptr.into_pointer_or_addr() else {
+            throw_unsup_format!("Miri only supports munmap on memory allocated directly by mmap");
+        };
+        let Some((alloc_id, offset, _prov)) = Machine::ptr_get_alloc(this, ptr) else {
+            throw_unsup_format!("Miri only supports munmap on memory allocated directly by mmap");
+        };
+
+        // Elsewhere in this function we are careful to check what we can and throw an unsupported
+        // error instead of Undefined Behavior when use of this function falls outside of the
+        // narrow scope we support. We deliberately do not check the MemoryKind of this allocation,
+        // because we want to report UB on attempting to unmap memory that Rust "understands", such
+        // the stack, heap, or statics.
+        let (_kind, alloc) = this.memory.alloc_map().get(alloc_id).unwrap();
+        if offset != Size::ZERO || alloc.len() as u64 != length {
+            throw_unsup_format!(
+                "Miri only supports munmap calls that exactly unmap a region previously returned by mmap"
+            );
+        }
+
+        let len = Size::from_bytes(alloc.len() as u64);
+        this.deallocate_ptr(
+            ptr.into(),
+            Some((len, this.machine.page_align())),
+            MemoryKind::Machine(MiriMemoryKind::Mmap),
+        )?;
+
+        Ok(Scalar::from_i32(0))
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs
index 6fefb054f3c..a8ebd369aba 100644
--- a/src/tools/miri/src/shims/unix/mod.rs
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.rs b/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.rs
new file mode 100644
index 00000000000..70f7a6a7cef
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/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/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.stderr b/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.stderr
new file mode 100644
index 00000000000..54e0cd5275d
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/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/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.rs b/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.rs
new file mode 100644
index 00000000000..1e00bc6b64f
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.rs
@@ -0,0 +1,19 @@
+//@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::munmap(ptr, 4096);
+        let _x = *(ptr as *mut u8); //~ ERROR: was dereferenced after this allocation got freed
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.stderr b/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.stderr
new file mode 100644
index 00000000000..f90701d400c
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.stderr
@@ -0,0 +1,30 @@
+warning: integer-to-pointer cast
+  --> $DIR/mmap_use_after_munmap.rs:LL:CC
+   |
+LL |         libc::munmap(ptr, 4096);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
+   |
+   = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,
+   = help: which means that Miri might miss pointer bugs in this program.
+   = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation.
+   = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead.
+   = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics.
+   = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning.
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/mmap_use_after_munmap.rs:LL:CC
+
+error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
+  --> $DIR/mmap_use_after_munmap.rs:LL:CC
+   |
+LL |         let _x = *(ptr as *mut u8);
+   |                  ^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
+   |
+   = 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_use_after_munmap.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error; 1 warning emitted
+
diff --git a/src/tools/miri/tests/fail/shims/munmap.rs b/src/tools/miri/tests/fail/shims/munmap.rs
new file mode 100644
index 00000000000..453437a06cf
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/munmap.rs
@@ -0,0 +1,22 @@
+//@compile-flags: -Zmiri-disable-isolation
+//@ignore-target-windows: No libc on Windows
+
+#![feature(rustc_private)]
+#![feature(strict_provenance)]
+
+use std::ptr;
+
+fn main() {
+    // Linux specifies that it is not an error if the specified range does not contain any pages.
+    // But we simply do not support such calls. This test checks that we report this as
+    // unsupported, not Undefined Behavior.
+    let res = unsafe {
+        libc::munmap(
+            //~^ ERROR: unsupported operation
+            // Some high address we surely have not allocated anything at
+            ptr::invalid_mut(1 << 30),
+            4096,
+        )
+    };
+    assert_eq!(res, 0);
+}
diff --git a/src/tools/miri/tests/fail/shims/munmap.stderr b/src/tools/miri/tests/fail/shims/munmap.stderr
new file mode 100644
index 00000000000..cb47769c063
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/munmap.stderr
@@ -0,0 +1,39 @@
+warning: integer-to-pointer cast
+  --> $DIR/munmap.rs:LL:CC
+   |
+LL | /         libc::munmap(
+LL | |
+LL | |             // Some high address we surely have not allocated anything at
+LL | |             ptr::invalid_mut(1 << 30),
+LL | |             4096,
+LL | |         )
+   | |_________^ integer-to-pointer cast
+   |
+   = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,
+   = help: which means that Miri might miss pointer bugs in this program.
+   = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation.
+   = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead.
+   = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics.
+   = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning.
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/munmap.rs:LL:CC
+
+error: unsupported operation: Miri only supports munmap on memory allocated directly by mmap
+  --> $DIR/munmap.rs:LL:CC
+   |
+LL | /         libc::munmap(
+LL | |
+LL | |             // Some high address we surely have not allocated anything at
+LL | |             ptr::invalid_mut(1 << 30),
+LL | |             4096,
+LL | |         )
+   | |_________^ Miri only supports munmap on memory allocated directly by mmap
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/munmap.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error; 1 warning emitted
+
diff --git a/src/tools/miri/tests/fail/shims/munmap_partial.rs b/src/tools/miri/tests/fail/shims/munmap_partial.rs
new file mode 100644
index 00000000000..938850ee286
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/munmap_partial.rs
@@ -0,0 +1,18 @@
+//! Our mmap/munmap support is a thin wrapper over Interpcx::allocate_ptr. Since the underlying
+//! layer has much more UB than munmap does, we need to be sure we throw an unsupported error here.
+//@ignore-target-windows: No libc on Windows
+
+fn main() {
+    unsafe {
+        let ptr = libc::mmap(
+            std::ptr::null_mut(),
+            page_size::get() * 2,
+            libc::PROT_READ | libc::PROT_WRITE,
+            libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
+            -1,
+            0,
+        );
+        libc::munmap(ptr, 1);
+        //~^ ERROR: unsupported operation
+    }
+}
diff --git a/src/tools/miri/tests/fail/shims/munmap_partial.stderr b/src/tools/miri/tests/fail/shims/munmap_partial.stderr
new file mode 100644
index 00000000000..9a084c50437
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/munmap_partial.stderr
@@ -0,0 +1,29 @@
+warning: integer-to-pointer cast
+  --> $DIR/munmap_partial.rs:LL:CC
+   |
+LL |         libc::munmap(ptr, 1);
+   |         ^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
+   |
+   = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,
+   = help: which means that Miri might miss pointer bugs in this program.
+   = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation.
+   = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead.
+   = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics.
+   = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning.
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/munmap_partial.rs:LL:CC
+
+error: unsupported operation: Miri only supports munmap calls that exactly unmap a region previously returned by mmap
+  --> $DIR/munmap_partial.rs:LL:CC
+   |
+LL |         libc::munmap(ptr, 1);
+   |         ^^^^^^^^^^^^^^^^^^^^ Miri only supports munmap calls that exactly unmap a region previously returned by mmap
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/munmap_partial.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error; 1 warning emitted
+
diff --git a/src/tools/miri/tests/pass-dep/shims/mmap.rs b/src/tools/miri/tests/pass-dep/shims/mmap.rs
new file mode 100644
index 00000000000..03ffc3b3898
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/mmap.rs
@@ -0,0 +1,70 @@
+//@ignore-target-windows: No libc on Windows
+//@compile-flags: -Zmiri-disable-isolation -Zmiri-permissive-provenance
+#![feature(strict_provenance)]
+
+use std::{ptr, slice};
+
+fn test_mmap() {
+    let page_size = page_size::get();
+    let ptr = unsafe {
+        libc::mmap(
+            ptr::null_mut(),
+            page_size,
+            libc::PROT_READ | libc::PROT_WRITE,
+            libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
+            -1,
+            0,
+        )
+    };
+    assert!(!ptr.is_null());
+
+    // Ensure that freshly mapped allocations are zeroed
+    let slice = unsafe { slice::from_raw_parts_mut(ptr as *mut u8, page_size) };
+    assert!(slice.iter().all(|b| *b == 0));
+
+    // Do some writes, make sure they worked
+    for b in slice.iter_mut() {
+        *b = 1;
+    }
+    assert!(slice.iter().all(|b| *b == 1));
+
+    // Ensure that we can munmap with just an integer
+    let just_an_address = ptr::invalid_mut(ptr.addr());
+    let res = unsafe { libc::munmap(just_an_address, page_size) };
+    assert_eq!(res, 0i32);
+}
+
+#[cfg(target_os = "linux")]
+fn test_mremap() {
+    let page_size = page_size::get();
+    let ptr = unsafe {
+        libc::mmap(
+            ptr::null_mut(),
+            page_size,
+            libc::PROT_READ | libc::PROT_WRITE,
+            libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
+            -1,
+            0,
+        )
+    };
+    let slice = unsafe { slice::from_raw_parts_mut(ptr as *mut u8, page_size) };
+    for b in slice.iter_mut() {
+        *b = 1;
+    }
+
+    let ptr = unsafe { libc::mremap(ptr, page_size, page_size * 2, libc::MREMAP_MAYMOVE) };
+    assert!(!ptr.is_null());
+
+    let slice = unsafe { slice::from_raw_parts_mut(ptr as *mut u8, page_size * 2) };
+    assert!(&slice[..page_size].iter().all(|b| *b == 1));
+    assert!(&slice[page_size..].iter().all(|b| *b == 0));
+
+    let res = unsafe { libc::munmap(ptr, page_size * 2) };
+    assert_eq!(res, 0i32);
+}
+
+fn main() {
+    test_mmap();
+    #[cfg(target_os = "linux")]
+    test_mremap();
+}