Skip to content

Borrow Windows provenance from a global_asm pointer - #483

Open
mmastrac wants to merge 8 commits into
masterfrom
provenance_windows_2
Open

Borrow Windows provenance from a global_asm pointer#483
mmastrac wants to merge 8 commits into
masterfrom
provenance_windows_2

Conversation

@mmastrac

@mmastrac mmastrac commented Jun 24, 2026

Copy link
Copy Markdown
Owner

As a further follow-up to the fix for #479, it is more correct for us to "borrow" provenance from a global_asm-defined symbol rather than using black_box which may or may not work in any given version.

//! ```text
//! # Build (set Cargo env vars when using plain rustc):
//! CARGO_CRATE_NAME=windows_provenance_repro \
//! CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=0 CARGO_PKG_VERSION_PATCH=1 \
//! rustc windows_provenance_repro.rs -O --target x86_64-pc-windows-msvc \
//!   --crate-type lib -C panic=abort --emit=llvm-ir -o repro.ll
//!
//! Four bounds strategies over the same `ITEM1`/`ITEM2` statics:
//!   sum_expose    — `expose` / `with_exposed_provenance` on Rust marker statics
//!   sum_black_box — same, but `black_box` blocks the provenance round-trip fold
//!   sum_asm       — per-section `global_asm` markers + `extern`
//!   sum_borrowed  — COMDAT provenance donor + `with_addr` (link-section style)
//!
//! At `-O`: `sum_expose` → `ret i32 0`, the other three load and return 85.
//! ```

#![no_std]

/// Same symbol naming as `link-section` (`mod.rs` provenance donor).
macro_rules! __ls_provenance_symbol {
    () => {
        concat!(
            "__ls_prov_",
            env!("CARGO_CRATE_NAME"),
            "_",
            env!("CARGO_PKG_VERSION_MAJOR"),
            "_",
            env!("CARGO_PKG_VERSION_MINOR"),
            "_",
            env!("CARGO_PKG_VERSION_PATCH"),
        )
    };
}

core::arch::global_asm!(core::concat!(
    ".section .rdata$",
    __ls_provenance_symbol!(),
    ",\"dr\",discard,",
    __ls_provenance_symbol!(),
    "\n",
    ".globl ",
    __ls_provenance_symbol!(),
    "\n",
    __ls_provenance_symbol!(),
    ":\n",
    ".byte 0\n",
));

unsafe extern "C" {
    #[link_name = __ls_provenance_symbol!()]
    static LS_PROVENANCE_DONOR: u8;
}

/// Borrow opaque provenance from the COMDAT donor, keep the real section address.
fn launder_pointer_provenance<T>(ptr: *const T) -> *const T {
    core::ptr::addr_of!(LS_PROVENANCE_DONOR).with_addr(ptr.addr()) as *const T
}

/// Non-ZST marker carrying `align_of::<T>()`.
#[repr(C)]
struct Alignment<T> {
    _align: [T; 0],
    _padding: u8,
}

impl<T> Alignment<T> {
    const fn new() -> Self {
        Self {
            _align: [],
            _padding: 0,
        }
    }
}

#[used]
#[link_section = ".data$items$a"]
static __START: Alignment<u32> = Alignment::new();

#[used]
#[link_section = ".data$items$b"]
static ITEM1: u32 = 42;

#[used]
#[link_section = ".data$items$b"]
static ITEM2: u32 = 43;

#[used]
#[link_section = ".data$items$c"]
static __END: Alignment<u32> = Alignment::new();

/// Zero-byte COFF boundary labels (LLVM sees `external global`, not internal constants).
const _: () = {
    mod asm_markers {
        core::arch::global_asm!(
            ".section .data$items$a,\"rw\"\n",
            ".globl __ls_items_s\n",
            ".align {align}\n",
            "__ls_items_s:\n",
            align = const core::mem::align_of::<u32>(),
        );
        core::arch::global_asm!(
            ".section .data$items$c,\"rw\"\n",
            ".globl __ls_items_e\n",
            ".align {align}\n",
            "__ls_items_e:\n",
            align = const core::mem::align_of::<u32>(),
        );
    }
};

fn raw_start_byte() -> *const u8 {
    unsafe { core::ptr::addr_of!(__START).cast::<u8>().add(1) }
}

fn raw_len() -> usize {
    let end = core::ptr::addr_of!(__END).cast::<u8>();
    end.addr()
        .wrapping_sub(raw_start_byte().addr())
        / core::mem::size_of::<u32>()
}

/// `expose` / `with_exposed_provenance` only — folds to `ret i32 0` at `-O`.
fn bounds_expose() -> (*const u32, usize) {
    let start = raw_start_byte();
    let ptr = core::ptr::with_exposed_provenance(start.expose_provenance()) as *const u32;
    (ptr, raw_len())
}

/// Same Rust markers, but `black_box` blocks LLVM from folding the round-trip.
fn bounds_black_box() -> (*const u32, usize) {
    let start = raw_start_byte();
    let exposed = core::ptr::with_exposed_provenance(start.expose_provenance());
    let ptr = core::hint::black_box(exposed) as *const u32;
    (ptr, raw_len())
}

/// Bounds from per-section asm-defined markers via `extern`.
fn bounds_asm() -> (*const u32, usize) {
    extern "C" {
        #[link_name = "__ls_items_s"]
        static ASM_START: u8;
        #[link_name = "__ls_items_e"]
        static ASM_END: u8;
    }
    let start = core::ptr::addr_of!(ASM_START);
    let end = core::ptr::addr_of!(ASM_END);
    let len = end.addr().wrapping_sub(start.addr()) / core::mem::size_of::<u32>();
    (start as *const u32, len)
}

/// Addresses from Rust markers; provenance borrowed from `LS_PROVENANCE_DONOR`.
fn bounds_borrowed() -> (*const u32, usize) {
    let ptr = launder_pointer_provenance(raw_start_byte()) as *const u32;
    (ptr, raw_len())
}

#[no_mangle]
pub extern "C" fn sum_expose() -> u32 {
    let (ptr, len) = bounds_expose();
    let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
    slice.iter().copied().sum()
}

#[no_mangle]
pub extern "C" fn sum_black_box() -> u32 {
    let (ptr, len) = bounds_black_box();
    let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
    slice.iter().copied().sum()
}

#[no_mangle]
pub extern "C" fn sum_asm() -> u32 {
    let (ptr, len) = bounds_asm();
    let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
    slice.iter().copied().sum()
}

#[no_mangle]
pub extern "C" fn sum_borrowed() -> u32 {
    let (ptr, len) = bounds_borrowed();
    let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
    slice.iter().copied().sum()
}

/// `0` when every working strategy returns 85; `-1` otherwise (Windows test harness).
#[no_mangle]
pub extern "C" fn check_all() -> i32 {
    if sum_black_box() == 85 && sum_asm() == 85 && sum_borrowed() == 85 {
        0
    } else {
        -1
    }
}

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant