Skip to content

Has anyone else been blocked by the null pointer restriction at address 0x0? #908

@H4n-uL

Description

@H4n-uL

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions