Control: reassign -1 rust-rustix 0.38.37 Control: affects -1 src:rust-timerfd
On Sun, May 04, 2025 at 11:41:36PM +0200, Chris Hofstaedtler wrote: > process didn't exit successfully: `CARGO=/usr/bin/cargo > CARGO_MANIFEST_DIR=/build/reproducible-path/rust-timerfd-1.5.0 > CARGO_MANIFEST_PATH=/build/reproducible-path/rust-timerfd-1.5.0/Cargo.toml > CARGO_PKG_AUTHORS='main() <m...@ehvag.de>' CARGO_PKG_DESCRIPTION='A rust > interface to the Linux kernel'\''s timerfd API' CARGO_PKG_HOMEPAGE='' > CARGO_PKG_LICENSE=MIT CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=timerfd > CARGO_PKG_README=README.md > CARGO_PKG_REPOSITORY='https://github.com/main--/rust-timerfd' > CARGO_PKG_RUST_VERSION='' CARGO_PKG_VERSION=1.5.0 CARGO_PKG_VERSION_MAJOR=1 > CARGO_PKG_VERSION_MINOR=5 CARGO_PKG_VERSION_PATCH=0 CARGO_PKG_VERSION_PRE='' > LD_LIBRARY_PATH='/build/reproducible-path/rust-timerfd-1.5.0/target/aarch64-unknown-linux-gnu/debug/deps:/build/reproducible-path/rust-timerfd-1.5.0/target/aarch64-unknown-linux-gnu/debug:/usr/lib/rustlib/aarch64-unknown-linux-gnu/lib' > > /build/reproducible-path/rust-timerfd-1.5.0/target/aarch64-unknown-linux-gnu/debug/deps/timerfd-782df7dd57033e04` > (signal: 11, SIGSEGV: invalid memory reference) This is caused by a Linux vDSO change on aarch64, see: https://github.com/torvalds/linux/commit/48f6430505c0b0498ee9020ce3cf9558b1caaaeb Upstream fix in rust-rustix: https://github.com/bytecodealliance/rustix/pull/1259 Attached is a backported patch that works for me. Chris
Backport of https://github.com/bytecodealliance/rustix/pull/1259 Necessary on aarch64 with linux kernel > 6.11 https://github.com/torvalds/linux/commit/48f6430505c0b0498ee9020ce3cf9558b1caaaeb Debian bug: #1104704 --- a/src/backend/linux_raw/process/syscalls.rs +++ b/src/backend/linux_raw/process/syscalls.rs @@ -41,7 +41,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] pub(crate) use crate::backend::vdso_wrappers::sched_getcpu; @@ -50,7 +51,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" )))] #[inline] pub(crate) fn sched_getcpu() -> usize { --- a/src/backend/linux_raw/vdso.rs +++ b/src/backend/linux_raw/vdso.rs @@ -20,6 +20,11 @@ use core::ptr::{null, null_mut}; use linux_raw_sys::elf::*; +#[cfg(target_arch = "s390x")] +type ElfHashEntry = u64; +#[cfg(not(target_arch = "s390x"))] +type ElfHashEntry = u32; + pub(super) struct Vdso { // Load information load_addr: *const Elf_Ehdr, @@ -29,10 +34,11 @@ // Symbol table symtab: *const Elf_Sym, symstrings: *const u8, - bucket: *const u32, - chain: *const u32, - nbucket: u32, - //nchain: u32, + gnu_hash: *const u32, + bucket: *const ElfHashEntry, + chain: *const ElfHashEntry, + nbucket: ElfHashEntry, + //nchain: ElfHashEntry, // Version table versym: *const u16, @@ -53,6 +59,16 @@ h } +fn gnu_hash(name: &CStr) -> u32 { + let mut h: u32 = 5381; + for s in name.to_bytes() { + h = h + .wrapping_add(h.wrapping_mul(32)) + .wrapping_add(u32::from(*s)); + } + h +} + /// Create a `Vdso` value by parsing the vDSO at the `sysinfo_ehdr` address. fn init_from_sysinfo_ehdr() -> Option<Vdso> { // SAFETY: The auxv initialization code does extensive checks to ensure @@ -73,7 +89,8 @@ pv_offset: 0, symtab: null(), symstrings: null(), - bucket: null(), + gnu_hash: null(), + bucket: null(), chain: null(), nbucket: 0, //nchain: 0, @@ -129,7 +146,7 @@ } // Fish out the useful bits of the dynamic table. - let mut hash: *const u32 = null(); + let mut hash: *const ElfHashEntry = null(); vdso.symstrings = null(); vdso.symtab = null(); vdso.versym = null(); @@ -152,9 +169,14 @@ .as_ptr(); } DT_HASH => { - hash = check_raw_pointer::<u32>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)? + hash = check_raw_pointer::<ElfHashEntry>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)? .as_ptr(); } + DT_GNU_HASH => { + vdso.gnu_hash = + check_raw_pointer::<u32>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)? + .as_ptr() + } DT_VERSYM => { vdso.versym = check_raw_pointer::<u16>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)? @@ -176,18 +198,37 @@ } i = i.checked_add(1)?; } - // The upstream code checks `symstrings`, `symtab`, and `hash` for - // null; here, `check_raw_pointer` has already done that. + + // `check_raw_pointer` will have checked these pointers for null, + // however they could still be null if the expected dynamic table + // entries are absent. + if vdso.symstrings.is_null() + || vdso.symtab.is_null() + || (hash.is_null() && vdso.gnu_hash.is_null()) + { + return None; // Failed + } if vdso.verdef.is_null() { vdso.versym = null(); } // Parse the hash table header. - vdso.nbucket = *hash.add(0); - //vdso.nchain = *hash.add(1); - vdso.bucket = hash.add(2); - vdso.chain = hash.add(vdso.nbucket as usize + 2); + if !vdso.gnu_hash.is_null() { + vdso.nbucket = ElfHashEntry::from(*vdso.gnu_hash); + // The bucket array is located after the header (4 uint32) and the bloom + // filter (size_t array of gnu_hash[2] elements). + vdso.bucket = vdso + .gnu_hash + .add(4) + .add(size_of::<c::size_t>() / 4 * *vdso.gnu_hash.add(2) as usize) + .cast(); + } else { + vdso.nbucket = *hash.add(0); + //vdso.nchain = *hash.add(1); + vdso.bucket = hash.add(2); + vdso.chain = hash.add(vdso.nbucket as usize + 2); + } // That's all we need. Some(vdso) @@ -253,6 +294,46 @@ && (name == CStr::from_ptr(self.symstrings.add(aux.vda_name as usize).cast())) } + /// Check to see if the symbol is the one we're looking for. + /// + /// # Safety + /// + /// The raw pointers inside `self` must be valid. + unsafe fn check_sym( + &self, + sym: &Elf_Sym, + i: ElfHashEntry, + name: &CStr, + version: &CStr, + ver_hash: u32, + ) -> bool { + // Check for a defined global or weak function w/ right name. + // + // Accept `STT_NOTYPE` in addition to `STT_FUNC` for the symbol + // type, for compatibility with some versions of Linux on + // PowerPC64. See [this commit] in Linux for more background. + // + // [this commit]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/tools/testing/selftests/vDSO/parse_vdso.c?id=0161bd38c24312853ed5ae9a425a1c41c4ac674a + if ELF_ST_TYPE(sym.st_info) != STT_FUNC && ELF_ST_TYPE(sym.st_info) != STT_NOTYPE { + return false; + } + if ELF_ST_BIND(sym.st_info) != STB_GLOBAL && ELF_ST_BIND(sym.st_info) != STB_WEAK { + return false; + } + if name != CStr::from_ptr(self.symstrings.add(sym.st_name as usize).cast()) { + return false; + } + + // Check symbol version. + if !self.versym.is_null() + && !self.match_version(*self.versym.add(i as usize), version, ver_hash) + { + return false; + } + + true + } + /// Look up a symbol in the vDSO. pub(super) fn sym(&self, version: &CStr, name: &CStr) -> *mut c::c_void { let ver_hash = elf_hash(version); @@ -260,38 +341,64 @@ // SAFETY: The pointers in `self` must be valid. unsafe { - let mut chain = *self.bucket.add((name_hash % self.nbucket) as usize); + if !self.gnu_hash.is_null() { + let mut h1: u32 = gnu_hash(name); - while chain != STN_UNDEF { - let sym = &*self.symtab.add(chain as usize); - - // Check for a defined global or weak function w/ right name. - // - // The reference parser in Linux's parse_vdso.c requires - // symbols to have type `STT_FUNC`, but on powerpc64, the vDSO - // uses `STT_NOTYPE`, so allow that too. - if (ELF_ST_TYPE(sym.st_info) != STT_FUNC && - ELF_ST_TYPE(sym.st_info) != STT_NOTYPE) - || (ELF_ST_BIND(sym.st_info) != STB_GLOBAL - && ELF_ST_BIND(sym.st_info) != STB_WEAK) - || sym.st_shndx == SHN_UNDEF - || sym.st_shndx == SHN_ABS - || ELF_ST_VISIBILITY(sym.st_other) != STV_DEFAULT - || (name != CStr::from_ptr(self.symstrings.add(sym.st_name as usize).cast())) - // Check symbol version. - || (!self.versym.is_null() - && !self.match_version(*self.versym.add(chain as usize), version, ver_hash)) - { - chain = *self.chain.add(chain as usize); - continue; - } - - let sum = self.addr_from_elf(sym.st_value).unwrap(); - assert!( - sum as usize >= self.load_addr as usize - && sum as usize <= self.load_end as usize - ); - return sum as *mut c::c_void; + // Changes to fix the pointer arithmetic on s390x: cast + // `self.bucket` to `*const u32` here, because even though + // s390x's `ElfHashEntry` is 64-bit for `DT_HASH` tables, + // it uses 32-bit entries for `DT_GNU_HASH` tables. + let mut i = *self + .bucket + .cast::<u32>() + .add((ElfHashEntry::from(h1) % self.nbucket) as usize); + if i == 0 { + return null_mut(); + } + h1 |= 1; + // Changes to fix the pointer arithmetic on s390x: As above, + // cast `self.bucket` to `*const u32`. + let mut hashval = self + .bucket + .cast::<u32>() + .add(self.nbucket as usize) + .add((i - *self.gnu_hash.add(1)) as usize); + loop { + let sym: &Elf_Sym = &*self.symtab.add(i as usize); + let h2 = *hashval; + hashval = hashval.add(1); + if h1 == (h2 | 1) + && self.check_sym(sym, ElfHashEntry::from(i), name, version, ver_hash) + { + let sum = self.addr_from_elf(sym.st_value).unwrap(); + assert!( + sum as usize >= self.load_addr as usize + && sum as usize <= self.load_end as usize + ); + return sum as *mut c::c_void; + } + if (h2 & 1) != 0 { + break; + } + i += 1; + } + } else { + let mut i = *self + .bucket + .add((ElfHashEntry::from(elf_hash(name)) % self.nbucket) as usize); + while i != 0 { + let sym: &Elf_Sym = &*self.symtab.add(i as usize); + if sym.st_shndx != SHN_UNDEF && self.check_sym(sym, i, name, version, ver_hash) + { + let sum = self.addr_from_elf(sym.st_value).unwrap(); + assert!( + sum as usize >= self.load_addr as usize + && sum as usize <= self.load_end as usize + ); + return sum as *mut c::c_void; + } + i = *self.chain.add(i as usize); + } } } --- a/src/backend/linux_raw/vdso_wrappers.rs +++ b/src/backend/linux_raw/vdso_wrappers.rs @@ -20,7 +20,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] use core::ffi::c_void; use core::mem::transmute; @@ -37,7 +38,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ) ), feature = "time" @@ -117,7 +119,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] #[inline] pub(crate) fn sched_getcpu() -> usize { @@ -268,7 +271,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] type GetcpuType = unsafe extern "C" fn(*mut u32, *mut u32, *mut c_void) -> c::c_int; @@ -294,7 +298,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] #[cold] fn init_getcpu() -> GetcpuType { @@ -324,7 +329,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] static mut GETCPU: AtomicPtr<Function> = AtomicPtr::new(null_mut()); #[cfg(target_arch = "x86")] @@ -393,7 +399,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] unsafe extern "C" fn rustix_getcpu_via_syscall( cpu: *mut u32, @@ -457,7 +464,8 @@ target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] { GETCPU @@ -559,11 +567,14 @@ let ptr = vdso.sym(cstr!("LINUX_4.15"), cstr!("__kernel_getcpu")); #[cfg(target_arch = "powerpc64")] let ptr = vdso.sym(cstr!("LINUX_2.6.15"), cstr!("__kernel_getcpu")); + #[cfg(target_arch = "s390x")] + let ptr = vdso.sym(cstr!("LINUX_2.6.29"), cstr!("__kernel_getcpu")); #[cfg(any( target_arch = "x86_64", target_arch = "riscv64", - target_arch = "powerpc64" + target_arch = "powerpc64", + target_arch = "s390x" ))] let ok = true;