Ensure that it is not possible to explicitly free stack memory

This commit is contained in:
Oliver Schneider 2017-07-12 14:51:47 +02:00 committed by Oliver Schneider
parent 56d4de303f
commit 192da8819f
No known key found for this signature in database
GPG key ID: 1D5CB4FC597C3004
7 changed files with 122 additions and 90 deletions

View file

@ -2,7 +2,7 @@ use std::error::Error;
use std::fmt;
use rustc::mir;
use rustc::ty::{FnSig, Ty, layout};
use memory::MemoryPointer;
use memory::{MemoryPointer, Kind};
use rustc_const_math::ConstMathErr;
use syntax::codemap::Span;
@ -12,6 +12,7 @@ pub enum EvalError<'tcx> {
NoMirFor(String),
UnterminatedCString(MemoryPointer),
DanglingPointerDeref,
DoubleFree,
InvalidMemoryAccess,
InvalidFunctionPointer,
InvalidBool,
@ -56,8 +57,8 @@ pub enum EvalError<'tcx> {
AssumptionNotHeld,
InlineAsm,
TypeNotPrimitive(Ty<'tcx>),
ReallocatedStaticMemory,
DeallocatedStaticMemory,
ReallocatedWrongMemoryKind(Kind, Kind),
DeallocatedWrongMemoryKind(Kind, Kind),
ReallocateNonBasePtr,
DeallocateNonBasePtr,
IncorrectAllocationInformation,
@ -84,6 +85,8 @@ impl<'tcx> Error for EvalError<'tcx> {
"tried to access memory through an invalid pointer",
DanglingPointerDeref =>
"dangling pointer was dereferenced",
DoubleFree =>
"tried to deallocate dangling pointer",
InvalidFunctionPointer =>
"tried to use an integer pointer or a dangling pointer as a function pointer",
InvalidBool =>
@ -148,10 +151,10 @@ impl<'tcx> Error for EvalError<'tcx> {
"miri does not support inline assembly",
TypeNotPrimitive(_) =>
"expected primitive type, got nonprimitive",
ReallocatedStaticMemory =>
"tried to reallocate static memory",
DeallocatedStaticMemory =>
"tried to deallocate static memory",
ReallocatedWrongMemoryKind(_, _) =>
"tried to reallocate memory from one kind to another",
DeallocatedWrongMemoryKind(_, _) =>
"tried to deallocate memory of the wrong kind",
ReallocateNonBasePtr =>
"tried to reallocate with a pointer not to the beginning of an existing object",
DeallocateNonBasePtr =>
@ -198,6 +201,10 @@ impl<'tcx> fmt::Display for EvalError<'tcx> {
write!(f, "tried to call a function with sig {} through a function pointer of type {}", sig, got),
ArrayIndexOutOfBounds(span, len, index) =>
write!(f, "index out of bounds: the len is {} but the index is {} at {:?}", len, index, span),
ReallocatedWrongMemoryKind(old, new) =>
write!(f, "tried to reallocate memory from {:?} to {:?}", old, new),
DeallocatedWrongMemoryKind(old, new) =>
write!(f, "tried to deallocate {:?} memory but gave {:?} as the kind", old, new),
Math(span, ref err) =>
write!(f, "{:?} at {:?}", err, span),
Intrinsic(ref err) =>

View file

@ -154,7 +154,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
) -> EvalResult<'tcx, MemoryPointer> {
let size = self.type_size_with_substs(ty, substs)?.expect("cannot alloc memory for unsized type");
let align = self.type_align_with_substs(ty, substs)?;
self.memory.allocate(size, align)
self.memory.allocate(size, align, ::memory::Kind::Stack)
}
pub fn memory(&self) -> &Memory<'a, 'tcx> {
@ -354,16 +354,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
// FIXME: to_ptr()? might be too extreme here, static zsts might reach this under certain conditions
Value::ByRef(ptr, _aligned) =>
// Alignment does not matter for this call
self.memory.mark_static_initalized(ptr.to_ptr()?.alloc_id, mutable)?,
self.memory.mark_static_initalized(ptr.to_ptr()?.alloc_id, !mutable)?,
Value::ByVal(val) => if let PrimVal::Ptr(ptr) = val {
self.memory.mark_inner_allocation(ptr.alloc_id, mutable)?;
self.memory.mark_inner_allocation(ptr.alloc_id, !mutable)?;
},
Value::ByValPair(val1, val2) => {
if let PrimVal::Ptr(ptr) = val1 {
self.memory.mark_inner_allocation(ptr.alloc_id, mutable)?;
self.memory.mark_inner_allocation(ptr.alloc_id, !mutable)?;
}
if let PrimVal::Ptr(ptr) = val2 {
self.memory.mark_inner_allocation(ptr.alloc_id, mutable)?;
self.memory.mark_inner_allocation(ptr.alloc_id, !mutable)?;
}
},
}
@ -414,11 +414,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
trace!("deallocating local");
let ptr = ptr.to_ptr()?;
self.memory.dump_alloc(ptr.alloc_id);
match self.memory.deallocate(ptr, None) {
// We could alternatively check whether the alloc_id is static before calling
// deallocate, but this is much simpler and is probably the rare case.
Ok(()) | Err(EvalError::DeallocatedStaticMemory) => {},
other => return other,
match self.memory.get(ptr.alloc_id)?.kind {
::memory::Kind::Static => {},
::memory::Kind::Stack => self.memory.deallocate(ptr, None, ::memory::Kind::Stack)?,
other => bug!("local contained non-stack memory: {:?}", other),
}
};
Ok(())
@ -693,11 +692,13 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
return Err(EvalError::NeedsRfc("\"heap\" allocations".to_string()));
}
// FIXME: call the `exchange_malloc` lang item if available
if self.type_size(ty)?.expect("box only works with sized types") == 0 {
let size = self.type_size(ty)?.expect("box only works with sized types");
if size == 0 {
let align = self.type_align(ty)?;
self.write_primval(dest, PrimVal::Bytes(align.into()), dest_ty)?;
} else {
let ptr = self.alloc_ptr(ty)?;
let align = self.type_align(ty)?;
let ptr = self.memory.allocate(size, align, ::memory::Kind::Rust)?;
self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?;
}
}
@ -1032,7 +1033,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_value_to_ptr(global_val.value, ptr.into(), global_val.ty)?;
// see comment on `initialized` field
if global_val.initialized {
self.memory.mark_static_initalized(ptr.alloc_id, global_val.mutable)?;
self.memory.mark_static_initalized(ptr.alloc_id, !global_val.mutable)?;
}
let lval = self.globals.get_mut(&cid).expect("already checked");
*lval = Global {
@ -1686,7 +1687,7 @@ pub fn eval_main<'a, 'tcx: 'a>(
}
// Return value
let ret_ptr = ecx.memory.allocate(ecx.tcx.data_layout.pointer_size.bytes(), ecx.tcx.data_layout.pointer_align.abi())?;
let ret_ptr = ecx.memory.allocate(ecx.tcx.data_layout.pointer_size.bytes(), ecx.tcx.data_layout.pointer_align.abi(), ::memory::Kind::Stack)?;
cleanup_ptr = Some(ret_ptr);
// Push our stack frame
@ -1728,7 +1729,7 @@ pub fn eval_main<'a, 'tcx: 'a>(
while ecx.step()? {}
if let Some(cleanup_ptr) = cleanup_ptr {
ecx.memory.deallocate(cleanup_ptr, None)?;
ecx.memory.deallocate(cleanup_ptr, None, ::memory::Kind::Stack)?;
}
return Ok(());
}

