Skip to content

Commit

Permalink
aarch64-apple-*: Use a CStr polyfill in feature detection.
Browse files Browse the repository at this point in the history
Make the memory safety clearer, and make it the check happen at
compile time instead of runtime.
  • Loading branch information
briansmith committed May 19, 2024
1 parent 68c7b89 commit dfc9b54
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 18 deletions.
27 changes: 9 additions & 18 deletions src/cpu/arm/darwin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

use super::{AES, ARMCAP_STATIC, NEON, PMULL, SHA256, SHA512};
use crate::polyfill::cstr;

// ```
// $ rustc +1.61.0 --print cfg --target=aarch64-apple-ios | grep -E "neon|aes|sha|pmull"
Expand Down Expand Up @@ -50,28 +51,18 @@ const _AARCH64_APPLE_DARWIN_TARGETS_EXPECTED_FEATURES: () =
assert!(ARMCAP_STATIC == MIN_STATIC_FEATURES);

pub fn detect_features() -> u32 {
// TODO(MSRV 1.64): Use `name: &core::ffi::CStr`.
fn detect_feature(name: &[u8]) -> bool {
fn detect_feature(name: cstr::Ref) -> bool {
use crate::polyfill;
use core::mem;
use libc::{c_char, c_int, c_void};

let nul_terminated = name
.iter()
.position(|&b| b == 0)
.map(|index| (index + 1) == name.len())
.unwrap_or(false);
if !nul_terminated {
return false;
}
let name = polyfill::ptr::from_ref(name).cast::<c_char>();
use libc::{c_int, c_void};

let mut value: c_int = 0;
let mut len = mem::size_of_val(&value);
let value_ptr = polyfill::ptr::from_mut(&mut value).cast::<c_void>();
// SAFETY: `name` is nul-terminated and it doesn't contain interior nul bytes. `value_ptr`
// is a valid pointer to `value` and `len` is the size of `value`.
let rc = unsafe { libc::sysctlbyname(name, value_ptr, &mut len, core::ptr::null_mut(), 0) };
// SAFETY: `value_ptr` is a valid pointer to `value` and `len` is the size of `value`.
let rc = unsafe {
libc::sysctlbyname(name.as_ptr(), value_ptr, &mut len, core::ptr::null_mut(), 0)
};
// All the conditions are separated so we can observe them in code coverage.
if rc != 0 {
return false;
Expand All @@ -88,9 +79,9 @@ pub fn detect_features() -> u32 {

let mut features = 0;

// TODO(MSRV 1.64): Use `: &CStr = CStr::from_bytes_with_nul_unchecked`.
// TODO(MSRV 1.77): Use c"..." literal.
const SHA512_NAME: &[u8] = b"hw.optional.armv8_2_sha512\0";
const SHA512_NAME: cstr::Ref =
cstr::unwrap_const_from_bytes_with_nul(b"hw.optional.armv8_2_sha512\0");
if detect_feature(SHA512_NAME) {
features |= SHA512.mask;
}
Expand Down
2 changes: 2 additions & 0 deletions src/polyfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ mod array_flat_map;
mod array_flatten;
mod array_split_map;

pub mod cstr;

pub mod sliceutil;

#[cfg(feature = "alloc")]
Expand Down
93 changes: 93 additions & 0 deletions src/polyfill/cstr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2024 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

//! Work around lack of `core::ffi::CStr` prior to Rust 1.64, and the lack of
//! `const fn` support for `CStr` in later versions.

#![cfg(all(target_arch = "aarch64", target_vendor = "apple"))]

// TODO(MSRV 1.64): Use `core::ffi::c_char`.
use libc::c_char;

// TODO(MSRV 1.64): Replace with `&core::ffi::CStr`.
pub struct Ref(&'static [u8]);

impl Ref {
#[inline(always)]
pub fn as_ptr(&self) -> *const c_char {
const _SAME_ALIGNMENT: () =
assert!(core::mem::align_of::<u8>() == core::mem::align_of::<c_char>());
const _SAME_SIZE: () =
assert!(core::mem::size_of::<u8>() == core::mem::size_of::<c_char>());

// It is safe to cast a `*const u8` to a `const c_char` as they are the
// same size and alignment.
self.0.as_ptr().cast()
}

// SAFETY: Same as `CStr::from_bytes_with_nul_unchecked`.
const unsafe fn from_bytes_with_nul_unchecked(value: &'static [u8]) -> Self {
Self(value)
}
}

pub const fn unwrap_const_from_bytes_with_nul(value: &'static [u8]) -> Ref {
// XXX: We cannot use `unwrap_const` since `Ref`/`CStr` is not `Copy`.
match const_from_bytes_with_nul(value) {
Some(r) => r,
None => panic!("const_from_bytes_with_nul failed"),
}
}

// TODO(MSRV 1.72): Replace with `CStr::from_bytes_with_nul`.
#[inline(always)]
const fn const_from_bytes_with_nul(value: &'static [u8]) -> Option<Ref> {
const fn const_contains(mut value: &[u8], needle: &u8) -> bool {
while let [head, tail @ ..] = value {
if *head == *needle {
return true;
}
value = tail;
}
false
}

// TODO(MSRV 1.69): Use `core::ffi::CStr::from_bytes_until_nul`
match value {
[before_nul @ .., 0] if !const_contains(before_nul, &0) => {
// SAFETY:
// * `value` is nul-terminated according to the slice pattern.
// * `value` doesn't contain any interior null, by the guard.
// TODO(MSRV 1.64): Use `CStr::from_bytes_with_nul_unchecked`
Some(unsafe { Ref::from_bytes_with_nul_unchecked(value) })
}
_ => None,
}
}

mod tests {
use super::const_from_bytes_with_nul;

// Bad.
const _EMPTY_UNTERMINATED: () = assert!(const_from_bytes_with_nul(b"").is_none());
const _EMPTY_DOUBLE_TERMINATED: () = assert!(const_from_bytes_with_nul(b"\0\0").is_none());
const _DOUBLE_NUL: () = assert!(const_from_bytes_with_nul(b"\0\0").is_none());
const _LEADINGL_NUL: () = assert!(const_from_bytes_with_nul(b"\0a\0").is_none());
const _INTERNAL_NUL_UNTERMINATED: () = assert!(const_from_bytes_with_nul(b"\0a").is_none());

// Good.
const EMPTY_TERMINATED: () = assert!(const_from_bytes_with_nul(b"\0").is_some());
const _NONEMPTY: () = assert!(const_from_bytes_with_nul(b"asdf\0").is_some());
const _1_CHAR: () = assert!(const_from_bytes_with_nul(b"a\0").is_some());
}

0 comments on commit dfc9b54

Please sign in to comment.