Skip to content

Commit 4051b0e

Browse files
authored
Merge pull request rust-lang#53 from oli-obk/dont_touch_my_precious_constants
Detect modifications of immutable memory
2 parents db7d842 + 3c5f595 commit 4051b0e

File tree

8 files changed

+87
-17
lines changed

8 files changed

+87
-17
lines changed

src/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub enum EvalError<'tcx> {
4242
},
4343
CalledClosureAsFunction,
4444
VtableForArgumentlessMethod,
45+
ModifiedConstantMemory,
4546
}
4647

4748
pub type EvalResult<'tcx, T> = Result<T, EvalError<'tcx>>;
@@ -94,6 +95,8 @@ impl<'tcx> Error for EvalError<'tcx> {
9495
"tried to call a closure through a function pointer",
9596
EvalError::VtableForArgumentlessMethod =>
9697
"tried to call a vtable function without arguments",
98+
EvalError::ModifiedConstantMemory =>
99+
"tried to modify constant memory",
97100
}
98101
}
99102

src/interpreter/mod.rs

+22-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::iter;
1515
use syntax::codemap::{self, DUMMY_SP};
1616

1717
use error::{EvalError, EvalResult};
18-
use memory::{Memory, Pointer};
18+
use memory::{Memory, Pointer, AllocId};
1919
use primval::{self, PrimVal};
2020

2121
use std::collections::HashMap;
@@ -74,7 +74,7 @@ pub struct Frame<'a, 'tcx: 'a> {
7474
pub return_ptr: Option<Pointer>,
7575

7676
/// The block to return to when returning from the current stack frame
77-
pub return_to_block: Option<mir::BasicBlock>,
77+
pub return_to_block: StackPopCleanup,
7878

7979
/// The list of locals for the current function, stored in order as
8080
/// `[arguments..., variables..., temporaries...]`. The variables begin at `self.var_offset`
@@ -139,6 +139,18 @@ enum ConstantKind {
139139
Global,
140140
}
141141

142+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
143+
pub enum StackPopCleanup {
144+
/// The stackframe existed to compute the initial value of a static/constant, make sure the
145+
/// static isn't modifyable afterwards
146+
Freeze(AllocId),
147+
/// A regular stackframe added due to a function call will need to get forwarded to the next
148+
/// block
149+
Goto(mir::BasicBlock),
150+
/// The main function and diverging functions have nowhere to return to
151+
None,
152+
}
153+
142154
impl<'a, 'tcx> EvalContext<'a, 'tcx> {
143155
pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>, memory_size: usize, stack_limit: usize) -> Self {
144156
EvalContext {
@@ -313,7 +325,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
313325
mir: CachedMir<'a, 'tcx>,
314326
substs: &'tcx Substs<'tcx>,
315327
return_ptr: Option<Pointer>,
316-
return_to_block: Option<mir::BasicBlock>,
328+
return_to_block: StackPopCleanup,
317329
) -> EvalResult<'tcx, ()> {
318330
let arg_tys = mir.arg_decls.iter().map(|a| a.ty);
319331
let var_tys = mir.var_decls.iter().map(|v| v.ty);
@@ -350,13 +362,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
350362
}
351363
}
352364

