Skip to content

miri: ensure validity of references and pointers we dereference and cast - #160012

Merged
rust-bors[bot] merged 4 commits into
rust-lang:mainfrom
RalfJung:deref-validity
Aug 1, 2026
Merged

miri: ensure validity of references and pointers we dereference and cast#160012
rust-bors[bot] merged 4 commits into
rust-lang:mainfrom
RalfJung:deref-validity

Conversation

@RalfJung

@RalfJung RalfJung commented Jul 27, 2026

Copy link
Copy Markdown
Member

View all comments

Conceptually, when we evaluate the place expression *place, there are two steps that happen:

  • perform a (typed) load from place, which yields a value of pointer/ref type
  • construct a new place from that value

Since this is a typed load, we have to ensure that the value satisfies the validity invariant. We forgot to do that, which led to Miri missing a bunch of UB. This PR fixes that by adding the missing checks.

Triggering this UB is a bit non-trivial: you cannot just store an invalid value into place using regular assignment (that would already be caught as part of the assignment). However, you can take a raw pointer to place and then use that to mutate the contents of place in a way that its validity invariant no longer holds. For example:

fn main() {
    let mut x = &();
    // Overwrite `x` with null.
    unsafe { (&raw mut x).cast::<usize>().write(0) };
    let _val = *x; //~ERROR: null reference
}

This program clearly (IMO) should have UB, and has UB in MiniRust, but Miri accepted that program before this PR. The same applies to references that are invalid because they are unaligned (even if we end up only accessing a 1-aligned field, i.e. the rest of the place expression evaluates just fine), and to references that are invalid because they are not dereferenceable (even if we end up projecting to a zero-sized field, i.e., the rest of the place expression evaluates just fine).

Even raw pointers have this problem: a raw pointer can be invalid if its vtable pointer is wrong, and we did not check that.

And finally, this also affects Box, and this is where things get tricky. Box derefs are not even present any more in the MIR Miri sees; they have been replaced by derefs of the underlying raw pointer. But that means we have no way to know that we should check all those things. So to fix that I adjusted the ElaborateBoxDerefs to emit a new special kind of cast that's almost a transmute but also checks Box validity.

Fixes rust-lang/miri#5226
Fixes rust-lang/unsafe-code-guidelines#617
Cc @rust-lang/opsem

@rustbot

rustbot commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

miri is developed in its own repository. If the Miri part of this change can be broken out, consider making this change to rust-lang/miri instead. However, if Miri needs adjusting for rustc changes, just ignore this message.

cc @rust-lang/miri

Some changes occurred to the CTFE machinery

cc @oli-obk, @lcnr

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri, @oli-obk, @lcnr

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 27, 2026
@rustbot

rustbot commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

r? @chenyukang

rustbot has assigned @chenyukang.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler, mir
  • compiler, mir expanded to 74 candidates
  • Random selection from 18 candidates

@rust-log-analyzer

This comment has been minimized.

@theemathas

Copy link
Copy Markdown
Contributor

Does this PR affect whether consteval code compiles? If so, do we need a crater run?

@RalfJung

Copy link
Copy Markdown
Member Author

This probably improves our const-eval UB detection capabilities, but the cases this affects are so niche I don't think it needs a crater run.

@rust-log-analyzer

This comment has been minimized.

@oli-obk

oli-obk commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

So to fix that I had to disable the ElaborateBoxDerefs pass in Miri, and make Miri able to deref Box natively. (The code for that is annoying but we already needed that same code to check Box validity to I just moved it to a shared helper.)

Hmm... I don't like it, but I see why that would happen. Tho I worry that will be an issue for miri + mir-opts, as the latter don't expect box derefs anymore.

Ideally we'd track extra stuff to check as some sort of marker like the now-gone StatementKind::Retag. But that's also a lot of noise for a weird edge case.

Cant you instead detect the box deref via the long chained projection? Can't be worse than having to manually turn a box deref back into a longer projection

@RalfJung

Copy link
Copy Markdown
Member Author

Yeah I don't like it either, I just could not think of anything better. Maybe we could have separate MIR phase for "post box deref removal" and a flag that somehow skips that entire phase in the MIR pipeline (so you can't even turn on the passes in that phase manually) but I can't think of a nice way to do that.

Cant you instead detect the box deref via the long chained projection? Can't be worse than having to manually turn a box deref back into a longer projection

If someone actually spells out that projection, I don't think it should behave like a typed load of the entire Box. ElaborateBoxDerefs is fundamentally a non-UB-preserving transformation in my eyes. And, well, a mandatory UB-removing transform is just bad news for any UB detection tool.

@oli-obk oli-obk assigned oli-obk and unassigned chenyukang Jul 27, 2026
@RalfJung
RalfJung force-pushed the deref-validity branch 3 times, most recently from d83d238 to 45ae4d4 Compare July 28, 2026 16:26
@oli-obk

oli-obk commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

If someone actually spells out that projection, I don't think it should behave like a typed load of the entire Box.

Doesn't seem like much of an issue to me considering how special Box is, but I see how it could technically cause UB to be introduced by some MIR opt that ends up with such an access, tho all situations I could think of were already UB, or very fishy creations of &mut Box<T> from a &mut *mut T without ever actually having that Box anywhere.

ElaborateBoxDerefs is fundamentally a non-UB-preserving transformation in my eyes. And, well, a mandatory UB-removing transform is just bad news for any UB detection tool.

hmm... special casing box more (again?) doesn't seem too great either. What's missing here for a general solution? Can we get there via some intermediate marker on ProjectionKind::Deref that distinguishes the derefs instead of relying on the type?

@RalfJung

Copy link
Copy Markdown
Member Author

hmm... special casing box more (again?) doesn't seem too great either.

Well, Box fundamentally is special in the opsem. So what's missing is rust-lang/rfcs#3712. ;)

