Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial work on Miri permissive-exposed-provenance #2059

Merged
merged 2 commits into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,17 @@ to Miri failing to detect cases of undefined behavior in a program.
application instead of raising an error within the context of Miri (and halting
execution). Note that code might not expect these operations to ever panic, so
this flag can lead to strange (mis)behavior.
* `-Zmiri-permissive-provenance` is **experimental**. This will make Miri do a
best-effort attempt to implement the semantics of
[`expose_addr`](https://doc.rust-lang.org/nightly/std/primitive.pointer.html#method.expose_addr)
and
[`ptr::from_exposed_addr`](https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html)
for pointer-to-int and int-to-pointer casts, respectively. This will
necessarily miss some bugs as those semantics are not efficiently
implementable in a sanitizer, but it will only miss bugs that concerns
memory/pointers which is subject to these operations. Also note that this flag
is currently incompatible with Stacked Borrows, so you will have to also pass
`-Zmiri-disable-stacked-borrows` to use this.
* `-Zmiri-seed=<hex>` configures the seed of the RNG that Miri uses to resolve
non-determinism. This RNG is used to pick base addresses for allocations.
When isolation is enabled (the default), this is also used to emulate system
Expand Down
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;
}
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
"-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();
carbotaniuman marked this conversation as resolved.
Show resolved Hide resolved

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))
}
carbotaniuman marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading