1//! This is an incomplete implementation of mmap/munmap which is restricted in order to be
2//! implementable on top of the existing memory system. The point of these function as-written is
3//! to allow memory allocators written entirely in Rust to be executed by Miri. This implementation
4//! does not support other uses of mmap such as file mappings.
5//!
6//! mmap/munmap behave a lot like alloc/dealloc, and for simple use they are exactly
7//! equivalent. That is the only part we support: no MAP_FIXED or MAP_SHARED or anything
8//! else that goes beyond a basic allocation API.
9//!
10//! Note that in addition to only supporting malloc-like calls to mmap, we only support free-like
11//! calls to munmap, but for a very different reason. In principle, according to the man pages, it
12//! is possible to unmap arbitrary regions of address space. But in a high-level language like Rust
13//! this amounts to partial deallocation, which LLVM does not support. So any attempt to call our
14//! munmap shim which would partially unmap a region of address space previously mapped by mmap will
15//! report UB.
1617use rustc_abi::Size;
1819use crate::*;
2021impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
22pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
23fn mmap(
24&mut self,
25 addr: &OpTy<'tcx>,
26 length: &OpTy<'tcx>,
27 prot: &OpTy<'tcx>,
28 flags: &OpTy<'tcx>,
29 fd: &OpTy<'tcx>,
30 offset: i128,
31 ) -> InterpResult<'tcx, Scalar> {
32let this = self.eval_context_mut();
3334// We do not support MAP_FIXED, so the addr argument is always ignored (except for the MacOS hack)
35let addr = this.read_target_usize(addr)?;
36let length = this.read_target_usize(length)?;
37let prot = this.read_scalar(prot)?.to_i32()?;
38let flags = this.read_scalar(flags)?.to_i32()?;
39let fd = this.read_scalar(fd)?.to_i32()?;
4041let map_private = this.eval_libc_i32("MAP_PRIVATE");
42let map_anonymous = this.eval_libc_i32("MAP_ANONYMOUS");
43let map_shared = this.eval_libc_i32("MAP_SHARED");
44let map_fixed = this.eval_libc_i32("MAP_FIXED");
4546// This is a horrible hack, but on MacOS and Solarish the guard page mechanism uses mmap
47 // in a way we do not support. We just give it the return value it expects.
48if this.frame_in_std()
49 && matches!(&*this.tcx.sess.target.os, "macos" | "solaris" | "illumos")
50 && (flags & map_fixed) != 0
51{
52return interp_ok(Scalar::from_maybe_pointer(Pointer::from_addr_invalid(addr), this));
53 }
5455let prot_read = this.eval_libc_i32("PROT_READ");
56let prot_write = this.eval_libc_i32("PROT_WRITE");
5758// First, we do some basic argument validation as required by mmap
59if (flags & (map_private | map_shared)).count_ones() != 1 {
60 this.set_last_error(LibcError("EINVAL"))?;
61return interp_ok(this.eval_libc("MAP_FAILED"));
62 }
63if length == 0 {
64 this.set_last_error(LibcError("EINVAL"))?;
65return interp_ok(this.eval_libc("MAP_FAILED"));
66 }
6768// If a user tries to map a file, we want to loudly inform them that this is not going
69 // to work. It is possible that POSIX gives us enough leeway to return an error, but the
70 // outcome for the user (I need to add cfg(miri)) is the same, just more frustrating.
71if fd != -1 {
72throw_unsup_format!("Miri does not support file-backed memory mappings");
73 }
7475// Miri doesn't support MAP_FIXED.
76if flags & map_fixed != 0 {
77throw_unsup_format!(
78"Miri does not support calls to mmap with MAP_FIXED as part of the flags argument",
79 );
80 }
8182// Miri doesn't support protections other than PROT_READ|PROT_WRITE.
83if prot != prot_read | prot_write {
84throw_unsup_format!(
85"Miri does not support calls to mmap with protections other than \
86 PROT_READ|PROT_WRITE",
87 );
88 }
8990// Miri does not support shared mappings, or any of the other extensions that for example
91 // Linux has added to the flags arguments.
92if flags != map_private | map_anonymous {
93throw_unsup_format!(
94"Miri only supports calls to mmap which set the flags argument to \
95 MAP_PRIVATE|MAP_ANONYMOUS",
96 );
97 }
9899// This is only used for file mappings, which we don't support anyway.
100if offset != 0 {
101throw_unsup_format!("Miri does not support non-zero offsets to mmap");
102 }
103104let align = this.machine.page_align();
105let Some(map_length) = length.checked_next_multiple_of(this.machine.page_size) else {
106 this.set_last_error(LibcError("EINVAL"))?;
107return interp_ok(this.eval_libc("MAP_FAILED"));
108 };
109if map_length > this.target_usize_max() {
110 this.set_last_error(LibcError("EINVAL"))?;
111return interp_ok(this.eval_libc("MAP_FAILED"));
112 }
113114let ptr = this.allocate_ptr(
115 Size::from_bytes(map_length),
116 align,
117 MiriMemoryKind::Mmap.into(),
118// mmap guarantees new mappings are zero-init.
119AllocInit::Zero,
120 )?;
121122 interp_ok(Scalar::from_pointer(ptr, this))
123 }
124125fn munmap(&mut self, addr: &OpTy<'tcx>, length: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
126let this = self.eval_context_mut();
127128let addr = this.read_pointer(addr)?;
129let length = this.read_target_usize(length)?;
130131// addr must be a multiple of the page size, but apart from that munmap is just implemented
132 // as a dealloc.
133#[expect(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
134if addr.addr().bytes() % this.machine.page_size != 0 {
135return this.set_last_error_and_return_i32(LibcError("EINVAL"));
136 }
137138let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else {
139return this.set_last_error_and_return_i32(LibcError("EINVAL"));
140 };
141if length > this.target_usize_max() {
142 this.set_last_error(LibcError("EINVAL"))?;
143return interp_ok(this.eval_libc("MAP_FAILED"));
144 }
145146let length = Size::from_bytes(length);
147 this.deallocate_ptr(
148 addr,
149Some((length, this.machine.page_align())),
150 MemoryKind::Machine(MiriMemoryKind::Mmap),
151 )?;
152153 interp_ok(Scalar::from_i32(0))
154 }
155}