As long as Box is special, that needs to be represented somehow. This PR represents it the way we used to, with a primitive Deref on Box. One could also imagine

  • A special "ensure this is a dereferenceable non-null pointer" instruction that codegen can compile to nothing, inserted by ElaborateBoxDerefs.
  • The Deref could be decorated with information about the actual type of the pointer it is deref'ing.
  • ElaborateBoxDerefs introduces a transmute cast from the inner NonNull to a raw pointer. That could be replaced by a special kind of cast that checks if the input is a valid box. (I don't know why we even bother projecting to the NonNull before transmuting -- we could just project only to the Unique and then transmute that.)

I think among these three I prefer the last one.

@rustbot

rustbot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

rustc_codegen_cranelift is developed in its own repository. If possible, consider making this change to rust-lang/rustc_codegen_cranelift instead.

cc @bjorn3

This PR changes rustc_public

cc @oli-obk, @celinval, @ouz-a, @makai410

This PR changes MIR

cc @oli-obk, @JakobDegen, @vakaras

@RalfJung

Copy link
Copy Markdown
Member Author

I think among these three I prefer the last one.

I have now implemented option three.

@rust-log-analyzer

This comment has been minimized.

@rustbot

rustbot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

clippy is developed in its own repository. If possible, consider making this change to rust-lang/rust-clippy instead.

cc @rust-lang/clippy

@rustbot rustbot added the T-clippy Relevant to the Clippy team. label Jul 29, 2026
@rust-log-analyzer

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Aug 1, 2026
miri: ensure validity of references and pointers we dereference and cast


try-job: x86_64-gnu-llvm-21
@rust-bors

rust-bors Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 5b654bb (5b654bbca05a898f69a47d8ece4a8cdbf011ab11)
Base parent: 6c04025 (6c04025a8a1d2b4b25283bb56b6c6450e3d38ba7)

@jhpratt

jhpratt commented Aug 1, 2026

Copy link
Copy Markdown
Member

@bors r=oli-obk

@rust-bors

rust-bors Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📌 Commit fa7f448 has been approved by oli-obk

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Aug 1, 2026
@rust-bors rust-bors Bot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Aug 1, 2026
@rust-bors

rust-bors Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ A new commit 907a5620f23723872a971166b9c5bf6aa2175c8d was pushed.

This pull request was unapproved.

@RalfJung

RalfJung commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

I realized boxed_ty exists and made use of it.
@bors r=oli-obk

@rust-bors

rust-bors Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 907a562 has been approved by oli-obk

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Aug 1, 2026
jhpratt added a commit to jhpratt/rust that referenced this pull request Aug 1, 2026
miri: ensure validity of references and pointers we dereference and cast

Conceptually, when we evaluate the place expression `*place`, there are two steps that happen:
- perform a (typed) load from `place`, which yields a value of pointer/ref type
- construct a new place from that value

Since this is a typed load, we have to ensure that the value satisfies the validity invariant. We forgot to do that, which led to Miri missing a bunch of UB. This PR fixes that by adding the missing checks.

Triggering this UB is a bit non-trivial: you cannot just store an invalid value into `place` using regular assignment (that would already be caught as part of the assignment). However, you can take a raw pointer to `place` and then use that to mutate the contents of `place` in a way that its validity invariant no longer holds. For example:
```rust
fn main() {
    let mut x = &();
    // Overwrite `x` with null.
    unsafe { (&raw mut x).cast::<usize>().write(0) };
    let _val = *x; //~ERROR: null reference
}
```
This program clearly (IMO) should have UB, and has UB in MiniRust, but Miri accepted that program before this PR. The same applies to references that are invalid because they are unaligned (even if we end up only accessing a 1-aligned field, i.e. the rest of the place expression evaluates just fine), and to references that are invalid because they are not dereferenceable (even if we end up projecting to a zero-sized field, i.e., the rest of the place expression evaluates just fine).

Even raw pointers have this problem: a raw pointer can be invalid if its vtable pointer is wrong, and we did not check that.

And finally, this also affects `Box`, and this is where things get tricky. `Box` derefs are not even present any more in the MIR Miri sees; they have been replaced by derefs of the underlying raw pointer. But that means we have no way to know that we should check all those things. So to fix that I adjusted the ElaborateBoxDerefs to emit a new special kind of cast that's almost a transmute but also checks `Box` validity.

Fixes rust-lang/miri#5226
Fixes rust-lang/unsafe-code-guidelines#617
Cc @rust-lang/opsem
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Aug 1, 2026
miri: ensure validity of references and pointers we dereference and cast

Conceptually, when we evaluate the place expression `*place`, there are two steps that happen:
- perform a (typed) load from `place`, which yields a value of pointer/ref type
- construct a new place from that value

Since this is a typed load, we have to ensure that the value satisfies the validity invariant. We forgot to do that, which led to Miri missing a bunch of UB. This PR fixes that by adding the missing checks.

Triggering this UB is a bit non-trivial: you cannot just store an invalid value into `place` using regular assignment (that would already be caught as part of the assignment). However, you can take a raw pointer to `place` and then use that to mutate the contents of `place` in a way that its validity invariant no longer holds. For example:
```rust
fn main() {
    let mut x = &();
    // Overwrite `x` with null.
    unsafe { (&raw mut x).cast::<usize>().write(0) };
    let _val = *x; //~ERROR: null reference
}
```
This program clearly (IMO) should have UB, and has UB in MiniRust, but Miri accepted that program before this PR. The same applies to references that are invalid because they are unaligned (even if we end up only accessing a 1-aligned field, i.e. the rest of the place expression evaluates just fine), and to references that are invalid because they are not dereferenceable (even if we end up projecting to a zero-sized field, i.e., the rest of the place expression evaluates just fine).

Even raw pointers have this problem: a raw pointer can be invalid if its vtable pointer is wrong, and we did not check that.

And finally, this also affects `Box`, and this is where things get tricky. `Box` derefs are not even present any more in the MIR Miri sees; they have been replaced by derefs of the underlying raw pointer. But that means we have no way to know that we should check all those things. So to fix that I adjusted the ElaborateBoxDerefs to emit a new special kind of cast that's almost a transmute but also checks `Box` validity.

Fixes rust-lang/miri#5226
Fixes rust-lang/unsafe-code-guidelines#617
Cc @rust-lang/opsem
rust-bors Bot pushed a commit that referenced this pull request Aug 1, 2026
…uwer

Rollup of 10 pull requests

Successful merges:

 - #157572 (stabilize size_of_val_raw, align_of_val_raw, Layout::for_value_raw)
 - #160012 (miri: ensure validity of references and pointers we dereference and cast)
 - #160294 (Update Enzyme to resolve one of the open bugs)
 - #159503 (allocations: document that they can be read-only)
 - #160250 (When issuing suggestions for missing trait items, label unstable items)
 - #160251 (Replace unsafe usage of `NonNull::new_unchecked` with `Box::into_non_null`)
 - #160311 (Remove final use of sealed traits from stdlib)
 - #160313 (Make the noundef-on-Cast size guard explicit)
 - #160323 (Box::leak: tell people to avoid unleaking)
 - #160328 (Move `check_track_caller` into the attribute parser)
rust-bors Bot pushed a commit that referenced this pull request Aug 1, 2026
…uwer

Rollup of 12 pull requests

Successful merges:

 - #157572 (stabilize size_of_val_raw, align_of_val_raw, Layout::for_value_raw)
 - #160012 (miri: ensure validity of references and pointers we dereference and cast)
 - #160294 (Update Enzyme to resolve one of the open bugs)
 - #159503 (allocations: document that they can be read-only)
 - #160179 (std: Update `wasip3` crate dependency)
 - #160250 (When issuing suggestions for missing trait items, label unstable items)
 - #160251 (Replace unsafe usage of `NonNull::new_unchecked` with `Box::into_non_null`)
 - #160311 (Remove final use of sealed traits from stdlib)
 - #160313 (Make the noundef-on-Cast size guard explicit)
 - #160323 (Box::leak: tell people to avoid unleaking)
 - #160328 (Move `check_track_caller` into the attribute parser)
 - #160333 (Remove itertools dependency from `rustc_ast_pretty`)
@rust-bors
rust-bors Bot merged commit 9956011 into rust-lang:main Aug 1, 2026
13 checks passed
@rustbot rustbot added this to the 1.99.0 milestone Aug 1, 2026
rust-timer added a commit that referenced this pull request Aug 1, 2026
Rollup merge of #160012 - RalfJung:deref-validity, r=oli-obk

miri: ensure validity of references and pointers we dereference and cast

Conceptually, when we evaluate the place expression `*place`, there are two steps that happen:
- perform a (typed) load from `place`, which yields a value of pointer/ref type
- construct a new place from that value

Since this is a typed load, we have to ensure that the value satisfies the validity invariant. We forgot to do that, which led to Miri missing a bunch of UB. This PR fixes that by adding the missing checks.

Triggering this UB is a bit non-trivial: you cannot just store an invalid value into `place` using regular assignment (that would already be caught as part of the assignment). However, you can take a raw pointer to `place` and then use that to mutate the contents of `place` in a way that its validity invariant no longer holds. For example:
```rust
fn main() {
    let mut x = &();
    // Overwrite `x` with null.
    unsafe { (&raw mut x).cast::<usize>().write(0) };
    let _val = *x; //~ERROR: null reference
}
```
This program clearly (IMO) should have UB, and has UB in MiniRust, but Miri accepted that program before this PR. The same applies to references that are invalid because they are unaligned (even if we end up only accessing a 1-aligned field, i.e. the rest of the place expression evaluates just fine), and to references that are invalid because they are not dereferenceable (even if we end up projecting to a zero-sized field, i.e., the rest of the place expression evaluates just fine).

Even raw pointers have this problem: a raw pointer can be invalid if its vtable pointer is wrong, and we did not check that.

And finally, this also affects `Box`, and this is where things get tricky. `Box` derefs are not even present any more in the MIR Miri sees; they have been replaced by derefs of the underlying raw pointer. But that means we have no way to know that we should check all those things. So to fix that I adjusted the ElaborateBoxDerefs to emit a new special kind of cast that's almost a transmute but also checks `Box` validity.

Fixes rust-lang/miri#5226
Fixes rust-lang/unsafe-code-guidelines#617
Cc @rust-lang/opsem
@Kobzol

Kobzol commented Aug 1, 2026

Copy link
Copy Markdown
Member

@rust-timer build fe46b75

For #160339.

@rust-timer

Copy link
Copy Markdown
Collaborator

Queued fe46b75 with parent c47bb2f, future comparison URL.
There is currently 1 preceding artifact in the queue.
It will probably take at least ~1.8 hours until the benchmark run finishes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-clippy Relevant to the Clippy team. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

What is the operational semantics of Box derefs? Miri allows calling function item via null reference.

9 participants