Miscompilation from interaction between mir-opt, Codegen and LLVM on merged statics
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Found in this zulip thread, based on @RalfJung's idea.
The comments in the code describe what I think is going on based on the way I constructed this code. Please take them with a grain of salt ^^
NOTE: The version below abuses that MIR-opt propagates the uninitialized memory from a global, which is then passed on to LLVM as zeroed out. See this comment for a version that instead uses LLVM propagating undef before passing it to the linker as zeroed out. It just happens to be that rustc and LLVM both have this "propagate uninit + pass on zero to the next stage" logic, which is only correct if the memory model allows different allocations with different contents to have the same address. See Ralf's comment right below this.
use std::mem::MaybeUninit;
#[inline(always)]
fn check<'a>(a: &[u8], b: &'a [MaybeUninit<u8>]) -> &'a [u8] {
if a.as_ptr().addr() != b.as_ptr().addr() || a.len() != b.len() {
std::process::abort()
}
// SAFETY: these point to the same memory, and that memory must be
// initialized and immutable based on the safety invariant of `a`.
unsafe { std::mem::transmute(b) }
}
static INIT: [u8; 1024] = [0; _];
unsafe extern "C" {
fn observe_addr(p: *const (), q: *const ());
}
#[unsafe(no_mangle)]
pub fn weird() {
let fixed_uninit: &'static [MaybeUninit<u8>; INIT.len()] = const {
let mut buf = [MaybeUninit::uninit(); _];
// interleave init and uninit memory to encourage merging with `INIT`
let mut i = 0;
while i < buf.len() {
buf[i].write(0);
i += 2;
}
&{ buf }
};
if fixed_uninit.as_ptr().addr() != INIT.as_ptr().addr() {
// This code runs unconditionally because the code below has UB
// This is bad if `observe_addr` requires that the args have different addresses!
loop { unsafe { observe_addr(INIT.as_ptr().cast(), fixed_uninit.as_ptr().cast()) } }
}
// There's a poison here. The match turns it into UB
match check(&INIT, fixed_uninit)[1] {
0 => {},
1.. => {},
}
}
@rustbot label T-opsem A-LLVM I-unsound I-miscompile A-mir-opt
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 with the reduced Rust example in the issue and reproduce it using the linked Godbolt example. Trace how MIR-opt, Codegen, and LLVM handle the merged statics and the differing initialization states. Done means the responsible compiler interaction and the expected memory-model behavior are established well enough to guide a fix.
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
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100