Skip to content

Commit

Permalink
Initial work on permissive provenance
Browse files Browse the repository at this point in the history
  • Loading branch information
carbotaniuman authored and RalfJung committed May 23, 2022
1 parent d60aa47 commit f7bc441
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 48 deletions.
8 changes: 6 additions & 2 deletions src/bin/miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use rustc_middle::{
};
use rustc_session::{config::ErrorOutputType, search_paths::PathKind, CtfeBacktrace};

use miri::BacktraceStyle;
use miri::{BacktraceStyle, ProvenanceMode};

struct MiriCompilerCalls {
miri_config: miri::MiriConfig,
Expand Down Expand Up @@ -384,10 +384,14 @@ fn main() {
miri_config.tag_raw = true;
}
"-Zmiri-strict-provenance" => {
miri_config.strict_provenance = true;
miri_config.provenance_mode = ProvenanceMode::Strict;
miri_config.tag_raw = true;
miri_config.check_number_validity = true;
}
"-Zmiri-permissive-provenance" => {
miri_config.provenance_mode = ProvenanceMode::Permissive;
miri_config.tag_raw = true;
}
"-Zmiri-mute-stdout-stderr" => {
miri_config.mute_stdout_stderr = true;
}
Expand Down
7 changes: 3 additions & 4 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ pub struct MiriConfig {
pub panic_on_unsupported: bool,
/// Which style to use for printing backtraces.
pub backtrace_style: BacktraceStyle,
/// Whether to enforce "strict provenance" rules. Enabling this means int2ptr casts return
/// pointers with an invalid provenance, i.e., not valid for any memory access.
pub strict_provenance: bool,
/// Which provenance to use for int2ptr casts
pub provenance_mode: ProvenanceMode,
/// Whether to ignore any output by the program. This is helpful when debugging miri
/// as its messages don't get intermingled with the program messages.
pub mute_stdout_stderr: bool,
Expand Down Expand Up @@ -144,7 +143,7 @@ impl Default for MiriConfig {
measureme_out: None,
panic_on_unsupported: false,
backtrace_style: BacktraceStyle::Short,
strict_provenance: false,
provenance_mode: ProvenanceMode::Legacy,
mute_stdout_stderr: false,
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -786,8 +786,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
fn mark_immutable(&mut self, mplace: &MemPlace<Tag>) {
let this = self.eval_context_mut();
// This got just allocated, so there definitely is a pointer here.
this.alloc_mark_immutable(mplace.ptr.into_pointer_or_addr().unwrap().provenance.alloc_id)
.unwrap();
let provenance = mplace.ptr.into_pointer_or_addr().unwrap().provenance;
this.alloc_mark_immutable(provenance.get_alloc_id().unwrap()).unwrap();
}

fn item_link_name(&self, def_id: DefId) -> Symbol {
Expand Down
123 changes: 103 additions & 20 deletions src/intptrcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,25 @@ use std::collections::hash_map::Entry;
use log::trace;
use rand::Rng;

use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_target::abi::{HasDataLayout, Size};

use crate::*;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ProvenanceMode {
/// Int2ptr casts return pointers with "wildcard" provenance
/// that basically matches that of all exposed pointers
/// (and SB tags, if enabled).
Permissive,
/// Int2ptr casts return pointers with an invalid provenance,
/// i.e., not valid for any memory access.
Strict,
/// Int2ptr casts determine the allocation they point to at cast time.
/// All allocations are considered exposed.
Legacy,
}

pub type GlobalState = RefCell<GlobalStateInner>;

#[derive(Clone, Debug)]
Expand All @@ -21,35 +35,37 @@ pub struct GlobalStateInner {
/// they do not have an `AllocExtra`.
/// This is the inverse of `int_to_ptr_map`.
base_addr: FxHashMap<AllocId, u64>,
/// Whether an allocation has been exposed or not. This cannot be put
/// into `AllocExtra` for the same reason as `base_addr`.
exposed: FxHashSet<AllocId>,
/// This is used as a memory address when a new pointer is casted to an integer. It
/// is always larger than any address that was previously made part of a block.
next_base_addr: u64,
/// Whether to enforce "strict provenance" rules. Enabling this means int2ptr casts return
/// pointers with an invalid provenance, i.e., not valid for any memory access.
strict_provenance: bool,
/// The provenance to use for int2ptr casts
provenance_mode: ProvenanceMode,
}

impl GlobalStateInner {
pub fn new(config: &MiriConfig) -> Self {
GlobalStateInner {
int_to_ptr_map: Vec::default(),
base_addr: FxHashMap::default(),
exposed: FxHashSet::default(),
next_base_addr: STACK_ADDR,
strict_provenance: config.strict_provenance,
provenance_mode: config.provenance_mode,
}
}
}

impl<'mir, 'tcx> GlobalStateInner {
pub fn ptr_from_addr(addr: u64, ecx: &MiriEvalContext<'mir, 'tcx>) -> Pointer<Option<Tag>> {
trace!("Casting 0x{:x} to a pointer", addr);
// Returns the exposed `AllocId` that corresponds to the specified addr,
// or `None` if the addr is out of bounds
fn alloc_id_from_addr(ecx: &MiriEvalContext<'mir, 'tcx>, addr: u64) -> Option<AllocId> {
let global_state = ecx.machine.intptrcast.borrow();

if global_state.strict_provenance {
return Pointer::new(None, Size::from_bytes(addr));
}
assert!(global_state.provenance_mode != ProvenanceMode::Strict);

let pos = global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr);

let alloc_id = match pos {
Ok(pos) => Some(global_state.int_to_ptr_map[pos].1),
Err(0) => None,
Expand All @@ -60,6 +76,7 @@ impl<'mir, 'tcx> GlobalStateInner {
// This never overflows because `addr >= glb`
let offset = addr - glb;
// If the offset exceeds the size of the allocation, don't use this `alloc_id`.

if offset
<= ecx
.get_alloc_size_and_align(alloc_id, AllocCheck::MaybeDead)
Expand All @@ -72,12 +89,65 @@ impl<'mir, 'tcx> GlobalStateInner {
None
}
}
};
// Pointers created from integers are untagged.
Pointer::new(
alloc_id.map(|alloc_id| Tag { alloc_id, sb: SbTag::Untagged }),
Size::from_bytes(addr),
)
}?;

// In legacy mode, we consider all allocations exposed.
if global_state.provenance_mode == ProvenanceMode::Legacy
|| global_state.exposed.contains(&alloc_id)
{
Some(alloc_id)
} else {
None
}
}

pub fn expose_addr(ecx: &MiriEvalContext<'mir, 'tcx>, alloc_id: AllocId) {
trace!("Exposing allocation id {:?}", alloc_id);

let mut global_state = ecx.machine.intptrcast.borrow_mut();
if global_state.provenance_mode == ProvenanceMode::Permissive {
global_state.exposed.insert(alloc_id);
}
}

pub fn ptr_from_addr_transmute(
ecx: &MiriEvalContext<'mir, 'tcx>,
addr: u64,
) -> Pointer<Option<Tag>> {
trace!("Transmuting 0x{:x} to a pointer", addr);

let global_state = ecx.machine.intptrcast.borrow();

// In legacy mode, we have to support int2ptr transmutes,
// so just pretend they do the same thing as a cast.
if global_state.provenance_mode == ProvenanceMode::Legacy {
Self::ptr_from_addr_cast(ecx, addr)
} else {
Pointer::new(None, Size::from_bytes(addr))
}
}

pub fn ptr_from_addr_cast(
ecx: &MiriEvalContext<'mir, 'tcx>,
addr: u64,
) -> Pointer<Option<Tag>> {
trace!("Casting 0x{:x} to a pointer", addr);

let global_state = ecx.machine.intptrcast.borrow();

if global_state.provenance_mode == ProvenanceMode::Strict {
Pointer::new(None, Size::from_bytes(addr))
} else if global_state.provenance_mode == ProvenanceMode::Legacy {
let alloc_id = Self::alloc_id_from_addr(ecx, addr);

Pointer::new(
alloc_id
.map(|alloc_id| Tag::Concrete(ConcreteTag { alloc_id, sb: SbTag::Untagged })),
Size::from_bytes(addr),
)
} else {
Pointer::new(Some(Tag::Wildcard), Size::from_bytes(addr))
}
}

fn alloc_base_addr(ecx: &MiriEvalContext<'mir, 'tcx>, alloc_id: AllocId) -> u64 {
Expand Down Expand Up @@ -136,14 +206,27 @@ impl<'mir, 'tcx> GlobalStateInner {
dl.overflowing_offset(base_addr, offset.bytes()).0
}

pub fn abs_ptr_to_rel(ecx: &MiriEvalContext<'mir, 'tcx>, ptr: Pointer<Tag>) -> Size {
pub fn abs_ptr_to_rel(
ecx: &MiriEvalContext<'mir, 'tcx>,
ptr: Pointer<Tag>,
) -> Option<(AllocId, Size)> {
let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance)
let base_addr = GlobalStateInner::alloc_base_addr(ecx, tag.alloc_id);

let alloc_id = if let Tag::Concrete(concrete) = tag {
concrete.alloc_id
} else {
GlobalStateInner::alloc_id_from_addr(ecx, addr.bytes())?
};

let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);

// Wrapping "addr - base_addr"
let dl = ecx.data_layout();
let neg_base_addr = (base_addr as i64).wrapping_neg();
Size::from_bytes(dl.overflowing_signed_offset(addr.bytes(), neg_base_addr).0)
Some((
alloc_id,
Size::from_bytes(dl.overflowing_signed_offset(addr.bytes(), neg_base_addr).0),
))
}

/// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ pub use crate::eval::{
create_ecx, eval_entry, AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, RejectOpWith,
};
pub use crate::helpers::{CurrentSpan, EvalContextExt as HelpersEvalContextExt};
pub use crate::intptrcast::ProvenanceMode;
pub use crate::machine::{
AllocExtra, Evaluator, FrameData, MiriEvalContext, MiriEvalContextExt, MiriMemoryKind, Tag,
NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
AllocExtra, ConcreteTag, Evaluator, FrameData, MiriEvalContext, MiriEvalContextExt,
MiriMemoryKind, Tag, NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
};
pub use crate::mono_hash_map::MonoHashMap;
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
Expand Down
70 changes: 53 additions & 17 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,22 @@ impl fmt::Display for MiriMemoryKind {

/// Pointer provenance (tag).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Tag {
pub enum Tag {
Concrete(ConcreteTag),
Wildcard,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ConcreteTag {
pub alloc_id: AllocId,
/// Stacked Borrows tag.
pub sb: SbTag,
}

#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
static_assert_size!(Pointer<Tag>, 24);
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
static_assert_size!(Pointer<Option<Tag>>, 24);
// #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
// static_assert_size!(Pointer<Option<Tag>>, 24);
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
static_assert_size!(ScalarMaybeUninit<Tag>, 32);

Expand All @@ -148,18 +154,31 @@ impl Provenance for Tag {
fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (tag, addr) = ptr.into_parts(); // address is absolute
write!(f, "0x{:x}", addr.bytes())?;
// Forward `alternate` flag to `alloc_id` printing.
if f.alternate() {
write!(f, "[{:#?}]", tag.alloc_id)?;
} else {
write!(f, "[{:?}]", tag.alloc_id)?;

match tag {
Tag::Concrete(tag) => {
// Forward `alternate` flag to `alloc_id` printing.
if f.alternate() {
write!(f, "[{:#?}]", tag.alloc_id)?;
} else {
write!(f, "[{:?}]", tag.alloc_id)?;
}
// Print Stacked Borrows tag.
write!(f, "{:?}", tag.sb)?;
}
Tag::Wildcard => {
write!(f, "[Wildcard]")?;
}
}
// Print Stacked Borrows tag.
write!(f, "{:?}", tag.sb)

Ok(())
}

fn get_alloc_id(self) -> Option<AllocId> {
Some(self.alloc_id)
match self {
Tag::Concrete(concrete) => Some(concrete.alloc_id),
Tag::Wildcard => None,
}
}
}

Expand Down Expand Up @@ -611,30 +630,41 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
} else {
SbTag::Untagged
};
Pointer::new(Tag { alloc_id: ptr.provenance, sb: sb_tag }, Size::from_bytes(absolute_addr))
Pointer::new(
Tag::Concrete(ConcreteTag { alloc_id: ptr.provenance, sb: sb_tag }),
Size::from_bytes(absolute_addr),
)
}

#[inline(always)]
fn ptr_from_addr_cast(
ecx: &MiriEvalContext<'mir, 'tcx>,
addr: u64,
) -> Pointer<Option<Self::PointerTag>> {
intptrcast::GlobalStateInner::ptr_from_addr(addr, ecx)
intptrcast::GlobalStateInner::ptr_from_addr_cast(ecx, addr)
}

#[inline(always)]
fn ptr_from_addr_transmute(
ecx: &MiriEvalContext<'mir, 'tcx>,
addr: u64,
) -> Pointer<Option<Self::PointerTag>> {
Self::ptr_from_addr_cast(ecx, addr)
intptrcast::GlobalStateInner::ptr_from_addr_transmute(ecx, addr)
}

#[inline(always)]
fn expose_ptr(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
_ptr: Pointer<Self::PointerTag>,
ecx: &mut InterpCx<'mir, 'tcx, Self>,
ptr: Pointer<Self::PointerTag>,
) -> InterpResult<'tcx> {
let tag = ptr.provenance;

if let Tag::Concrete(concrete) = tag {
intptrcast::GlobalStateInner::expose_addr(ecx, concrete.alloc_id);
}

// No need to do anything for wildcard pointers as
// their provenances have already been previously exposed.
Ok(())
}

Expand All @@ -645,7 +675,13 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
ptr: Pointer<Self::PointerTag>,
) -> Option<(AllocId, Size, Self::TagExtra)> {
let rel = intptrcast::GlobalStateInner::abs_ptr_to_rel(ecx, ptr);
Some((ptr.provenance.alloc_id, rel, ptr.provenance.sb))

let sb = match ptr.provenance {
Tag::Concrete(ConcreteTag { sb, .. }) => sb,
Tag::Wildcard => SbTag::Untagged,
};

rel.map(|(alloc_id, size)| (alloc_id, size, sb))
}

#[inline(always)]
Expand Down
Loading

0 comments on commit f7bc441

Please sign in to comment.