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

Miri: better document and fix dynamic const pattern soundness checks #71655

Merged
merged 6 commits into from
Apr 30, 2020
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
2 changes: 1 addition & 1 deletion src/librustc_mir/const_eval/eval_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ fn validate_and_turn_into_const<'tcx>(
mplace.into(),
path,
&mut ref_tracking,
/*may_ref_to_static*/ is_static,
/*may_ref_to_static*/ ecx.memory.extra.can_access_statics,
)?;
}
}
Expand Down
11 changes: 10 additions & 1 deletion src/librustc_mir/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,12 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {

#[derive(Copy, Clone, Debug)]
pub struct MemoryExtra {
/// Whether this machine may read from statics
/// We need to make sure consts never point to anything mutable, even recursively. That is
/// relied on for pattern matching on consts with references.
/// To achieve this, two pieces have to work together:
/// * Interning makes everything outside of statics immutable.
/// * Pointers to allocations inside of statics can never leak outside, to a non-static global.
/// This boolean here controls the second part.
pub(super) can_access_statics: bool,
}

Expand Down Expand Up @@ -337,6 +342,10 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
} else if static_def_id.is_some() {
// Machine configuration does not allow us to read statics
// (e.g., `const` initializer).
// See const_eval::machine::MemoryExtra::can_access_statics for why
// this check is so important: if we could read statics, we could read pointers
// to mutable allocations *inside* statics. These allocations are not themselves
// statics, so pointers to them can get around the check in `validity.rs`.
Err(ConstEvalErrKind::ConstAccessesStatic.into())
} else {
// Immutable global, this read is fine.
Expand Down
4 changes: 3 additions & 1 deletion src/librustc_mir/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
// thing here: one maps to `GlobalAlloc::Static`, this is the "lazy" ID,
// and the other one is maps to `GlobalAlloc::Memory`, this is returned by
// `const_eval_raw` and it is the "resolved" ID.
// The resolved ID is never used by the interpreted progrma, it is hidden.
// The resolved ID is never used by the interpreted program, it is hidden.
// This is relied upon for soundness of const-patterns; a pointer to the resolved
// ID would "sidestep" the checks that make sure consts do not point to statics!
// The `GlobalAlloc::Memory` branch here is still reachable though; when a static
// contains a reference to memory that was created during its evaluation (i.e., not
// to another static), those inner references only exist in "resolved" form.
Expand Down
22 changes: 15 additions & 7 deletions src/librustc_mir/interpret/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,19 +404,27 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// Skip validation entirely for some external statics
let alloc_kind = self.ecx.tcx.alloc_map.lock().get(ptr.alloc_id);
if let Some(GlobalAlloc::Static(did)) = alloc_kind {
// `extern static` cannot be validated as they have no body.
// FIXME: Statics from other crates are also skipped.
// They might be checked at a different type, but for now we
// want to avoid recursing too deeply. This is not sound!
if !did.is_local() || self.ecx.tcx.is_foreign_item(did) {
return Ok(());
}
// See const_eval::machine::MemoryExtra::can_access_statics for why
// this check is so important.
// This check is reachable when the const just referenced the static,
// but never read it (so we never entered `before_access_global`).
// We also need to do it here instead of going on to avoid running
// into the `before_access_global` check during validation.
if !self.may_ref_to_static && self.ecx.tcx.is_static(did) {
throw_validation_failure!(
format_args!("a {} pointing to a static variable", kind),
self.path
);
}
// `extern static` cannot be validated as they have no body.
// FIXME: Statics from other crates are also skipped.
// They might be checked at a different type, but for now we
// want to avoid recursing too deeply. We might miss const-invalid data,
// but things are still sound otherwise (in particular re: consts
// referring to statics).
if !did.is_local() || self.ecx.tcx.is_foreign_item(did) {
return Ok(());
}
}
}
// Proceed recursively even for ZST, no reason to skip them!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub static mut ZERO: [u8; 1] = [0];
pub static ZERO_REF: &[u8; 1] = unsafe { &ZERO };
pub static mut OPT_ZERO: Option<u8> = Some(0);
3 changes: 2 additions & 1 deletion src/test/ui/consts/miri_unleashed/const_refers_to_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

