LLVM does not expose provenance if the return value of `expose_provenance` is dropped, causing a miscompile
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
LLVM's current implementation of exposed provenance does not match the specification of Rust's expose_provenance API (specifically, Rust allows the resulting provenance to be picked up by any exposed-provenance pointer with the right address, but LLVM only allows picking up the provenance from a usize that was produced by expose_provenance, i.e. LLVM is tracking the integer itself to see whether it comes from the expected place). This means that code can comply with the specification of exposed provenance, but still be miscompiled with LLVM.
This is related to #147538, but not the same (in particular, it does not make use of the i2p-p2i-opt and thus can't be fixed by disabling it).
I tried this code in release mode:
fn foo(xpr: &mut Vec<String>, xa: usize) {
assert!(xpr.len() == 4);
let xprp = &raw mut *xpr;
for i in 0..4 {
dbg!(&unsafe { &mut *xprp }[i]);
xprp.expose_provenance();
// xprp2 is equivalent to xprp, because it has the same
// address (xa) and picks up its exposed provenance
let xprp2: *mut Vec<String> =
core::ptr::with_exposed_provenance_mut(xa);
unsafe { &mut *xprp2 }.clear();
}
}
fn main() {
let mut x = vec![String::from("abcdefghijklmnop"),
String::from("bcdefghijklmnopq"),
String::from("cdefghijklmnopqr"),
String::from("defghijklmnopqrs")];
let xp = &raw mut x;
let xpr = unsafe { &mut *xp };
foo(xpr, xp.addr());
}
I expected to see this happen: On the second iteration of the loop in foo, the code panics with a bounds check error, because it is attempting to dereference the second element of a 0-element Vec (the Vec was cleared on the previous iteration, so now has 0 elements rather than 4).
Instead, this happened: LLVM optimises out the bounds check, causing a use-after-free in the dbg! statement (which in practice usually manifests as attempting to print invalid UTF-8). LLVM is assuming that xprp2 cannot alias xprp (because xprp is derived from xpr which is flagged noalias), so it optimises out the bounds check (which under LLVM's aliasing assumption is redundant to the assert! at the start of the function). However, under Rust semantics, they can legitimately alias because xprp2 can pick up the exposed provenance from xprp.
I expect code like this to be unlikely in practice: most people who know that Rust accepts it also know that LLVM has a lot of miscompiles related to exposed provenance, so few people are likely to write it with the expectation of it working.
Meta
Tested on the Rust playground in release mode. It reproduces on both the latest stable (1.97.1), and on the nightly available on the playground (1.99.0-nightly (2026-08-06 84b36a78a28a63f13417)).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the supplied Rust playground reproducer in release mode and compare the behavior with related issue #147538. Trace LLVM's exposed-provenance handling and verify the fix against the example's expected bounds-check panic, without introducing the reported use-after-free or miscompile.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100