Skip to content

Commit 84ed1ee

Browse files
committed
mmap/munmap/mremamp shims
1 parent 2f1fa12 commit 84ed1ee

File tree

14 files changed

+409
-2
lines changed

14 files changed

+409
-2
lines changed

src/concurrency/data_race.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,10 @@ impl VClockAlloc {
707707
let (alloc_timestamp, alloc_index) = match kind {
708708
// User allocated and stack memory should track allocation.
709709
MemoryKind::Machine(
710-
MiriMemoryKind::Rust | MiriMemoryKind::C | MiriMemoryKind::WinHeap,
710+
MiriMemoryKind::Rust
711+
| MiriMemoryKind::C
712+
| MiriMemoryKind::WinHeap
713+
| MiriMemoryKind::Mmap,
711714
)
712715
| MemoryKind::Stack => {
713716
let (alloc_index, clocks) = global.current_thread_state(thread_mgr);

src/machine.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ pub enum MiriMemoryKind {
8888
/// Memory for thread-local statics.
8989
/// This memory may leak.
9090
Tls,
91+
/// Memory mapped directly by the program
92+
Mmap,
9193
}
9294

9395
impl From<MiriMemoryKind> for MemoryKind<MiriMemoryKind> {
@@ -102,7 +104,7 @@ impl MayLeak for MiriMemoryKind {
102104
fn may_leak(self) -> bool {
103105
use self::MiriMemoryKind::*;
104106
match self {
105-
Rust | C | WinHeap | Runtime => false,
107+
Rust | C | WinHeap | Runtime | Mmap => false,
106108
Machine | Global | ExternStatic | Tls => true,
107109
}
108110
}
@@ -120,6 +122,7 @@ impl fmt::Display for MiriMemoryKind {
120122
Global => write!(f, "global (static or const)"),
121123
ExternStatic => write!(f, "extern static"),
122124
Tls => write!(f, "thread-local static"),
125+
Mmap => write!(f, "mmap"),
123126
}
124127
}
125128
}
@@ -290,6 +293,14 @@ impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> {
290293
}
291294
}
292295

296+
pub struct Mapping {
297+
pub ptr: Pointer<Option<Provenance>>,
298+
pub alloc_id: AllocId,
299+
pub len: u64,
300+
pub can_read: bool,
301+
pub can_write: bool,
302+
}
303+
293304
/// The machine itself.
294305
pub struct Evaluator<'mir, 'tcx> {
295306
pub stacked_borrows: Option<stacked_borrows::GlobalState>,
@@ -310,6 +321,9 @@ pub struct Evaluator<'mir, 'tcx> {
310321
/// TLS state.
311322
pub(crate) tls: TlsData<'tcx>,
312323

324+
/// Mappings established through mmap
325+
pub(crate) mappings: Vec<Mapping>,
326+
313327
/// What should Miri do when an op requires communicating with the host,
314328
/// such as accessing host env vars, random number generation, and
315329
/// file system access.
@@ -428,6 +442,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
428442
argv: None,
429443
cmd_line: None,
430444
tls: TlsData::default(),
445+
mappings: Vec::new(),
431446
isolated_op: config.isolated_op,
432447
validate: config.validate,
433448
enforce_abi: config.check_abi,
@@ -573,6 +588,10 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
573588
let def_id = frame.instance.def_id();
574589
def_id.is_local() || self.local_crates.contains(&def_id.krate)
575590
}
591+
592+
pub(crate) fn get_mapping(&self, alloc_id: AllocId) -> Option<&Mapping> {
593+
self.mappings.iter().find(|m| m.alloc_id == alloc_id)
594+
}
576595
}
577596

578597
/// A rustc InterpCx for Miri.
@@ -872,6 +891,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
872891
(alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
873892
range: AllocRange,
874893
) -> InterpResult<'tcx> {
894+
if let Some(map) = machine.get_mapping(alloc_id) {
895+
if !map.can_read {
896+
throw_ub_format!("{alloc_id:?} is a mapping that does not allow reads");
897+
}
898+
}
875899
if let Some(data_race) = &alloc_extra.data_race {
876900
data_race.read(
877901
alloc_id,
@@ -904,6 +928,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
904928
(alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
905929
range: AllocRange,
906930
) -> InterpResult<'tcx> {
931+
if let Some(map) = machine.get_mapping(alloc_id) {
932+
if !map.can_write {
933+
throw_ub_format!("{alloc_id:?} is a mapping that does not allow writes");
934+
}
935+
}
907936
if let Some(data_race) = &mut alloc_extra.data_race {
908937
data_race.write(
909938
alloc_id,

src/shims/unix/foreign_items.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc_target::spec::abi::Abi;
1010
use crate::*;
1111
use shims::foreign_items::EmulateByNameResult;
1212
use shims::unix::fs::EvalContextExt as _;
13+
use shims::unix::mem::EvalContextExt as _;
1314
use shims::unix::sync::EvalContextExt as _;
1415
use shims::unix::thread::EvalContextExt as _;
1516

@@ -207,6 +208,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
207208
}
208209
}
209210

211+
"mmap" => {
212+
let [addr, length, prot, flags, fd, offset] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?;
213+
let result = this.mmap(addr, length, prot, flags, fd, offset)?;
214+
this.write_pointer(result, dest)?;
215+
}
216+
"mremap" => {
217+
let [old_address, old_size, new_size, flags] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?;
218+
let result = this.mremap(old_address, old_size, new_size, flags)?;
219+
this.write_pointer(result, dest)?;
220+
}
221+
"munmap" => {
222+
let [addr, length] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?;
223+
let result = this.munmap(addr, length)?;
224+
this.write_scalar(Scalar::from_i32(result), dest)?;
225+
}
226+
210227
// Dynamic symbol loading
211228
"dlsym" => {
212229
let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;

src/shims/unix/mem.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
use crate::machine::{Mapping, PAGE_SIZE};
2+
use crate::*;
3+
use rustc_target::abi::{Align, Size};
4+
5+
fn round_up_to_multiple_of_page_size(length: u64) -> Option<u64> {
6+
#[allow(clippy::integer_arithmetic)] // PAGE_SIZE is nonzero
7+
(length.checked_add(PAGE_SIZE - 1)? / PAGE_SIZE).checked_mul(PAGE_SIZE)
8+
}
9+
10+
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
11+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
12+
fn mmap(
13+
&mut self,
14+
addr: &OpTy<'tcx, Provenance>,
15+
length: &OpTy<'tcx, Provenance>,
16+
prot: &OpTy<'tcx, Provenance>,
17+
flags: &OpTy<'tcx, Provenance>,
18+
fd: &OpTy<'tcx, Provenance>,
19+
offset: &OpTy<'tcx, Provenance>,
20+
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
21+
let this = self.eval_context_mut();
22+
23+
let addr = this.read_pointer(addr)?;
24+
let length = this.read_scalar(length)?.to_machine_usize(this)?;
25+
let prot = this.read_scalar(prot)?.to_i32()?;
26+
let flags = this.read_scalar(flags)?.to_i32()?;
27+
let fd = this.read_scalar(fd)?.to_i32()?;
28+
let offset = this.read_scalar(offset)?.to_machine_usize(this)?;
29+
30+
let prot_read = this.eval_libc_i32("PROT_READ")?;
31+
let prot_write = this.eval_libc_i32("PROT_WRITE")?;
32+
let map_private = this.eval_libc_i32("MAP_PRIVATE")?;
33+
let map_anonymous = this.eval_libc_i32("MAP_ANONYMOUS")?;
34+
35+
// Only one of MAP_PRIVATE, MAP_SHARED, or MAP_SHARED_VALIDATE may be passed
36+
if flags & this.eval_libc_i32("MAP_PRIVATE")? == 0 {
37+
throw_unsup_format!("Miri does not support MAP_SHARED or MAP_SHARED_VALIDATE");
38+
}
39+
40+
if flags & this.eval_libc_i32("MAP_STACK")? > 0 {
41+
throw_unsup_format!("Miri does not support MAP_STACK");
42+
}
43+
44+
if prot & this.eval_libc_i32("PROT_EXEC")? > 0 {
45+
throw_unsup_format!("Miri does not support mapping executable pages");
46+
}
47+
48+
if offset != 0 {
49+
throw_unsup_format!("Miri does not support non-zero offsets to mmap (yet)");
50+
}
51+
52+
if !this.ptr_is_null(addr)? {
53+
throw_unsup_format!("Miri does not support non-null pointers to mmap");
54+
}
55+
56+
if fd != -1 {
57+
throw_unsup_format!("Miri does not support file-backed memory mappings");
58+
}
59+
60+
if length == 0 {
61+
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")?))?;
62+
return Ok(Pointer::null());
63+
}
64+
65+
let align = Align::from_bytes(PAGE_SIZE).unwrap();
66+
let map_length = round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX);
67+
68+
if (flags == map_private | map_anonymous) || (flags == map_private && fd != -1) {
69+
// mmap as a memory allocator
70+
let ptr = this.allocate_ptr(
71+
Size::from_bytes(map_length),
72+
align,
73+
MiriMemoryKind::Mmap.into(),
74+
)?;
75+
// We just allocated this, the access is definitely in-bounds and fits into our address space.
76+
// mmap guarantees new mappings are zero-init.
77+
this.write_bytes_ptr(
78+
ptr.into(),
79+
std::iter::repeat(0u8).take(usize::try_from(map_length).unwrap()),
80+
)
81+
.unwrap();
82+
let (prov, offset) = ptr.into_parts();
83+
let ptr = Pointer::new(Some(prov), offset);
84+
85+
this.machine.mappings.push(Mapping {
86+
ptr,
87+
alloc_id: match prov {
88+
Provenance::Concrete { alloc_id, .. } => alloc_id,
89+
Provenance::Wildcard =>
90+
unreachable!("allocate_ptr should not return a Wildcard pointer"),
91+
},
92+
len: map_length,
93+
can_read: prot & prot_read > 0,
94+
can_write: prot & prot_write > 0,
95+
});
96+
97+
Ok(ptr)
98+
} else {
99+
throw_unsup_format!(
100+
"mmap is not supported with arguments: (addr: {addr}, length: {length}, prot: {prot:x}, flags: {flags:0x}, fd: {fd}, offset: {offset})"
101+
);
102+
}
103+
}
104+
105+
fn mremap(
106+
&mut self,
107+
old_address: &OpTy<'tcx, Provenance>,
108+
old_size: &OpTy<'tcx, Provenance>,
109+
new_size: &OpTy<'tcx, Provenance>,
110+
flags: &OpTy<'tcx, Provenance>,
111+
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
112+
let this = self.eval_context_mut();
113+
114+
let old_address = this.read_pointer(old_address)?;
115+
let _old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
116+
let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?;
117+
let flags = this.read_scalar(flags)?.to_i32()?;
118+
119+
if flags & this.eval_libc_i32("MREMAP_FIXED")? > 0 {
120+
throw_unsup_format!("Miri does not support mremap wth MREMAP_FIXED");
121+
}
122+
123+
if flags & this.eval_libc_i32("MREMAP_DONTUNMAP")? > 0 {
124+
throw_unsup_format!("Miri does not support mremap wth MREMAP_DONTUNMAP");
125+
}
126+
127+
if flags & this.eval_libc_i32("MREMAP_MAYMOVE")? == 0 {
128+
// We only support MREMAP_MAYMOVE, so not passing the flag is just a failure
129+
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")?))?;
130+
return Ok(Pointer::null());
131+
}
132+
133+
let map_idx = this.machine.mappings.iter_mut().position(|map| map.ptr == old_address);
134+
135+
if let Some(i) = map_idx {
136+
let pointer = this.realloc(old_address, new_size, MiriMemoryKind::Mmap)?;
137+
let map = &mut this.machine.mappings[i];
138+
map.ptr = pointer;
139+
map.len = new_size;
140+
map.alloc_id = match pointer.into_parts().0.unwrap() {
141+
Provenance::Concrete { alloc_id, .. } => alloc_id,
142+
Provenance::Wildcard =>
143+
unreachable!("allocate_ptr should not return a Wildcard pointer"),
144+
};
145+
Ok(pointer)
146+
} else {
147+
// This isn't a previous mapping
148+
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")?))?;
149+
Ok(Pointer::null())
150+
}
151+
}
152+
153+
fn munmap(
154+
&mut self,
155+
addr: &OpTy<'tcx, Provenance>,
156+
length: &OpTy<'tcx, Provenance>,
157+
) -> InterpResult<'tcx, i32> {
158+
let this = self.eval_context_mut();
159+
160+
let addr = this.read_pointer(addr)?;
161+
let length = this.read_scalar(length)?.to_machine_usize(this)?;
162+
163+
// The address addr must be a multiple of the page size (but length need not be).
164+
#[allow(clippy::integer_arithmetic)] // PAGE_SIZE is nonzero
165+
if addr.addr().bytes() % PAGE_SIZE != 0 {
166+
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")?))?;
167+
return Ok(-1);
168+
}
169+
170+
// All pages containing a part of the indicated range are unmapped.
171+
// TODO: That means we can actually alter multiple mappings with munmap :/
172+
let length = round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX);
173+
174+
let map_idx = this.machine.mappings.iter_mut().position(|map| {
175+
let start = map.ptr.addr();
176+
let end = map.ptr.addr() + Size::from_bytes(map.len);
177+
addr.addr() >= start && addr.addr() < end
178+
});
179+
180+
if let Some(i) = map_idx {
181+
let map = &this.machine.mappings[i];
182+
if map.ptr.addr() == addr.addr() && map.len == length {
183+
this.machine.mappings.remove(i);
184+
this.free(addr, MiriMemoryKind::Mmap)?;
185+
} else {
186+
throw_unsup_format!("Miri does not support partial munmap");
187+
}
188+
}
189+
Ok(0)
190+
// It is not an error if the indicated range does not contain any mapped pages.
191+
}
192+
}

src/shims/unix/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod dlsym;
22
pub mod foreign_items;
33

44
mod fs;
5+
mod mem;
56
mod sync;
67
mod thread;
78

tests/fail/mmap_invalid_dealloc.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//@compile-flags: -Zmiri-disable-isolation
2+
//@ignore-target-windows: No libc on Windows
3+
4+
#![feature(rustc_private)]
5+
6+
fn main() {
7+
unsafe {
8+
let ptr = libc::mmap(
9+
std::ptr::null_mut(),
10+
4096,
11+
libc::PROT_READ | libc::PROT_WRITE,
12+
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
13+
-1,
14+
0,
15+
);
16+
libc::free(ptr); //~ ERROR: which is mmap memory, using C heap deallocation operation
17+
}
18+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: deallocating ALLOC, which is mmap memory, using C heap deallocation operation
2+
--> $DIR/mmap_invalid_dealloc.rs:LL:CC
3+
|
4+
LL | libc::free(ptr);
5+
| ^^^^^^^^^^^^^^^ deallocating ALLOC, which is mmap memory, using C heap deallocation operation
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/mmap_invalid_dealloc.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to previous error
15+

tests/fail/mmap_prot_none.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//@compile-flags: -Zmiri-disable-isolation
2+
//@ignore-target-windows: No libc on Windows
3+
4+
#![feature(rustc_private)]
5+
6+
fn main() {
7+
unsafe {
8+
let ptr = libc::mmap(
9+
std::ptr::null_mut(),
10+
4096,
11+
libc::PROT_NONE,
12+
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
13+
-1,
14+
0,
15+
) as *mut u8;
16+
let _byte = *ptr; //~ ERROR: is a mapping that does not allow reads
17+
}
18+
}

0 commit comments

Comments
 (0)