diff --git a/Cargo.toml b/Cargo.toml index 032ae083..208e3115 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,8 @@ rustc-dep-of-std = [ ] # Unstable/test-only feature to run wasm-bindgen tests in a browser test-in-browser = [] +# Feature to enable the RNDR register-based implementation on aarch64 linux +rndr = [] [[test]] name = "custom" diff --git a/src/error.rs b/src/error.rs index 5eff99eb..805b1418 100644 --- a/src/error.rs +++ b/src/error.rs @@ -58,6 +58,10 @@ impl Error { pub const NODE_ES_MODULE: Error = internal_error(14); /// Calling Windows ProcessPrng failed. pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15); + /// RNDR register read failed due to a hardware issue. + pub const FAILED_RNDR: Error = internal_error(16); + /// RNDR register is not supported on this target. + pub const NO_RNDR: Error = internal_error(17); /// Codes below this point represent OS Errors (i.e. positive i32 values). /// Codes at or above this point, but below [`Error::CUSTOM_START`] are @@ -78,7 +82,7 @@ impl Error { /// /// [1]: https://doc.rust-lang.org/std/io/struct.Error.html#method.from_raw_os_error #[allow(dead_code)] - pub(super) fn from_os_error(code: u32) -> Self { + pub fn from_os_error(code: u32) -> Self { match NonZeroU32::new(code) { Some(code) if code.get() < Self::INTERNAL_START => Self(code), _ => Self::UNEXPECTED, @@ -175,6 +179,8 @@ fn internal_desc(error: Error) -> Option<&'static str> { Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"), Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"), Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"), + Error::NO_RNDR => Some("RNDR: Register not supported"), + Error::FAILED_RNDR => Some("RNDR: Could not generate a random number"), _ => None, } } diff --git a/src/lib.rs b/src/lib.rs index f5e13c36..4db9b78f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -334,6 +334,10 @@ cfg_if! { } else if #[cfg(all(feature = "rdrand", any(target_arch = "x86_64", target_arch = "x86")))] { #[path = "rdrand.rs"] mod imp; + } else if #[cfg(all( + target_arch = "aarch64", feature = "rndr" + ))] { + #[path = "rndr.rs"] mod imp; } else if #[cfg(all(feature = "js", any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))] { diff --git a/src/rndr.rs b/src/rndr.rs new file mode 100644 index 00000000..28a0f8a2 --- /dev/null +++ b/src/rndr.rs @@ -0,0 +1,62 @@ +//! RNDR register backend for aarch64 targets +// Arm Architecture Reference Manual for A-profile architecture +// ARM DDI 0487K.a, ID032224, D23.2.147 RNDR, Random Number + +#[cfg(any(not(target_feature = "rand"), not(target_arch = "aarch64")))] +compile_error!("The RNDR backend requires the `rand` target feature to be enabled at compile time"); + +use crate::{util::slice_as_uninit, Error}; +use core::arch::asm; +use core::mem::{size_of, MaybeUninit}; + +const RETRY_LIMIT: usize = 5; + +// Read a random number from the aarch64 rndr register +// +// Callers must ensure that FEAT_RNG is available on the system +// The function assumes that the RNDR register is available +// If it fails to read a random number, it will retry up to 5 times +// After 5 failed reads the function will return None +#[target_feature(enable = "rand")] +unsafe fn rndr() -> Option { + for _ in 0..RETRY_LIMIT { + let mut x: u64; + let mut nzcv: u64; + + // AArch64 RNDR register is accessible by s3_3_c2_c4_0 + asm!( + "mrs {x}, RNDR", + "mrs {nzcv}, NZCV", + x = out(reg) x, + nzcv = out(reg) nzcv, + ); + + // If the hardware returns a genuine random number, PSTATE.NZCV is set to 0b0000 + if nzcv == 0 { + return Some(x); + } + } + + None +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + unsafe { rndr_exact(dest).ok_or(Error::FAILED_RNDR) } +} + +#[target_feature(enable = "rand")] +unsafe fn rndr_exact(dest: &mut [MaybeUninit]) -> Option<()> { + let mut chunks = dest.chunks_exact_mut(size_of::()); + for chunk in chunks.by_ref() { + let src = rndr()?.to_ne_bytes(); + chunk.copy_from_slice(slice_as_uninit(&src)); + } + + let tail = chunks.into_remainder(); + let n = tail.len(); + if n > 0 { + let src = rndr()?.to_ne_bytes(); + tail.copy_from_slice(slice_as_uninit(&src[..n])); + } + Some(()) +} diff --git a/src/rndr_with_fallback.rs b/src/rndr_with_fallback.rs new file mode 100644 index 00000000..ee9e21ba --- /dev/null +++ b/src/rndr_with_fallback.rs @@ -0,0 +1,49 @@ +//! Linux-only safe RNDR register backend for aarch64 targets with fallback + +#[cfg(any( + not(any(target_os = "linux", target_os = "android")), + not(target_feature = "rand"), + not(target_arch = "aarch64") +))] +compile_error!( + "The rndr_with_fallback backend requires the `rand` target feature to be enabled + at compile time and can only be built for Linux or Android." +); + +use crate::{lazy::LazyBool, linux_android_with_fallback, rndr, Error}; +use core::arch::asm; +use core::mem::MaybeUninit; + +// Check whether FEAT_RNG is available on the system +// +// Requires the caller either be running in EL1 or be on a system supporting MRS emulation. +// Due to the above, the implementation is currently restricted to Linux. +fn is_rndr_available() -> bool { + let mut id_aa64isar0: u64; + + // If FEAT_RNG is implemented, ID_AA64ISAR0_EL1.RNDR (bits 60-63) are 0b0001 + // This is okay to do from EL0 in Linux because Linux will emulate MRS as per + // https://docs.kernel.org/arch/arm64/cpu-feature-registers.html + unsafe { + asm!( + "mrs {id}, ID_AA64ISAR0_EL1", + id = out(reg) id_aa64isar0, + ); + } + + (id_aa64isar0 >> 60) & 0xf >= 1 +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + static RNDR_AVAILABLE: LazyBool = LazyBool::new(); + if !RNDR_AVAILABLE.unsync_init(is_rndr_available) { + return Err(Error::NO_RNDR); + } + + // We've already checked that RNDR is available + if rndr::getrandom_inner(dest).is_ok() { + Ok(()) + } else { + linux_android_with_fallback::getrandom_inner(dest) + } +} diff --git a/tests/rndr.rs b/tests/rndr.rs new file mode 100644 index 00000000..a12194c9 --- /dev/null +++ b/tests/rndr.rs @@ -0,0 +1,13 @@ +#![cfg(all(target_os = "linux", target_arch = "aarch64", feature = "rndr"))] + +use getrandom::Error; +#[path = "../src/rndr.rs"] +mod rndr; +#[path = "../src/util.rs"] +mod util; + +fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> { + rndr::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?; + Ok(()) +} +mod common; diff --git a/tests/rndr_with_fallback.rs b/tests/rndr_with_fallback.rs new file mode 100644 index 00000000..5af09758 --- /dev/null +++ b/tests/rndr_with_fallback.rs @@ -0,0 +1,32 @@ +#![cfg(all( + target_os = "linux", + target_arch = "aarch64", + feature = "rndr", + target_feature = "rand" +))] + +use getrandom::Error; +#[macro_use] +extern crate cfg_if; +#[path = "../src/lazy.rs"] +mod lazy; +#[path = "../src/linux_android.rs"] +mod linux_android; +#[path = "../src/linux_android_with_fallback.rs"] +mod linux_android_with_fallback; +#[path = "../src/rndr.rs"] +mod rndr; +#[path = "../src/rndr_with_fallback.rs"] +mod rndr_with_fallback; +#[path = "../src/use_file.rs"] +mod use_file; +#[path = "../src/util.rs"] +mod util; +#[path = "../src/util_libc.rs"] +mod util_libc; + +fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> { + rndr_with_fallback::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?; + Ok(()) +} +mod common;