-
Notifications
You must be signed in to change notification settings - Fork 8
Description
This code pattern is UB:
Lines 444 to 445 in b568432
| const NULL: *mut ConfigMmio = core::ptr::null_mut(); | |
| let uart = unsafe { &mut *NULL.with_addr(self.addr()) }; |
Background: Provenance is shadow state of pointers (in CHERI and Miri it actually exists at runtime), which is normally the identity of the allocation which a pointer/reference points to when it is created, but probably more. Dereferencing a pointer when its address points to memory which is outside of the region specified by its provenance is UB.
The pointer returned by core::ptr::null_mut() has no provenance. This code then uses with_addr, which is a specially-crafted API which manipulates the address of a pointer without changing its provenance. So the pointer which comes out of here now has a non-zero address, but it is not valid to do any reads or writes through this pointer, or to turn it into a reference by any means. The basic validity requirements of a reference include that their address fall within their provenance (as well as being non-null and aligned).
So the pattern of doing NULL.with_addr(self.addr()) is technically fine by itself, but a bit odd. But then this code dereferences the pointer to reborrow it into a mutable reference. And there we have UB.
This is particularly concerning, because while LLVM's understanding of provenance is a bit... unclear, it is pretty trigger-happy with null pointers, probably on account of how much UB surrounds them in C: rust-lang/rust#96538 I don't want to spread FUD, but this is just one of those areas where I really am not comfortable with the hand-wave I've seen from some people that "it's UB according to Stacked Borrows but LLVM won't exploit that".
So, some suggestions:
If you want a pointer with some particular address value which you are never going to dereference, use ptr::invalid{_mut}. The hope of this API is that the name reminds you that this pointer is not okay to dereference.
If you have special knowledge of some address which Rust did not allocate but which is perfectly valid to access on the machine, use ptr::from_exposed_addr{_mut}1. If you do not have strict_provenance available (because you're on stable) use an as cast from integer to pointer type (or sptr) 2.
#![feature(strict_provenance)]
fn main() {
const NULL: *const usize = core::ptr::null();
let a = 0usize;
let ptr = &a as *const usize;
unsafe {
let _x = &*NULL.with_addr(ptr.addr());
}
}error: Undefined Behavior: dereferencing pointer failed: 0x276f0[noalloc] is a dangling pointer (it has no provenance)
--> demo.rs:7:18
|
7 | let _x = &*NULL.with_addr(ptr.addr());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: 0x276f0[noalloc] is a dangling pointer (it has no provenance)
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at demo.rs:7:18