Hi everyone,
I'm working on an RFC (Pre-RFC discussion here) that addresses the inability to construct references or slices at address 0x0 under the current abstract machine model.
The motivating case is real: a researcher at Universität Stuttgart's Institute of Space Systems had to fall back to inline assembly to read a bootloader image starting at 0x0 on Vorago chips like VA108xx - a rad-hard MCU used in aerospace - because slice::from_raw_parts constructs &[u8], which is instant UB at that address. (Original post, Source code)
I'd like to understand how widespread this problem is. If you've ever been blocked or forced into workarounds (inline assembly, volatile access, skipping bytes, etc.) because your hardware places valid objects, peripherals, or firmware structures at address 0x0, I'd really appreciate hearing about it - even briefly:
- What target / chip?
- What was at 0x0? (ordinary RAM, interrupt vector, device tree, etc.)
- What workaround did you use?
- Did the workaround cause any issues during code review, testing, or certification?
Your experience would directly strengthen the RFC's motivation and help demonstrate that this isn't an isolated edge case.
RFC tl;dr
Literal satellite researcher at Universität Stuttgart has to reference 0x0 to calculate CRC, Rust AM says "you must either use asm/volatile or waste a byte". This is absurd.
Generalised codes here
// This address is forced by the hardware.
// Rust does not get to choose it.
const BLOB_P: usize = 0;
const _: () = assert!(usize::BITS == 16);
#[unsafe(no_mangle)]
extern "C" fn ignite() -> ! {
// BLOB can never be read volatilely;
// There's no available RAM to copy the entire struct.
let mut blob = unsafe { &mut *(BLOB_P as *mut DevTreeBlob) };
// instant UB upon reference construction
let mapping = blob.foo();
blob.bar |= 0b1;
...
}
use core::slice::from_raw_parts as mkslice;
// `map` address is reported by the firmware.
// Rust does not get to choose it.
// Caller ensures there's at least one entry
#[unsafe(no_mangle)]
extern "C" fn spark(map: *const RamLayout, len: NonZeroUsize) -> ! {
for entry in unsafe { mkslice(map, len.get()).iter() } {
// instant UB upon calling `from_raw_parts`
// as `from_raw_parts` constructs `&T`
...
}
...
}
Phase 1 - Null-free pointer
- makes 0x0 valid for raw pointer, NOT FOR
&T
- performance penalty: almost none, even Linux is built with
-fno-delete-null-pointer-checks for 15+ yrs (CVE-2009-1897), no performance issue reported. OP patched rustc to benchmark it
- known issue:
core relies on &T internally (e.g. core::ptr::replace uses &mut *dst); Phase 2 can fix it
Phase 2 - Zeroable reference
- adds a "zeroable reference primitive"(say
@T) alongside &T
- something like
usize : NonZero<usize> == @T : &T
&T or Option<&T>: untouched, forever
- fixes issue of Phase 1
Why not library type:
// compiler/rustc_ty_utils/src/layout.rs:410-414
ty::Ref(_, pointee, _) | ty::RawPtr(pointee, _) => {
let mut data_ptr = scalar_unit(Pointer(AddressSpace::ZERO));
if !ty.is_raw_ptr() {
data_ptr.valid_range_mut().start = 1;
}
// library/core/src/ptr/mod.rs:1546-1569
pub const unsafe fn replace<T>(dst: *mut T, src: T) -> T {
/* ... */
mem::replace(&mut *dst, src)
}
}
uhh
Objections:
- "changing
&T will break everything" -> accepted, &T will never be changed
- "perf cost is significant" -> benchmarked, no counter-benchmark presented
- "existing unsafe code breaks" -> example requested (#83), no response
- "volatile is enough" -> actual satellite code demanded
slice::from_raw_parts
- "library type works" ->
Deref can't be implemented (lang team member ACK, #102); core depends on &T internally (#109)
- "
field projection solves it" -> depends on two unstabilised RFCs; core still routes through &T
- "extremely rare use case" -> is a literal satellite project
- "not a problem of Rust" -> what
Thank you for your time.
Hi everyone,
I'm working on an RFC (Pre-RFC discussion here) that addresses the inability to construct references or slices at address 0x0 under the current abstract machine model.
The motivating case is real: a researcher at Universität Stuttgart's Institute of Space Systems had to fall back to inline assembly to read a bootloader image starting at 0x0 on Vorago chips like VA108xx - a rad-hard MCU used in aerospace - because
slice::from_raw_partsconstructs&[u8], which is instant UB at that address. (Original post, Source code)I'd like to understand how widespread this problem is. If you've ever been blocked or forced into workarounds (inline assembly, volatile access, skipping bytes, etc.) because your hardware places valid objects, peripherals, or firmware structures at address 0x0, I'd really appreciate hearing about it - even briefly:
Your experience would directly strengthen the RFC's motivation and help demonstrate that this isn't an isolated edge case.
RFC tl;dr
Literal satellite researcher at Universität Stuttgart has to reference 0x0 to calculate CRC, Rust AM says "you must either use asm/volatile or waste a byte". This is absurd.
Generalised codes here
Phase 1 - Null-free pointer
&T-fno-delete-null-pointer-checksfor 15+ yrs (CVE-2009-1897), no performance issue reported. OP patchedrustcto benchmark itcorerelies on&Tinternally (e.g.core::ptr::replaceuses&mut *dst); Phase 2 can fix itPhase 2 - Zeroable reference
@T) alongside&Tusize:NonZero<usize>==@T:&T&TorOption<&T>: untouched, foreverWhy not library type:
uhh
Objections:
&Twill break everything" -> accepted,&Twill never be changedslice::from_raw_partsDerefcan't be implemented (lang team member ACK, #102);coredepends on&Tinternally (#109)field projectionsolves it" -> depends on two unstabilised RFCs;corestill routes through&TThank you for your time.