353-
fn pop_stack_frame(&mut self) {
365+
fn pop_stack_frame(&mut self) -> EvalResult<'tcx, ()> {
354366
::log_settings::settings().indentation -= 1;
355367
let frame = self.stack.pop().expect("tried to pop a stack frame, but there were none");
356-
if let Some(target) = frame.return_to_block {
357-
self.goto_block(target);
368+
match frame.return_to_block {
369+
StackPopCleanup::Freeze(alloc_id) => self.memory.freeze(alloc_id)?,
370+
StackPopCleanup::Goto(target) => self.goto_block(target),
371+
StackPopCleanup::None => {},
358372
}
359373
// TODO(solson): Deallocate local variables.
374+
Ok(())
360375
}
361376

362377
/// Applies the binary operation `op` to the two operands and writes a tuple of the result
@@ -1036,7 +1051,7 @@ pub fn eval_main<'a, 'tcx: 'a>(
10361051
let return_ptr = ecx.alloc_ret_ptr(mir.return_ty, substs)
10371052
.expect("should at least be able to allocate space for the main function's return value");
10381053

1039-
ecx.push_stack_frame(def_id, mir.span, CachedMir::Ref(mir), substs, Some(return_ptr), None)
1054+
ecx.push_stack_frame(def_id, mir.span, CachedMir::Ref(mir), substs, Some(return_ptr), StackPopCleanup::None)
10401055
.expect("could not allocate first stack frame");
10411056

10421057
if mir.arg_decls.len() == 2 {

src/interpreter/step.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ use super::{
77
ConstantId,
88
EvalContext,
99
ConstantKind,
10+
StackPopCleanup,
1011
};
1112
use error::EvalResult;
1213
use rustc::mir::repr as mir;
1314
use rustc::ty::{subst, self};
1415
use rustc::hir::def_id::DefId;
16+
use rustc::hir;
1517
use rustc::mir::visit::{Visitor, LvalueContext};
1618
use syntax::codemap::Span;
1719
use std::rc::Rc;
@@ -110,7 +112,7 @@ struct ConstantExtractor<'a, 'b: 'a, 'tcx: 'b> {
110112
}
111113

112114
impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> {
113-
fn global_item(&mut self, def_id: DefId, substs: &'tcx subst::Substs<'tcx>, span: Span) {
115+
fn global_item(&mut self, def_id: DefId, substs: &'tcx subst::Substs<'tcx>, span: Span, immutable: bool) {
114116
let cid = ConstantId {
115117
def_id: def_id,
116118
substs: substs,
@@ -123,7 +125,12 @@ impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> {
123125
self.try(|this| {
124126
let ptr = this.ecx.alloc_ret_ptr(mir.return_ty, substs)?;
125127
this.ecx.statics.insert(cid.clone(), ptr);
126-
this.ecx.push_stack_frame(def_id, span, mir, substs, Some(ptr), None)
128+
let cleanup = if immutable && !mir.return_ty.type_contents(this.ecx.tcx).interior_unsafe() {
129+
StackPopCleanup::Freeze(ptr.alloc_id)
130+
} else {
131+
StackPopCleanup::None
132+
};
133+
this.ecx.push_stack_frame(def_id, span, mir, substs, Some(ptr), cleanup)
127134
});
128135
}
129136
fn try<F: FnOnce(&mut Self) -> EvalResult<'tcx, ()>>(&mut self, f: F) {
@@ -150,7 +157,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> {
150157
// because the type is the actual function, not the signature of the function.
151158
// Thus we can simply create a zero sized allocation in `evaluate_operand`
152159
} else {
153-
self.global_item(def_id, substs, constant.span);
160+
self.global_item(def_id, substs, constant.span, true);
154161
}
155162
},
156163
mir::Literal::Promoted { index } => {
@@ -168,7 +175,12 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> {
168175
let return_ptr = this.ecx.alloc_ret_ptr(return_ty, cid.substs)?;
169176
let mir = CachedMir::Owned(Rc::new(mir));
170177
this.ecx.statics.insert(cid.clone(), return_ptr);
171-
this.ecx.push_stack_frame(this.def_id, constant.span, mir, this.substs, Some(return_ptr), None)
178+
this.ecx.push_stack_frame(this.def_id,
179+
constant.span,
180+
mir,
181+
this.substs,
182+
Some(return_ptr),
183+
StackPopCleanup::Freeze(return_ptr.alloc_id))
172184
});
173185
}
174186
}
@@ -179,7 +191,17 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> {
179191
if let mir::Lvalue::Static(def_id) = *lvalue {
180192
let substs = subst::Substs::empty(self.ecx.tcx);
181193
let span = self.span;
182-
self.global_item(def_id, substs, span);
194+
if let hir::map::Node::NodeItem(&hir::Item { ref node, .. }) = self.ecx.tcx.map.get_if_local(def_id).expect("static not found") {
195+
if let hir::ItemStatic(_, m, _) = *node {
196+
self.global_item(def_id, substs, span, m == hir::MutImmutable);
197+
return;
198+
} else {
199+
bug!("static def id doesn't point to static");
200+
}
201+
} else {
202+
bug!("static def id doesn't point to item");
203+
}
204+
self.global_item(def_id, substs, span, false);
183205
}
184206
}
185207
}

src/interpreter/terminator.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::iter;
1010
use syntax::{ast, attr};
1111
use syntax::codemap::{DUMMY_SP, Span};
1212

13-
use super::{EvalContext, IntegerExt};
13+
use super::{EvalContext, IntegerExt, StackPopCleanup};
1414
use error::{EvalError, EvalResult};
1515
use memory::Pointer;
1616

@@ -27,7 +27,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
2727
) -> EvalResult<'tcx, ()> {
2828
use rustc::mir::repr::TerminatorKind::*;
2929
match terminator.kind {
30-
Return => self.pop_stack_frame(),
30+
Return => self.pop_stack_frame()?,
3131

3232
Goto { target } => self.goto_block(target),
3333

@@ -210,8 +210,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
210210

211211
let mir = self.load_mir(resolved_def_id);
212212
let (return_ptr, return_to_block) = match destination {
213-
Some((ptr, block)) => (Some(ptr), Some(block)),
214-
None => (None, None),
213+
Some((ptr, block)) => (Some(ptr), StackPopCleanup::Goto(block)),
214+
None => (None, StackPopCleanup::None),
215215
};
216216
self.push_stack_frame(def_id, span, mir, resolved_substs, return_ptr, return_to_block)?;
217217

src/interpreter/vtable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
9696
}
9797
}
9898

