diff --git a/src/imp/detect/aarch64_aa64reg.rs b/src/imp/detect/aarch64_aa64reg.rs index 5c4b329f..76727840 100644 --- a/src/imp/detect/aarch64_aa64reg.rs +++ b/src/imp/detect/aarch64_aa64reg.rs @@ -120,7 +120,7 @@ mod imp { use core::{mem, ptr}; - use super::AA64Reg; + use super::{c_str, AA64Reg}; // core::ffi::c_* (except c_void) requires Rust 1.64, libc requires Rust 1.63 #[allow(non_camel_case_types)] @@ -173,24 +173,21 @@ mod imp { }); } - pub(super) unsafe fn sysctl_cpu_id(name: &[u8]) -> Option { + pub(super) fn sysctl_cpu_id(name: &c_str::CStr) -> Option { const OUT_LEN: ffi::c_size_t = mem::size_of::() as ffi::c_size_t; - debug_assert_eq!(name.last(), Some(&0), "{:?}", name); - debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name); - // SAFETY: all fields of aarch64_sysctl_cpu_id are zero-able and we use // the result when machdep.cpuN.cpu_id sysctl was successful. let mut buf: ffi::aarch64_sysctl_cpu_id = unsafe { mem::zeroed() }; let mut out_len = OUT_LEN; // SAFETY: - // - the caller must guarantee that `name` is ` machdep.cpuN.cpu_id` in a C string. + // - `name` a valid C string. // - `out_len` does not exceed the size of the value at `buf`. // - `sysctlbyname` is thread-safe. let res = unsafe { ffi::sysctlbyname( - name.as_ptr().cast::(), + name.as_ptr(), (&mut buf as *mut ffi::aarch64_sysctl_cpu_id).cast::(), &mut out_len, ptr::null_mut(), @@ -211,11 +208,10 @@ mod imp { // Get system registers for cpu0. // If failed, returns default because machdep.cpuN.cpu_id sysctl is not available. // machdep.cpuN.cpu_id sysctl was added in NetBSD 9.0 so it is not available on older versions. - // SAFETY: we passed a valid name in a C string. // It is ok to check only cpu0, even if there are more CPUs. // https://github.com/NetBSD/src/commit/bd9707e06ea7d21b5c24df6dfc14cb37c2819416 // https://github.com/golang/sys/commit/ef9fd89ba245e184bdd308f7f2b4f3c551fa5b0f - match unsafe { sysctl_cpu_id(b"machdep.cpu0.cpu_id\0") } { + match sysctl_cpu_id(c!("machdep.cpu0.cpu_id")) { Some(cpu_id) => cpu_id, None => AA64Reg { aa64isar0: 0, aa64isar1: 0, aa64mmfr2: 0 }, } @@ -389,7 +385,7 @@ mod tests { // much as Linux does (It may actually be stable enough, though: https://lists.llvm.org/pipermail/llvm-dev/2019-June/133393.html). // // This is currently used only for testing. - unsafe fn sysctl_cpu_id_no_libc(name: &[&[u8]]) -> Result { + fn sysctl_cpu_id_no_libc(name: &[&[u8]]) -> Result { // https://github.com/golang/go/blob/4badad8d477ffd7a6b762c35bc69aed82faface7/src/syscall/asm_netbsd_arm64.s #[inline] unsafe fn sysctl( @@ -504,11 +500,9 @@ mod tests { }) } - unsafe { - assert_eq!( - imp::sysctl_cpu_id(b"machdep.cpu0.cpu_id\0").unwrap(), - sysctl_cpu_id_no_libc(&[b"machdep", b"cpu0", b"cpu_id"]).unwrap() - ); - } + assert_eq!( + imp::sysctl_cpu_id(c!("machdep.cpu0.cpu_id")).unwrap(), + sysctl_cpu_id_no_libc(&[b"machdep", b"cpu0", b"cpu_id"]).unwrap() + ); } } diff --git a/src/imp/detect/aarch64_apple.rs b/src/imp/detect/aarch64_apple.rs index b2cec8b7..e820cae6 100644 --- a/src/imp/detect/aarch64_apple.rs +++ b/src/imp/detect/aarch64_apple.rs @@ -42,21 +42,18 @@ mod ffi { }); } -unsafe fn sysctlbyname32(name: &[u8]) -> Option { +fn sysctlbyname32(name: &c_str::CStr) -> Option { const OUT_LEN: ffi::c_size_t = mem::size_of::() as ffi::c_size_t; - debug_assert_eq!(name.last(), Some(&0), "{:?}", name); - debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name); - let mut out = 0_u32; let mut out_len = OUT_LEN; // SAFETY: - // - the caller must guarantee that `name` a valid C string. + // - `name` a valid C string. // - `out_len` does not exceed the size of `out`. // - `sysctlbyname` is thread-safe. let res = unsafe { ffi::sysctlbyname( - name.as_ptr().cast::(), + name.as_ptr(), (&mut out as *mut u32).cast::(), &mut out_len, ptr::null_mut(), @@ -77,23 +74,18 @@ fn _detect(info: &mut CpuInfo) { // Query both names in case future versions of macOS remove the old name. // https://github.com/golang/go/commit/c15593197453b8bf90fc3a9080ba2afeaf7934ea // https://github.com/google/boringssl/commit/91e0b11eba517d83b910b20fe3740eeb39ecb37e - // SAFETY: we passed a valid C string. - if unsafe { - sysctlbyname32(b"hw.optional.arm.FEAT_LSE\0").unwrap_or(0) != 0 - || sysctlbyname32(b"hw.optional.armv8_1_atomics\0").unwrap_or(0) != 0 - } { + if sysctlbyname32(c!("hw.optional.arm.FEAT_LSE")).unwrap_or(0) != 0 + || sysctlbyname32(c!("hw.optional.armv8_1_atomics")).unwrap_or(0) != 0 + { info.set(CpuInfo::HAS_LSE); } - // SAFETY: we passed a valid C string. - if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LSE2\0").unwrap_or(0) != 0 } { + if sysctlbyname32(c!("hw.optional.arm.FEAT_LSE2")).unwrap_or(0) != 0 { info.set(CpuInfo::HAS_LSE2); } - // SAFETY: we passed a valid C string. - if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LSE128\0").unwrap_or(0) != 0 } { + if sysctlbyname32(c!("hw.optional.arm.FEAT_LSE128")).unwrap_or(0) != 0 { info.set(CpuInfo::HAS_LSE128); } - // SAFETY: we passed a valid C string. - if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC3\0").unwrap_or(0) != 0 } { + if sysctlbyname32(c!("hw.optional.arm.FEAT_LRCPC3")).unwrap_or(0) != 0 { info.set(CpuInfo::HAS_RCPC3); } } @@ -111,17 +103,15 @@ mod tests { #[test] fn test_macos() { - unsafe { - assert_eq!(sysctlbyname32(b"hw.optional.armv8_1_atomics\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE2\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE128\0"), None); - assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC2\0"), Some(1)); - assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC3\0"), None); - assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); - } + assert_eq!(sysctlbyname32(c!("hw.optional.armv8_1_atomics")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LSE")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LSE2")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LSE128")), None); + assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LRCPC")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LRCPC2")), Some(1)); + assert_eq!(sysctlbyname32(c!("hw.optional.arm.FEAT_LRCPC3")), None); + assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); } #[cfg(target_pointer_width = "64")] @@ -137,7 +127,7 @@ mod tests { // (And they actually changed it: https://go-review.googlesource.com/c/go/+/25495) // // This is currently used only for testing. - unsafe fn sysctlbyname32_no_libc(name: &[u8]) -> Result { + fn sysctlbyname32_no_libc(name: &c_str::CStr) -> Result { // https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/syscalls.master#L298 #[inline] unsafe fn sysctl( @@ -187,7 +177,7 @@ mod tests { } // https://github.com/apple-oss-distributions/Libc/blob/af11da5ca9d527ea2f48bb7efbd0f0f2a4ea4812/gen/FreeBSD/sysctlbyname.c unsafe fn sysctlbyname( - name: &[u8], + name: &c_str::CStr, old_p: *mut c_void, old_len_p: *mut c_size_t, new_p: *mut c_void, @@ -207,7 +197,7 @@ mod tests { real_oid.as_mut_ptr().cast::(), &mut oid_len, name.as_ptr().cast::() as *mut c_void, - name.len() - 1, + name.to_bytes_with_nul().len() - 1, )? }; oid_len /= mem::size_of::(); @@ -219,9 +209,6 @@ mod tests { const OUT_LEN: ffi::c_size_t = mem::size_of::() as ffi::c_size_t; - debug_assert_eq!(name.last(), Some(&0), "{:?}", name); - debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name); - let mut out = 0_u32; let mut out_len = OUT_LEN; // SAFETY: @@ -243,20 +230,18 @@ mod tests { } for name in [ - &b"hw.optional.armv8_1_atomics\0"[..], - b"hw.optional.arm.FEAT_LSE\0", - b"hw.optional.arm.FEAT_LSE2\0", - b"hw.optional.arm.FEAT_LSE128\0", - b"hw.optional.arm.FEAT_LRCPC\0", - b"hw.optional.arm.FEAT_LRCPC2\0", - b"hw.optional.arm.FEAT_LRCPC3\0", + c!("hw.optional.armv8_1_atomics"), + c!("hw.optional.arm.FEAT_LSE"), + c!("hw.optional.arm.FEAT_LSE2"), + c!("hw.optional.arm.FEAT_LSE128"), + c!("hw.optional.arm.FEAT_LRCPC"), + c!("hw.optional.arm.FEAT_LRCPC2"), + c!("hw.optional.arm.FEAT_LRCPC3"), ] { - unsafe { - if let Some(res) = sysctlbyname32(name) { - assert_eq!(res, sysctlbyname32_no_libc(name).unwrap()); - } else { - assert_eq!(sysctlbyname32_no_libc(name).unwrap_err(), libc::ENOENT); - } + if let Some(res) = sysctlbyname32(name) { + assert_eq!(res, sysctlbyname32_no_libc(name).unwrap()); + } else { + assert_eq!(sysctlbyname32_no_libc(name).unwrap_err(), libc::ENOENT); } } } diff --git a/src/imp/detect/auxv.rs b/src/imp/detect/auxv.rs index f74e5245..4939be93 100644 --- a/src/imp/detect/auxv.rs +++ b/src/imp/detect/auxv.rs @@ -276,7 +276,7 @@ mod os { // SAFETY: we passed a valid C string to dlsym, and a pointer returned by dlsym // is a valid pointer to the function if it is non-null. let getauxval: GetauxvalTy = unsafe { - let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, "getauxval\0".as_ptr().cast::()); + let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("getauxval").as_ptr()); if ptr.is_null() { return 0; } @@ -385,8 +385,7 @@ mod os { // SAFETY: we passed a valid C string to dlsym, and a pointer returned by dlsym // is a valid pointer to the function if it is non-null. let elf_aux_info: ElfAuxInfoTy = unsafe { - let ptr = - ffi::dlsym(ffi::RTLD_DEFAULT, "elf_aux_info\0".as_ptr().cast::()); + let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("elf_aux_info").as_ptr()); if ptr.is_null() { return 0; } @@ -464,7 +463,7 @@ mod arch { // SAFETY: we've passed a valid C string and a buffer with max length. let len = unsafe { ffi::__system_property_get( - b"ro.arch\0".as_ptr().cast::(), + c!("ro.arch").as_ptr(), arch.as_mut_ptr().cast::(), ) }; @@ -589,7 +588,7 @@ mod tests { unsafe { let mut arch = [1; ffi::PROP_VALUE_MAX as usize]; let len = ffi::__system_property_get( - b"ro.arch\0".as_ptr().cast::(), + c!("ro.arch").as_ptr(), arch.as_mut_ptr().cast::(), ); assert!(len >= 0); @@ -606,7 +605,7 @@ mod tests { #[test] fn test_dlsym_getauxval() { unsafe { - let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, "getauxval\0".as_ptr().cast::()); + let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("getauxval").as_ptr()); if cfg!(any( all( target_os = "linux", @@ -636,8 +635,7 @@ mod tests { #[test] fn test_dlsym_elf_aux_info() { unsafe { - let ptr = - ffi::dlsym(ffi::RTLD_DEFAULT, "elf_aux_info\0".as_ptr().cast::()); + let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("elf_aux_info").as_ptr()); if cfg!(target_os = "freebsd") || option_env!("CI").is_some() { assert!(!ptr.is_null()); } diff --git a/src/imp/detect/c_str.rs b/src/imp/detect/c_str.rs new file mode 100644 index 00000000..25d6658c --- /dev/null +++ b/src/imp/detect/c_str.rs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// This module has two purposes: +// +// - Provide safe abstraction (c! macro) for creating static C strings without runtime checks. +// (c"..." requires Rust 1.77) +// - Provide CStr that compatible with #![no_std]. +// (core::ffi::CStr requires Rust 1.64) + +use crate::utils::ffi::c_char; + +macro_rules! c { + ($s:expr) => {{ + const BYTES: &[u8] = concat!($s, "\0").as_bytes(); + #[cfg(not(portable_atomic_no_track_caller))] // see _is_c_str + const _: () = static_assert!(c_str::_is_c_str(BYTES)); + #[allow(unused_unsafe)] + // SAFETY: we've checked `BYTES` is a valid C string + unsafe { + c_str::CStr::from_bytes_with_nul_unchecked(BYTES) + } + }}; +} + +#[repr(transparent)] +pub(crate) struct CStr([c_char]); + +const _: () = { + static_assert!(core::mem::size_of::() == core::mem::size_of::()); +}; + +impl CStr { + #[inline] + #[must_use] + pub(crate) fn as_ptr(&self) -> *const c_char { + self.0.as_ptr() + } + + /// # Safety + /// + /// The provided slice **must** be nul-terminated and not contain any interior + /// nul bytes. + #[inline] + #[must_use] + pub(crate) unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr { + #[cfg(not(portable_atomic_no_track_caller))] // see _is_c_str + debug_assert!(_is_c_str(bytes)); + // SAFETY: Casting to CStr is safe because *our* CStr is #[repr(transparent)] + // and its internal representation is a [u8] too. (Note that std's CStr + // is not #[repr(transparent)].) + // Dereferencing the obtained pointer is safe because it comes from a + // reference. Making a reference is then safe because its lifetime + // is bound by the lifetime of the given `bytes`. + unsafe { &*(bytes as *const [u8] as *const CStr) } + } + + #[cfg(test)] + #[inline] + #[must_use] + pub(crate) fn to_bytes_with_nul(&self) -> &[u8] { + // SAFETY: Transmuting a slice of `c_char`s to a slice of `u8`s + // is safe on all supported targets. + unsafe { &*(&self.0 as *const [c_char] as *const [u8]) } + } +} + +// Since const_if_match/const_loop was stabilized a few days before track_caller was stabilized, +// we reuse the cfg for track_caller here instead of emitting a cfg for const_if_match/const_loop. +// https://github.com/rust-lang/rust/pull/72437 +#[cfg(not(portable_atomic_no_track_caller))] +#[must_use] +pub(crate) const fn _is_c_str(bytes: &[u8]) -> bool { + // Based on https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/c_str.rs#L434 + // - bytes must be nul-terminated. + // - bytes must not contain any interior nul bytes. + if bytes.is_empty() { + return false; + } + let mut i = bytes.len() - 1; + if bytes[i] != 0 { + return false; + } + // Ending null byte exists, skip to the rest. + while i != 0 { + i -= 1; + if bytes[i] == 0 { + return false; + } + } + true +} + +#[allow( + clippy::alloc_instead_of_core, + clippy::std_instead_of_alloc, + clippy::std_instead_of_core, + clippy::undocumented_unsafe_blocks, + clippy::wildcard_imports +)] +#[cfg(test)] +mod tests { + use super::super::c_str; + + #[test] + fn test_c_macro() { + #[track_caller] + fn t(s: &super::CStr, raw: &[u8]) { + assert_eq!(s.to_bytes_with_nul(), raw); + } + t(c!(""), b"\0"); + t(c!("a"), b"a\0"); + t(c!("abc"), b"abc\0"); + t(c!(concat!("abc", "d")), b"abcd\0"); + } + + #[cfg(not(portable_atomic_no_track_caller))] // see _is_c_str + #[test] + fn test_is_c_str() { + #[track_caller] + fn t(bytes: &[u8]) { + assert_eq!(c_str::_is_c_str(bytes), std::ffi::CStr::from_bytes_with_nul(bytes).is_ok()); + } + t(b"\0"); + t(b"a\0"); + t(b"abc\0"); + t(b""); + t(b"a"); + t(b"abc"); + t(b"\0a"); + t(b"\0a\0"); + t(b"ab\0c\0"); + t(b"\0\0"); + } +} diff --git a/src/imp/detect/common.rs b/src/imp/detect/common.rs index 9b63fdb5..081ce809 100644 --- a/src/imp/detect/common.rs +++ b/src/imp/detect/common.rs @@ -276,6 +276,12 @@ mod ffi_macros { } } +#[cfg(not(any(windows, target_arch = "x86", target_arch = "x86_64")))] +#[allow(dead_code, unused_macros)] +#[macro_use] +#[path = "c_str.rs"] +mod c_str; + #[allow( clippy::alloc_instead_of_core, clippy::std_instead_of_alloc,