Skip to content

Commit

Permalink
detect: Add safe abstraction for C strings
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Jan 12, 2025
1 parent 65ab1ba commit ec3f473
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 72 deletions.
26 changes: 10 additions & 16 deletions src/imp/detect/aarch64_aa64reg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ mod imp {
// core::ffi::c_* (except c_void) requires Rust 1.64, libc requires Rust 1.63
#[allow(non_camel_case_types)]
pub(super) mod ffi {
pub(crate) use crate::utils::ffi::{c_char, c_int, c_size_t, c_void};
pub(crate) use crate::utils::ffi::{c_char, c_int, c_size_t, c_void, CStr};

sys_struct!({
// Defined in machine/armreg.h.
Expand Down Expand Up @@ -173,24 +173,21 @@ mod imp {
});
}

pub(super) unsafe fn sysctl_cpu_id(name: &[u8]) -> Option<AA64Reg> {
pub(super) fn sysctl_cpu_id(name: &ffi::CStr) -> Option<AA64Reg> {
const OUT_LEN: ffi::c_size_t =
mem::size_of::<ffi::aarch64_sysctl_cpu_id>() 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::<ffi::c_char>(),
name.as_ptr(),
(&mut buf as *mut ffi::aarch64_sysctl_cpu_id).cast::<ffi::c_void>(),
&mut out_len,
ptr::null_mut(),
Expand All @@ -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 },
}
Expand Down Expand Up @@ -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<AA64Reg, c_int> {
fn sysctl_cpu_id_no_libc(name: &[&[u8]]) -> Result<AA64Reg, c_int> {
// https://github.com/golang/go/blob/4badad8d477ffd7a6b762c35bc69aed82faface7/src/syscall/asm_netbsd_arm64.s
#[inline]
unsafe fn sysctl(
Expand Down Expand Up @@ -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()
);
}
}
81 changes: 33 additions & 48 deletions src/imp/detect/aarch64_apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use core::{mem, ptr};
// core::ffi::c_* (except c_void) requires Rust 1.64, libc requires Rust 1.63
#[allow(non_camel_case_types)]
mod ffi {
pub(crate) use crate::utils::ffi::{c_char, c_int, c_size_t, c_void};
pub(crate) use crate::utils::ffi::{c_char, c_int, c_size_t, c_void, CStr};

sys_fn!({
extern "C" {
Expand All @@ -42,21 +42,18 @@ mod ffi {
});
}

unsafe fn sysctlbyname32(name: &[u8]) -> Option<u32> {
fn sysctlbyname32(name: &ffi::CStr) -> Option<u32> {
const OUT_LEN: ffi::c_size_t = mem::size_of::<u32>() 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::<ffi::c_char>(),
name.as_ptr(),
(&mut out as *mut u32).cast::<ffi::c_void>(),
&mut out_len,
ptr::null_mut(),
Expand All @@ -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);
}
}
Expand All @@ -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")]
Expand All @@ -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<u32, c_int> {
fn sysctlbyname32_no_libc(name: &CStr) -> Result<u32, c_int> {
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/syscalls.master#L298
#[inline]
unsafe fn sysctl(
Expand Down Expand Up @@ -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: &CStr,
old_p: *mut c_void,
old_len_p: *mut c_size_t,
new_p: *mut c_void,
Expand All @@ -207,7 +197,7 @@ mod tests {
real_oid.as_mut_ptr().cast::<c_void>(),
&mut oid_len,
name.as_ptr().cast::<c_void>() as *mut c_void,
name.len() - 1,
name.to_bytes_with_nul().len() - 1,
)?
};
oid_len /= mem::size_of::<c_int>();
Expand All @@ -219,9 +209,6 @@ mod tests {

const OUT_LEN: ffi::c_size_t = mem::size_of::<u32>() 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:
Expand All @@ -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);
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions src/imp/detect/auxv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<ffi::c_char>());
let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("getauxval").as_ptr());
if ptr.is_null() {
return 0;
}
Expand Down Expand Up @@ -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::<ffi::c_char>());
let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("elf_aux_info").as_ptr());
if ptr.is_null() {
return 0;
}
Expand Down Expand Up @@ -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::<ffi::c_char>(),
c!("ro.arch").as_ptr(),
arch.as_mut_ptr().cast::<ffi::c_char>(),
)
};
Expand Down Expand Up @@ -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::<ffi::c_char>(),
c!("ro.arch").as_ptr(),
arch.as_mut_ptr().cast::<ffi::c_char>(),
);
assert!(len >= 0);
Expand All @@ -606,7 +605,7 @@ mod tests {
#[test]
fn test_dlsym_getauxval() {
unsafe {
let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, "getauxval\0".as_ptr().cast::<ffi::c_char>());
let ptr = ffi::dlsym(ffi::RTLD_DEFAULT, c!("getauxval").as_ptr());
if cfg!(any(
all(
target_os = "linux",
Expand Down Expand Up @@ -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::<ffi::c_char>());
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());
}
Expand Down
Loading

0 comments on commit ec3f473

Please sign in to comment.