#162902·Rust

rustc and clang differ on noundef for hidden sret pointer parameters causing MSAN failures

Author: th0br0Created Sep 17, 2026Updated Sep 17, 2026
LabelsA-LLVMA-sanitizersC-bugA-ABI

Running the below code in msan (-Zsanitizer=memory) fails MSAN. A simple Cargo-based repro case can be found at https://github.com/th0br0/bug-rustc-cc-interop-msan.

Failure:

==3140423==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 ...

companion.cc:

struct Large {
  uint64_t a[3];
};

extern "C" Large cpp_return_large(uint64_t x) {
  return Large{x, 0, 0};
}

main.rs

#[allow(dead_code)]
pub enum Padded {
    Small([u8; 1]),
    Large([u32; 1]),
}

#[repr(C)]
pub struct Large(pub [u64; 3]);
unsafe extern "C" {
    pub fn cpp_return_large(x: u64) -> Large;
}

#[inline(never)]
pub fn pass_padded(_: Padded) {}

fn main() {
    pass_padded(Padded::Small([1]));
    let large = unsafe { cpp_return_large(42) };
}

Automated root causing found that in rustc https://github.com/rust-lang/rust/blob/main/compiler/rustc_target/src/callconv/mod.rs#L419 and https://github.com/rust-lang/rust/blob/main/compiler/rustc_codegen_llvm/src/abi.rs#L108 emit noundef but clang does not https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/CGCall.cpp#L3085 this then leads to this LLM-generated step-by-step:

  1. Step A: Rust calls a function by value with an 8-byte enum containing padding bytes (e.g. enum Padded { Small([u8; 1]), Large([u32; 1]) }, passed via PassMode::Cast(i64) without noundef), which stores non-zero padding shadow (0xffffff00_00000000) into __msan_param_tls[0].
  2. Step B: Rust calls a Clang-compiled extern "C" function that returns a > 16-byte struct via sret (Arg#0, %rdi). Because rustc marks the sret argument noundef, LLVM's MemorySanitizerPass::visitCallBase skips writing 0 to __msan_param_tls[0].
  3. Step C: Because Clang omits noundef on the sret parameter, MemorySanitizerPass in the C/C++ callee loads the stale shadow from __msan_param_tls[0] as the shadow for the sret pointer (%rdi) and traps when storing fields into *sret.

This supposedly can be fixed in either clang by emitting noundef or rust omitting noundef. I don't know which would be preferable.