// These tests only cause an error when *using* the const.
// These fail during CTFE (as they read a static), so they only cause an error
// when *using* the const.

const MUTATE_INTERIOR_MUT: usize = {
static FOO: AtomicUsize = AtomicUsize::new(0);
Expand Down
16 changes: 8 additions & 8 deletions src/test/ui/consts/miri_unleashed/const_refers_to_static.stderr
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
warning: skipping const checks
--> $DIR/const_refers_to_static.rs:14:5
--> $DIR/const_refers_to_static.rs:15:5
|
LL | FOO.fetch_add(1, Ordering::Relaxed)
| ^^^

warning: skipping const checks
--> $DIR/const_refers_to_static.rs:14:5
--> $DIR/const_refers_to_static.rs:15:5
|
LL | FOO.fetch_add(1, Ordering::Relaxed)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: skipping const checks
--> $DIR/const_refers_to_static.rs:21:17
--> $DIR/const_refers_to_static.rs:22:17
|
LL | unsafe { *(&FOO as *const _ as *const usize) }
| ^^^

warning: skipping const checks
--> $DIR/const_refers_to_static.rs:26:32
--> $DIR/const_refers_to_static.rs:27:32
|
LL | const READ_MUT: u32 = unsafe { MUTABLE };
| ^^^^^^^

warning: skipping const checks
--> $DIR/const_refers_to_static.rs:26:32
--> $DIR/const_refers_to_static.rs:27:32
|
LL | const READ_MUT: u32 = unsafe { MUTABLE };
| ^^^^^^^

error[E0080]: erroneous constant used
--> $DIR/const_refers_to_static.rs:31:5
--> $DIR/const_refers_to_static.rs:32:5
|
LL | MUTATE_INTERIOR_MUT;
| ^^^^^^^^^^^^^^^^^^^ referenced constant has errors

error[E0080]: erroneous constant used
--> $DIR/const_refers_to_static.rs:33:5
--> $DIR/const_refers_to_static.rs:34:5
|
LL | READ_INTERIOR_MUT;
| ^^^^^^^^^^^^^^^^^ referenced constant has errors

error[E0080]: erroneous constant used
--> $DIR/const_refers_to_static.rs:35:5
--> $DIR/const_refers_to_static.rs:36:5
|
LL | READ_MUT;
| ^^^^^^^^ referenced constant has errors
Expand Down
7 changes: 6 additions & 1 deletion src/test/ui/consts/miri_unleashed/const_refers_to_static2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

// These tests cause immediate error when *defining* the const.
// These only fail during validation (they do not use but just create a reference to a static),
// so they cause an immediate error when *defining* the const.

const REF_INTERIOR_MUT: &usize = { //~ ERROR undefined behavior to use this value
//~| NOTE encountered a reference pointing to a static variable
//~| NOTE
static FOO: AtomicUsize = AtomicUsize::new(0);
unsafe { &*(&FOO as *const _ as *const usize) }
//~^ WARN skipping const checks
};

// ok some day perhaps
const READ_IMMUT: &usize = { //~ ERROR it is undefined behavior to use this value
//~| NOTE encountered a reference pointing to a static variable
//~| NOTE
static FOO: usize = 0;
&FOO
//~^ WARN skipping const checks
Expand Down
12 changes: 8 additions & 4 deletions src/test/ui/consts/miri_unleashed/const_refers_to_static2.stderr
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
warning: skipping const checks
--> $DIR/const_refers_to_static2.rs:13:18
--> $DIR/const_refers_to_static2.rs:16:18
|
LL | unsafe { &*(&FOO as *const _ as *const usize) }
| ^^^

warning: skipping const checks
--> $DIR/const_refers_to_static2.rs:20:6
--> $DIR/const_refers_to_static2.rs:25:6
|
LL | &FOO
| ^^^

error[E0080]: it is undefined behavior to use this value
--> $DIR/const_refers_to_static2.rs:11:1
--> $DIR/const_refers_to_static2.rs:12:1
|
LL | / const REF_INTERIOR_MUT: &usize = {
LL | |
LL | |
LL | | static FOO: AtomicUsize = AtomicUsize::new(0);
LL | | unsafe { &*(&FOO as *const _ as *const usize) }
LL | |
Expand All @@ -23,9 +25,11 @@ LL | | };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.

error[E0080]: it is undefined behavior to use this value
--> $DIR/const_refers_to_static2.rs:18:1
--> $DIR/const_refers_to_static2.rs:21:1
|
LL | / const READ_IMMUT: &usize = {
LL | |
LL | |
LL | | static FOO: usize = 0;
LL | | &FOO
LL | |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// compile-flags: -Zunleash-the-miri-inside-of-you -Zdeduplicate-diagnostics
// aux-build:static_cross_crate.rs
#![allow(const_err)]

#![feature(exclusive_range_pattern, half_open_range_patterns, const_if_match, const_panic)]

extern crate static_cross_crate;

// Sneaky: reference to a mutable static.
// Allowing this would be a disaster for pattern matching, we could violate exhaustiveness checking!
const SLICE_MUT: &[u8; 1] = { //~ ERROR undefined behavior to use this value
//~| NOTE encountered a reference pointing to a static variable
//~| NOTE
unsafe { &static_cross_crate::ZERO }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't this be worked around if you do &static_cross_crate::ZERO[0] here, and then pattern matching suddenly sees the mutable memory?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would that happen? It's still a reference to a GlobalAlloc::Static.

I can add that testcase.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, that error does not get caught. But I do not understand why. The pointer should be the same, shouldn't it?

Are we somewhere accidentally "leaking" the internal pointer of the static, as opposed to the lazy one?

Copy link
Member Author

@RalfJung RalfJung Apr 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, never mind -- we have to actually use the constant to cause the error. (Not sure why though, we should be evaluating all consts whether they are used or not... probably because I did allow(const_err).)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we somewhere accidentally "leaking" the internal pointer of the static, as opposed to the lazy one?

If that case isn't doing it, then using &&[u8] for the static and using &*static_cross_crate::ZERO will definitely do it, or by using an enum and getting a reference to a field. Any operation that needs to actually read from the static in order to produce the reference will yield the internal AllocId instead of the lazy one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any operation that needs to actually read from the static in order to produce the reference will yield the internal AllocId instead of the lazy one.

I don't think that's true. The code in memory.rs is very careful to never leak the internal AllocId back to the interpreter.

Also, any code that actually reads from the static will fail the before_access_static dynamic check, and this is already covered.

//~^ WARN skipping const checks
};

const U8_MUT: &u8 = { //~ ERROR undefined behavior to use this value
//~| NOTE encountered a reference pointing to a static variable
//~| NOTE
unsafe { &static_cross_crate::ZERO[0] }
//~^ WARN skipping const checks
};

// Also test indirection that reads from other static. This causes a const_err.
#[warn(const_err)] //~ NOTE
const U8_MUT2: &u8 = { //~ NOTE
unsafe { &(*static_cross_crate::ZERO_REF)[0] }
//~^ WARN skipping const checks
//~| WARN [const_err]
//~| NOTE constant accesses static
};
#[warn(const_err)] //~ NOTE
const U8_MUT3: &u8 = { //~ NOTE
unsafe { match static_cross_crate::OPT_ZERO { Some(ref u) => u, None => panic!() } }
//~^ WARN skipping const checks
//~| WARN [const_err]
//~| NOTE constant accesses static
};

pub fn test(x: &[u8; 1]) -> bool {
match x {
SLICE_MUT => true,
//~^ ERROR could not evaluate constant pattern
&[1..] => false,
}
}

pub fn test2(x: &u8) -> bool {
match x {
U8_MUT => true,
//~^ ERROR could not evaluate constant pattern
&(1..) => false,
}
}

// We need to use these *in a pattern* to trigger the failure... likely because
// the errors above otherwise stop compilation too early?
pub fn test3(x: &u8) -> bool {
match x {
U8_MUT2 => true,
//~^ ERROR could not evaluate constant pattern
&(1..) => false,
}
}
pub fn test4(x: &u8) -> bool {
match x {
U8_MUT3 => true,
//~^ ERROR could not evaluate constant pattern
&(1..) => false,
}
}

fn main() {
unsafe {
static_cross_crate::ZERO[0] = 1;
}
// Now the pattern is not exhaustive any more!
test(&[0]);
test2(&0);
}
Loading