From 082effb3ee6b36bfac00ae8ff8cf2f4e1b253b5c Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Tue, 5 Jul 2016 14:27:27 +0200 Subject: [PATCH] align allocations in the worst possible way --- src/error.rs | 9 +++++ src/interpreter/mod.rs | 37 +++++++++++------ src/interpreter/terminator.rs | 7 ++-- src/memory.rs | 49 ++++++++++++++--------- tests/compile-fail/oom.rs | 2 +- tests/compile-fail/oom2.rs | 2 +- tests/compile-fail/out_of_bounds_read.rs | 2 +- tests/compile-fail/out_of_bounds_read2.rs | 2 +- 8 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/error.rs b/src/error.rs index 9952cfd9a7e..6f033557b96 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,6 +36,10 @@ pub enum EvalError<'tcx> { }, ExecutionTimeLimitReached, StackFrameLimitReached, + AlignmentCheckFailed { + required: usize, + has: usize, + }, } pub type EvalResult<'tcx, T> = Result>; @@ -82,6 +86,8 @@ impl<'tcx> Error for EvalError<'tcx> { "reached the configured maximum execution time", EvalError::StackFrameLimitReached => "reached the configured maximum number of stack frames", + EvalError::AlignmentCheckFailed{..} => + "tried to execute a misaligned read or write", } } @@ -106,6 +112,9 @@ impl<'tcx> fmt::Display for EvalError<'tcx> { EvalError::OutOfMemory { allocation_size, memory_size, memory_usage } => write!(f, "tried to allocate {} more bytes, but only {} bytes are free of the {} byte memory", allocation_size, memory_size - memory_usage, memory_size), + EvalError::AlignmentCheckFailed { required, has } => + write!(f, "tried to access memory with alignment {}, but alignment {} is required", + has, required), _ => write!(f, "{}", self.description()), } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 34fa9d8de6f..8d6c526c0c2 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -153,7 +153,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { match output_ty { ty::FnConverging(ty) => { let size = self.type_size_with_substs(ty, substs); - self.memory.allocate(size).map(Some) + let align = self.type_align_with_substs(ty, substs); + self.memory.allocate(size, align).map(Some) } ty::FnDiverging => Ok(None), } @@ -177,7 +178,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { use rustc_const_math::{ConstInt, ConstIsize, ConstUsize}; macro_rules! i2p { ($i:ident, $n:expr) => {{ - let ptr = self.memory.allocate($n)?; + let ptr = self.memory.allocate($n, $n)?; self.memory.write_int(ptr, $i as i64, $n)?; Ok(ptr) }} @@ -202,8 +203,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Integral(ConstInt::Usize(ConstUsize::Us64(i))) => i2p!(i, 8), Str(ref s) => { let psize = self.memory.pointer_size(); - let static_ptr = self.memory.allocate(s.len())?; - let ptr = self.memory.allocate(psize * 2)?; + let static_ptr = self.memory.allocate(s.len(), 1)?; + let ptr = self.memory.allocate(psize * 2, psize)?; self.memory.write_bytes(static_ptr, s.as_bytes())?; self.memory.write_ptr(ptr, static_ptr)?; self.memory.write_usize(ptr.offset(psize as isize), s.len() as u64)?; @@ -211,19 +212,19 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } ByteStr(ref bs) => { let psize = self.memory.pointer_size(); - let static_ptr = self.memory.allocate(bs.len())?; - let ptr = self.memory.allocate(psize)?; + let static_ptr = self.memory.allocate(bs.len(), 1)?; + let ptr = self.memory.allocate(psize, psize)?; self.memory.write_bytes(static_ptr, bs)?; self.memory.write_ptr(ptr, static_ptr)?; Ok(ptr) } Bool(b) => { - let ptr = self.memory.allocate(1)?; + let ptr = self.memory.allocate(1, 1)?; self.memory.write_bool(ptr, b)?; Ok(ptr) } Char(c) => { - let ptr = self.memory.allocate(4)?; + let ptr = self.memory.allocate(4, 4)?; self.memory.write_uint(ptr, c as u64, 4)?; Ok(ptr) }, @@ -269,10 +270,18 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.type_size_with_substs(ty, self.substs()) } + fn type_align(&self, ty: Ty<'tcx>) -> usize { + self.type_align_with_substs(ty, self.substs()) + } + fn type_size_with_substs(&self, ty: Ty<'tcx>, substs: &'tcx Substs<'tcx>) -> usize { self.type_layout_with_substs(ty, substs).size(&self.tcx.data_layout).bytes() as usize } + fn type_align_with_substs(&self, ty: Ty<'tcx>, substs: &'tcx Substs<'tcx>) -> usize { + self.type_layout_with_substs(ty, substs).align(&self.tcx.data_layout).pref() as usize + } + fn type_layout(&self, ty: Ty<'tcx>) -> &'tcx Layout { self.type_layout_with_substs(ty, self.substs()) } @@ -306,7 +315,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let locals: EvalResult<'tcx, Vec> = arg_tys.chain(var_tys).chain(temp_tys).map(|ty| { let size = self.type_size_with_substs(ty, substs); - self.memory.allocate(size) + let align = self.type_align_with_substs(ty, substs); + self.memory.allocate(size, align) }).collect(); self.stack.push(Frame { @@ -553,7 +563,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Box(ty) => { let size = self.type_size(ty); - let ptr = self.memory.allocate(size)?; + let align = self.type_align(ty); + let ptr = self.memory.allocate(size, align)?; self.memory.write_ptr(dest, ptr)?; } @@ -701,7 +712,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Item { def_id, substs } => { if let ty::TyFnDef(..) = ty.sty { // function items are zero sized - Ok(self.memory.allocate(0)?) + Ok(self.memory.allocate(0, 0)?) } else { let cid = ConstantId { def_id: def_id, @@ -955,9 +966,9 @@ pub fn eval_main<'a, 'tcx: 'a>( if mir.arg_decls.len() == 2 { // start function let ptr_size = ecx.memory().pointer_size(); - let nargs = ecx.memory_mut().allocate(ptr_size).expect("can't allocate memory for nargs"); + let nargs = ecx.memory_mut().allocate(ptr_size, ptr_size).expect("can't allocate memory for nargs"); ecx.memory_mut().write_usize(nargs, 0).unwrap(); - let args = ecx.memory_mut().allocate(ptr_size).expect("can't allocate memory for arg pointer"); + let args = ecx.memory_mut().allocate(ptr_size, ptr_size).expect("can't allocate memory for arg pointer"); ecx.memory_mut().write_usize(args, 0).unwrap(); ecx.frame_mut().locals[0] = nargs; ecx.frame_mut().locals[1] = args; diff --git a/src/interpreter/terminator.rs b/src/interpreter/terminator.rs index 2fbc9a63ef6..28b130e3d57 100644 --- a/src/interpreter/terminator.rs +++ b/src/interpreter/terminator.rs @@ -88,7 +88,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { match func_ty.sty { ty::TyFnPtr(bare_fn_ty) => { let ptr = self.eval_operand(func)?; - assert_eq!(ptr.offset, 0); let fn_ptr = self.memory.read_ptr(ptr)?; let FunctionDefinition { def_id, substs, fn_ty } = self.memory.get_fn(fn_ptr.alloc_id)?; if fn_ty != bare_fn_ty { @@ -416,14 +415,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { match &link_name[..] { "__rust_allocate" => { let size = self.memory.read_usize(args[0])?; - let ptr = self.memory.allocate(size as usize)?; + let align = self.memory.read_usize(args[1])?; + let ptr = self.memory.allocate(size as usize, align as usize)?; self.memory.write_ptr(dest, ptr)?; } "__rust_reallocate" => { let ptr = self.memory.read_ptr(args[0])?; let size = self.memory.read_usize(args[2])?; - let new_ptr = self.memory.reallocate(ptr, size as usize)?; + let align = self.memory.read_usize(args[3])?; + let new_ptr = self.memory.reallocate(ptr, size as usize, align as usize)?; self.memory.write_ptr(dest, new_ptr)?; } diff --git a/src/memory.rs b/src/memory.rs index 2453b1f6e67..ca041dc1d95 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -29,6 +29,7 @@ pub struct Allocation { pub bytes: Vec, pub relocations: BTreeMap, pub undef_mask: UndefMask, + pub align: usize, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -98,10 +99,11 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { bytes: Vec::new(), relocations: BTreeMap::new(), undef_mask: UndefMask::new(0), + align: 0, }; mem.alloc_map.insert(ZST_ALLOC_ID, alloc); // check that additional zst allocs work - debug_assert!(mem.allocate(0).unwrap().points_to_zst()); + debug_assert!(mem.allocate(0, 0).unwrap().points_to_zst()); debug_assert!(mem.get(ZST_ALLOC_ID).is_ok()); mem } @@ -133,62 +135,71 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } } - pub fn allocate(&mut self, size: usize) -> EvalResult<'tcx, Pointer> { + pub fn allocate(&mut self, size: usize, align: usize) -> EvalResult<'tcx, Pointer> { if size == 0 { return Ok(Pointer::zst_ptr()); } + // make sure we can offset the result pointer by the worst possible alignment + // this allows cheaply checking for alignment directly in the pointer + let least_aligned_size = size + align; if self.memory_size - self.memory_usage < size as u64 { return Err(EvalError::OutOfMemory { - allocation_size: size as u64, + allocation_size: least_aligned_size as u64, memory_size: self.memory_size, memory_usage: self.memory_usage, }); } self.memory_usage += size as u64; let alloc = Allocation { - bytes: vec![0; size], + bytes: vec![0; least_aligned_size], relocations: BTreeMap::new(), - undef_mask: UndefMask::new(size), + undef_mask: UndefMask::new(least_aligned_size), + align: align, }; let id = self.next_id; self.next_id.0 += 1; self.alloc_map.insert(id, alloc); Ok(Pointer { alloc_id: id, - offset: 0, + // offset by the alignment, so larger accesses will fail + offset: align, }) } // TODO(solson): Track which allocations were returned from __rust_allocate and report an error // when reallocating/deallocating any others. - pub fn reallocate(&mut self, ptr: Pointer, new_size: usize) -> EvalResult<'tcx, Pointer> { - if ptr.offset != 0 { + pub fn reallocate(&mut self, ptr: Pointer, new_size: usize, align: usize) -> EvalResult<'tcx, Pointer> { + if ptr.offset != self.get(ptr.alloc_id)?.align { // TODO(solson): Report error about non-__rust_allocate'd pointer. return Err(EvalError::Unimplemented(format!("bad pointer offset: {}", ptr.offset))); } if ptr.points_to_zst() { - return self.allocate(new_size); + return self.allocate(new_size, align); } - let size = self.get_mut(ptr.alloc_id)?.bytes.len(); + let size = self.get(ptr.alloc_id)?.bytes.len(); + let least_aligned_size = new_size + align; - if new_size > size { - let amount = new_size - size; + if least_aligned_size > size { + let amount = least_aligned_size - size; self.memory_usage += amount as u64; let alloc = self.get_mut(ptr.alloc_id)?; alloc.bytes.extend(iter::repeat(0).take(amount)); alloc.undef_mask.grow(amount, false); - } else if size > new_size { + } else if size > least_aligned_size { // it's possible to cause miri to use arbitrary amounts of memory that aren't detectable // through the memory_usage value, by allocating a lot and reallocating to zero - self.memory_usage -= (size - new_size) as u64; - self.clear_relocations(ptr.offset(new_size as isize), size - new_size)?; + self.memory_usage -= (size - least_aligned_size) as u64; + self.clear_relocations(ptr.offset(least_aligned_size as isize), size - least_aligned_size)?; let alloc = self.get_mut(ptr.alloc_id)?; - alloc.bytes.truncate(new_size); - alloc.undef_mask.truncate(new_size); + alloc.bytes.truncate(least_aligned_size); + alloc.undef_mask.truncate(least_aligned_size); } - Ok(ptr) + Ok(Pointer { + alloc_id: ptr.alloc_id, + offset: align, + }) } // TODO(solson): See comment on `reallocate`. @@ -196,7 +207,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { if ptr.points_to_zst() { return Ok(()); } - if ptr.offset != 0 { + if ptr.offset != self.get(ptr.alloc_id)?.align { // TODO(solson): Report error about non-__rust_allocate'd pointer. return Err(EvalError::Unimplemented(format!("bad pointer offset: {}", ptr.offset))); } diff --git a/tests/compile-fail/oom.rs b/tests/compile-fail/oom.rs index 83109a77e62..d5f021bda70 100644 --- a/tests/compile-fail/oom.rs +++ b/tests/compile-fail/oom.rs @@ -6,6 +6,6 @@ fn bar() { assert_eq!(x, 6); } -fn main() { //~ ERROR tried to allocate 4 more bytes, but only 0 bytes are free of the 0 byte memory +fn main() { //~ ERROR tried to allocate 8 more bytes, but only 0 bytes are free of the 0 byte memory bar(); } diff --git a/tests/compile-fail/oom2.rs b/tests/compile-fail/oom2.rs index 63c51dbaa7d..65ef0dd0b7d 100644 --- a/tests/compile-fail/oom2.rs +++ b/tests/compile-fail/oom2.rs @@ -3,7 +3,7 @@ fn bar(i: i32) { if i < 1000 { - bar(i + 1) //~ ERROR tried to allocate 4 more bytes, but only 1 bytes are free of the 1000 byte memory + bar(i + 1) //~ ERROR tried to allocate 8 more bytes, but only 1 bytes are free of the 1000 byte memory //~^NOTE inside call to bar //~|NOTE inside call to bar //~|NOTE inside call to bar diff --git a/tests/compile-fail/out_of_bounds_read.rs b/tests/compile-fail/out_of_bounds_read.rs index f6a305840c2..29355992e44 100644 --- a/tests/compile-fail/out_of_bounds_read.rs +++ b/tests/compile-fail/out_of_bounds_read.rs @@ -1,5 +1,5 @@ fn main() { let v: Vec = vec![1, 2]; - let x = unsafe { *v.get_unchecked(5) }; //~ ERROR: which has size 2 + let x = unsafe { *v.get_unchecked(5) }; //~ ERROR: which has size 3 panic!("this should never print: {}", x); } diff --git a/tests/compile-fail/out_of_bounds_read2.rs b/tests/compile-fail/out_of_bounds_read2.rs index 5509a8346e5..388ed8b1306 100644 --- a/tests/compile-fail/out_of_bounds_read2.rs +++ b/tests/compile-fail/out_of_bounds_read2.rs @@ -1,5 +1,5 @@ fn main() { let v: Vec = vec![1, 2]; - let x = unsafe { *v.get_unchecked(5) }; //~ ERROR: memory access of 5..6 outside bounds of allocation + let x = unsafe { *v.get_unchecked(5) }; //~ ERROR: memory access of 6..7 outside bounds of allocation panic!("this should never print: {}", x); }