99+
self.memory.freeze(vtable.alloc_id)?;
100+
99101
Ok(vtable)
100102
}
101103

src/memory.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,20 @@ impl fmt::Display for AllocId {
2626

2727
#[derive(Debug)]
2828
pub struct Allocation {
29+
/// The actual bytes of the allocation.
30+
/// Note that the bytes of a pointer represent the offset of the pointer
2931
pub bytes: Vec<u8>,
32+
/// Maps from byte addresses to allocations.
33+
/// Only the first byte of a pointer is inserted into the map.
3034
pub relocations: BTreeMap<usize, AllocId>,
35+
/// Denotes undefined memory. Reading from undefined memory is forbidden in miri
3136
pub undef_mask: UndefMask,
37+
/// The alignment of the allocation to detect unaligned reads.
3238
pub align: usize,
39+
/// Whether the allocation may be modified.
40+
/// Use the `freeze` method of `Memory` to ensure that an error occurs, if the memory of this
41+
/// allocation is modified in the future.
42+
pub immutable: bool,
3343
}
3444

3545
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -111,6 +121,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
111121
relocations: BTreeMap::new(),
112122
undef_mask: UndefMask::new(0),
113123
align: 1,
124+
immutable: false, // must be mutable, because sometimes we "move out" of a ZST
114125
};
115126
mem.alloc_map.insert(ZST_ALLOC_ID, alloc);
116127
// check that additional zst allocs work
@@ -179,6 +190,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
179190
relocations: BTreeMap::new(),
180191
undef_mask: UndefMask::new(size),
181192
align: align,
193+
immutable: false,
182194
};
183195
let id = self.next_id;
184196
self.next_id.0 += 1;
@@ -287,6 +299,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
287299

288300
pub fn get_mut(&mut self, id: AllocId) -> EvalResult<'tcx, &mut Allocation> {
289301
match self.alloc_map.get_mut(&id) {
302+
Some(ref alloc) if alloc.immutable => Err(EvalError::ModifiedConstantMemory),
290303
Some(alloc) => Ok(alloc),
291304
None => match self.functions.get(&id) {
292305
Some(_) => Err(EvalError::DerefFunctionPointer),
@@ -430,10 +443,16 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
430443

431444
/// Reading and writing
432445
impl<'a, 'tcx> Memory<'a, 'tcx> {
446+
447+
pub fn freeze(&mut self, alloc_id: AllocId) -> EvalResult<'tcx, ()> {
448+
self.get_mut(alloc_id)?.immutable = true;
449+
Ok(())
450+
}
451+
433452
pub fn copy(&mut self, src: Pointer, dest: Pointer, size: usize, align: usize) -> EvalResult<'tcx, ()> {
434453
self.check_relocation_edges(src, size)?;
435454

436-
let src_bytes = self.get_bytes_unchecked_mut(src, size)?.as_mut_ptr();
455+
let src_bytes = self.get_bytes_unchecked(src, size)?.as_ptr();
437456
let dest_bytes = self.get_bytes_mut(dest, size, align)?.as_mut_ptr();
438457

439458
// SAFE: The above indexing would have panicked if there weren't at least `size` bytes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
static X: usize = 5;
2+
3+
#[allow(mutable_transmutes)]
4+
fn main() {
5+
unsafe {
6+
*std::mem::transmute::<&usize, &mut usize>(&X) = 6; //~ ERROR: tried to modify constant memory
7+
assert_eq!(X, 6);
8+
}
9+
}
File renamed without changes.

0 commit comments

Comments
 (0)