Skip to content

Commit bef8f15

Browse files
committed
mmap/munmap/mremamp shims
1 parent 284b59c commit bef8f15

14 files changed

+465
-2
lines changed

src/concurrency/data_race.rs

+4-1
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

+30-1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ pub enum MiriMemoryKind {
8989
/// Memory for thread-local statics.
9090
/// This memory may leak.
9191
Tls,
92+
/// Memory mapped directly by the program
93+
Mmap,
9294
}
9395

9496
impl From<MiriMemoryKind> for MemoryKind<MiriMemoryKind> {
@@ -103,7 +105,7 @@ impl MayLeak for MiriMemoryKind {
103105
fn may_leak(self) -> bool {
104106
use self::MiriMemoryKind::*;
105107
match self {
106-
Rust | C | WinHeap | Runtime => false,
108+
Rust | C | WinHeap | Runtime | Mmap => false,
107109
Machine | Global | ExternStatic | Tls => true,
108110
}
109111
}
@@ -121,6 +123,7 @@ impl fmt::Display for MiriMemoryKind {
121123
Global => write!(f, "global (static or const)"),
122124
ExternStatic => write!(f, "extern static"),
123125
Tls => write!(f, "thread-local static"),
126+
Mmap => write!(f, "mmap"),
124127
}
125128
}
126129
}
@@ -276,6 +279,14 @@ impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> {
276279
}
277280
}
278281

282+
pub struct Mapping {
283+
pub ptr: Pointer<Option<Provenance>>,
284+
pub alloc_id: AllocId,
285+
pub len: u64,
286+
pub can_read: bool,
287+
pub can_write: bool,
288+
}
289+
279290
/// The machine itself.
280291
pub struct Evaluator<'mir, 'tcx> {
281292
pub stacked_borrows: Option<stacked_borrows::GlobalState>,
@@ -296,6 +307,9 @@ pub struct Evaluator<'mir, 'tcx> {
296307
/// TLS state.
297308
pub(crate) tls: TlsData<'tcx>,
298309

310+
/// Mappings established through mmap
311+
pub(crate) mappings: Vec<Mapping>,
312+
299313
/// What should Miri do when an op requires communicating with the host,
300314
/// such as accessing host env vars, random number generation, and
301315
/// file system access.
@@ -409,6 +423,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
409423
argv: None,
410424
cmd_line: None,
411425
tls: TlsData::default(),
426+
mappings: Vec::new(),
412427
isolated_op: config.isolated_op,
413428
validate: config.validate,
414429
enforce_abi: config.check_abi,
@@ -552,6 +567,10 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
552567
let def_id = frame.instance.def_id();
553568
def_id.is_local() || self.local_crates.contains(&def_id.krate)
554569
}
570+
571+
pub(crate) fn get_mapping(&self, alloc_id: AllocId) -> Option<&Mapping> {
572+
self.mappings.iter().find(|m| m.alloc_id == alloc_id)
573+
}
555574
}
556575

557576
/// A rustc InterpCx for Miri.
@@ -846,6 +865,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
846865
(alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
847866
range: AllocRange,
848867
) -> InterpResult<'tcx> {
868+
if let Some(map) = machine.get_mapping(alloc_id) {
869+
if !map.can_read {
870+
throw_ub_format!("{alloc_id:?} is a mapping that does not allow reads");
871+
}
872+
}
849873
if let Some(data_race) = &alloc_extra.data_race {
850874
data_race.read(
851875
alloc_id,
@@ -878,6 +902,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
878902
(alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
879903
range: AllocRange,
880904
) -> InterpResult<'tcx> {
905+
if let Some(map) = machine.get_mapping(alloc_id) {
906+
if !map.can_write {
907+
throw_ub_format!("{alloc_id:?} is a mapping that does not allow writes");
908+
}
909+
}
881910
if let Some(data_race) = &mut alloc_extra.data_race {
882911
data_race.write(
883912
alloc_id,

src/shims/unix/foreign_items.rs

+17
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

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

src/shims/unix/mod.rs

+1
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

+18
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+
}
+15
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+

0 commit comments

Comments
 (0)