blob: d3470893dbbcabfca746201863f48c0713a2a5a9 [file] [log] [blame]
//! 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.
//!
//! Note that in addition to only supporting malloc-like calls to mmap, we only support free-like
//! calls to munmap, but for a very different reason. In principle, according to the man pages, it
//! is possible to unmap arbitrary regions of address space. But in a high-level language like Rust
//! this amounts to partial deallocation, which LLVM does not support. So any attempt to call our
//! munmap shim which would partily unmap a region of address space previously mapped by mmap will
//! report UB.
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: i128,
) -> 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 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(this.eval_libc("MAP_FAILED"));
}
if length == 0 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
// 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(this.eval_libc("MAP_FAILED"));
}
// 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 Some(map_length) = length.checked_next_multiple_of(this.machine.page_size) else {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(this.eval_libc("MAP_FAILED"));
};
if map_length > this.target_usize_max() {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
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();
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_pointer(addr)?;
let length = this.read_target_usize(length)?;
// addr must be a multiple of the page size, but apart from that munmap is just implemented
// as a dealloc.
#[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
if addr.addr().bytes() % this.machine.page_size != 0 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(Scalar::from_i32(-1));
}
let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(Scalar::from_i32(-1));
};
if length > this.target_usize_max() {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
let length = Size::from_bytes(length);
this.deallocate_ptr(
addr,
Some((length, this.machine.page_align())),
MemoryKind::Machine(MiriMemoryKind::Mmap),
)?;
Ok(Scalar::from_i32(0))
}
}