View file

@ -35,19 +35,25 @@ pub struct Allocation {
/// The alignment of the allocation to detect unaligned reads.
pub align: u64,
/// Whether the allocation may be modified.
pub mutable: bool,
/// Use the `mark_static_initalized` method of `Memory` to ensure that an error occurs, if the memory of this
/// allocation is modified or deallocated in the future.
pub static_kind: StaticKind,
/// Helps guarantee that stack allocations aren't deallocated via `rust_deallocate`
pub kind: Kind,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum StaticKind {
/// may be deallocated without breaking miri's invariants
NotStatic,
/// may be modified, but never deallocated
Mutable,
/// may neither be modified nor deallocated
Immutable,
pub enum Kind {
/// Error if deallocated any other way than `rust_deallocate`
Rust,
/// Error if deallocated any other way than `free`
C,
/// Error if deallocated via `rust_deallocate`
Stack,
/// May never be deallocated
Static,
/// Part of env var emulation
Env,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -181,14 +187,14 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
return Ok(MemoryPointer::new(alloc_id, 0));
}
let ptr = self.allocate(bytes.len() as u64, 1)?;
let ptr = self.allocate(bytes.len() as u64, 1, Kind::Static)?;
self.write_bytes(PrimVal::Ptr(ptr), bytes)?;
self.mark_static_initalized(ptr.alloc_id, false)?;
self.mark_static_initalized(ptr.alloc_id, true)?;
self.literal_alloc_cache.insert(bytes.to_vec(), ptr.alloc_id);
Ok(ptr)
}
pub fn allocate(&mut self, size: u64, align: u64) -> EvalResult<'tcx, MemoryPointer> {
pub fn allocate(&mut self, size: u64, align: u64, kind: Kind) -> EvalResult<'tcx, MemoryPointer> {
assert_ne!(align, 0);
assert!(align.is_power_of_two());
@ -206,7 +212,8 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
relocations: BTreeMap::new(),
undef_mask: UndefMask::new(size),
align,
static_kind: StaticKind::NotStatic,
kind,
mutable: true,
};
let id = self.next_id;
self.next_id.0 += 1;
@ -214,49 +221,45 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
Ok(MemoryPointer::new(id, 0))
}
// 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: MemoryPointer, old_size: u64, old_align: u64, new_size: u64, new_align: u64) -> EvalResult<'tcx, MemoryPointer> {
pub fn reallocate(&mut self, ptr: MemoryPointer, old_size: u64, old_align: u64, new_size: u64, new_align: u64, kind: Kind) -> EvalResult<'tcx, MemoryPointer> {
use std::cmp::min;
// TODO(solson): Report error about non-__rust_allocate'd pointer.
if ptr.offset != 0 || self.get(ptr.alloc_id).is_err() {
return Err(EvalError::ReallocateNonBasePtr);
}
if self.get(ptr.alloc_id).ok().map_or(false, |alloc| alloc.static_kind != StaticKind::NotStatic) {
return Err(EvalError::ReallocatedStaticMemory);
if let Ok(alloc) = self.get(ptr.alloc_id) {
if alloc.kind != kind {
return Err(EvalError::ReallocatedWrongMemoryKind(alloc.kind, kind));
}
}
// For simplicities' sake, we implement reallocate as "alloc, copy, dealloc"
let new_ptr = self.allocate(new_size, new_align)?;
let new_ptr = self.allocate(new_size, new_align, kind)?;
self.copy(ptr.into(), new_ptr.into(), min(old_size, new_size), min(old_align, new_align), /*nonoverlapping*/true)?;
self.deallocate(ptr, Some((old_size, old_align)))?;
self.deallocate(ptr, Some((old_size, old_align)), kind)?;
Ok(new_ptr)
}
// TODO(solson): See comment on `reallocate`.
pub fn deallocate(&mut self, ptr: MemoryPointer, size_and_align: Option<(u64, u64)>) -> EvalResult<'tcx> {
pub fn deallocate(&mut self, ptr: MemoryPointer, size_and_align: Option<(u64, u64)>, kind: Kind) -> EvalResult<'tcx> {
if ptr.offset != 0 || self.get(ptr.alloc_id).is_err() {
// TODO(solson): Report error about non-__rust_allocate'd pointer.
return Err(EvalError::DeallocateNonBasePtr);
}
{
// deallocate_local in eval_context.rs relies on nothing actually having changed when this error occurs.
// So we do this test in advance.
let alloc = self.get(ptr.alloc_id)?;
if alloc.static_kind != StaticKind::NotStatic {
return Err(EvalError::DeallocatedStaticMemory);
}
if let Some((size, align)) = size_and_align {
if size != alloc.bytes.len() as u64 || align != alloc.align {
return Err(EvalError::IncorrectAllocationInformation);
}
let alloc = match self.alloc_map.remove(&ptr.alloc_id) {
Some(alloc) => alloc,
None => return Err(EvalError::DoubleFree),
};
if alloc.kind != kind {
return Err(EvalError::DeallocatedWrongMemoryKind(alloc.kind, kind));
}
if let Some((size, align)) = size_and_align {
if size != alloc.bytes.len() as u64 || align != alloc.align {
return Err(EvalError::IncorrectAllocationInformation);
}
}
let alloc = self.alloc_map.remove(&ptr.alloc_id).expect("already verified");
self.memory_usage -= alloc.bytes.len() as u64;
debug!("deallocated : {}", ptr.alloc_id);
@ -401,10 +404,10 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
pub fn get_mut(&mut self, id: AllocId) -> EvalResult<'tcx, &mut Allocation> {
match self.alloc_map.get_mut(&id) {
Some(alloc) => match alloc.static_kind {
StaticKind::Mutable |
StaticKind::NotStatic => Ok(alloc),
StaticKind::Immutable => Err(EvalError::ModifiedConstantMemory),
Some(alloc) => if alloc.mutable {
Ok(alloc)
} else {
Err(EvalError::ModifiedConstantMemory)
},
None => match self.functions.get(&id) {
Some(_) => Err(EvalError::DerefFunctionPointer),
@ -473,10 +476,13 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
}
}
let immutable = match alloc.static_kind {
StaticKind::Mutable => " (static mut)",
StaticKind::Immutable => " (immutable)",
StaticKind::NotStatic => "",
let immutable = match (alloc.kind, alloc.mutable) {
(Kind::Static, true) => " (static mut)",
(Kind::Static, false) => " (immutable)",
(Kind::Env, _) => " (env var)",
(Kind::C, _) => " (malloc)",
(Kind::Rust, _) => " (heap)",
(Kind::Stack, _) => " (stack)",
};
trace!("{}({} bytes, alignment {}){}", msg, alloc.bytes.len(), alloc.align, immutable);
@ -503,7 +509,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
let leaks: Vec<_> = self.alloc_map
.iter()
.filter_map(|(&key, val)| {
if val.static_kind == StaticKind::NotStatic {
if val.kind != Kind::Static {
Some(key)
} else {
None
@ -578,26 +584,31 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
}
/// mark an allocation pointed to by a static as static and initialized
pub fn mark_inner_allocation(&mut self, alloc: AllocId, mutable: bool) -> EvalResult<'tcx> {
pub fn mark_inner_allocation(&mut self, alloc: AllocId, make_immutable: bool) -> EvalResult<'tcx> {
// relocations into other statics are not "inner allocations"
if !self.static_alloc.contains(&alloc) {
self.mark_static_initalized(alloc, mutable)?;
self.mark_static_initalized(alloc, make_immutable)?;
}
Ok(())
}
/// mark an allocation as static and initialized, either mutable or not
pub fn mark_static_initalized(&mut self, alloc_id: AllocId, mutable: bool) -> EvalResult<'tcx> {
trace!("mark_static_initialized {:?}, mutable: {:?}", alloc_id, mutable);
pub fn mark_static_initalized(&mut self, alloc_id: AllocId, make_immutable: bool) -> EvalResult<'tcx> {
trace!("mark_static_initalized {:?}, make_immutable: {:?}", alloc_id, make_immutable);
// do not use `self.get_mut(alloc_id)` here, because we might have already marked a
// sub-element or have circular pointers (e.g. `Rc`-cycles)
let relocations = match self.alloc_map.get_mut(&alloc_id) {
Some(&mut Allocation { ref mut relocations, static_kind: ref mut kind @ StaticKind::NotStatic, .. }) => {
*kind = if mutable {
StaticKind::Mutable
} else {
StaticKind::Immutable
};
Some(&mut Allocation { kind: Kind::Static, ref mut mutable, .. }) => {
if make_immutable {
*mutable = false;
}
return Ok(());
},
Some(&mut Allocation { ref mut relocations, ref mut kind, ref mut mutable, .. }) => {
*kind = Kind::Static;
if make_immutable {
*mutable = false;
}
// take out the relocations vector to free the borrow on self, so we can call
// mark recursively
mem::replace(relocations, Default::default())
@ -607,7 +618,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
};
// recurse into inner allocations
for &alloc in relocations.values() {
self.mark_inner_allocation(alloc, mutable)?;
self.mark_inner_allocation(alloc, make_immutable)?;
}
// put back the relocations
self.alloc_map.get_mut(&alloc_id).expect("checked above").relocations = relocations;

View file

@ -9,7 +9,7 @@ use syntax::abi::Abi;
use error::{EvalError, EvalResult};
use eval_context::{EvalContext, IntegerExt, StackPopCleanup, is_inhabited};
use lvalue::Lvalue;
use memory::{MemoryPointer, TlsKey};
use memory::{MemoryPointer, TlsKey, Kind};
use value::{PrimVal, Value};
use rustc_data_structures::indexed_vec::Idx;
@ -558,7 +558,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
if !align.is_power_of_two() {
return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align));
}
let ptr = self.memory.allocate(size, align)?;
let ptr = self.memory.allocate(size, align, Kind::Rust)?;
self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?;
}
"alloc::heap::::__rust_alloc_zeroed" => {
@ -570,7 +570,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
if !align.is_power_of_two() {
return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align));
}
let ptr = self.memory.allocate(size, align)?;
let ptr = self.memory.allocate(size, align, Kind::Rust)?;
self.memory.write_repeat(ptr.into(), 0, size)?;
self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?;
}
@ -584,7 +584,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
if !align.is_power_of_two() {
return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align));
}
self.memory.deallocate(ptr, Some((old_size, align)))?;
self.memory.deallocate(ptr, Some((old_size, align)), Kind::Rust)?;
}
"alloc::heap::::__rust_realloc" => {
let ptr = args[0].into_ptr(&mut self.memory)?.to_ptr()?;
@ -601,7 +601,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
if !new_align.is_power_of_two() {
return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(new_align));
}
let new_ptr = self.memory.reallocate(ptr, old_size, old_align, new_size, new_align)?;
let new_ptr = self.memory.reallocate(ptr, old_size, old_align, new_size, new_align, Kind::Rust)?;
self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?;
}
@ -657,7 +657,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_null(dest, dest_ty)?;
} else {
let align = self.memory.pointer_size();
let ptr = self.memory.allocate(size, align)?;
let ptr = self.memory.allocate(size, align, Kind::C)?;
self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?;
}
}
@ -665,7 +665,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
"free" => {
let ptr = args[0].into_ptr(&mut self.memory)?;
if !ptr.is_null()? {
self.memory.deallocate(ptr.to_ptr()?, None)?;
self.memory.deallocate(ptr.to_ptr()?, None, Kind::C)?;
}
}
@ -789,7 +789,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
if let Some(old) = success {
if let Some(var) = old {
self.memory.deallocate(var, None)?;
self.memory.deallocate(var, None, Kind::Env)?;
}
self.write_null(dest, dest_ty)?;
} else {
@ -812,11 +812,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
if let Some((name, value)) = new {
// +1 for the null terminator
let value_copy = self.memory.allocate((value.len() + 1) as u64, 1)?;
let value_copy = self.memory.allocate((value.len() + 1) as u64, 1, Kind::Env)?;
self.memory.write_bytes(PrimVal::Ptr(value_copy), &value)?;
self.memory.write_bytes(PrimVal::Ptr(value_copy.offset(value.len() as u64, self.memory.layout)?), &[0])?;
if let Some(var) = self.env_vars.insert(name.to_owned(), value_copy) {
self.memory.deallocate(var, None)?;
self.memory.deallocate(var, None, Kind::Env)?;
}
self.write_null(dest, dest_ty)?;
} else {

View file

@ -1,7 +1,7 @@
use rustc::traits::{self, Reveal};
use eval_context::EvalContext;
use memory::MemoryPointer;
use memory::{MemoryPointer, Kind};
use value::{Value, PrimVal};
use rustc::hir::def_id::DefId;
@ -51,7 +51,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
let ptr_size = self.memory.pointer_size();
let methods = ::rustc::traits::get_vtable_methods(self.tcx, trait_ref);
let vtable = self.memory.allocate(ptr_size * (3 + methods.count() as u64), ptr_size)?;
let vtable = self.memory.allocate(ptr_size * (3 + methods.count() as u64), ptr_size, Kind::Static)?;
let drop = ::eval_context::resolve_drop_in_place(self.tcx, ty);
let drop = self.memory.create_fn_alloc(drop);
@ -68,7 +68,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
}
self.memory.mark_static_initalized(vtable.alloc_id, false)?;
self.memory.mark_static_initalized(vtable.alloc_id, true)?;
Ok(vtable)
}

View file

@ -0,0 +1,8 @@
fn main() {
let x = Box::new(42);
{
let bad_box: Box<i32> = unsafe { std::ptr::read(&x) };
drop(bad_box);
}
drop(x); //~ ERROR dangling pointer was dereferenced
}

View file

@ -0,0 +1,5 @@
fn main() {
let x = 42;
let bad_box = unsafe { std::mem::transmute::<&i32, Box<i32>>(&x) };
drop(bad_box); //~ ERROR tried to deallocate Stack memory but gave Rust as the kind
}