From 6783b8754e2bf5ca416d1161cf91ab18c6f3eade Mon Sep 17 00:00:00 2001 From: Kajetan Puchalski Date: Mon, 29 Jul 2024 15:15:15 +0100 Subject: [PATCH 1/2] rndr: Add support for aarch64 RNDR register backend AArch64 platforms from version Armv8.4 onwards may implement FEAT_RNG. FEAT_RNG introduces the RNDR (and RNDRRS) register, reading from which returns a random number. Add support for using the RNDR register as a backend for getrandom. The implementation is hidden behind a new "rndr" crate feature. For the backend to work, users must ensure that the target platform supports FEAT_RNG because it is not possible to reliably detect the feature at runtime across platforms. --- Cargo.toml | 2 ++ src/error.rs | 6 +++++ src/lib.rs | 4 ++++ src/rndr.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ tests/rndr.rs | 13 +++++++++++ 5 files changed, 87 insertions(+) create mode 100644 src/rndr.rs create mode 100644 tests/rndr.rs 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..72c640ff 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 @@ -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/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; From f3c52440386ac0296f4525c53ea2b4b7ac24155d Mon Sep 17 00:00:00 2001 From: Kajetan Puchalski Date: Mon, 29 Jul 2024 15:16:57 +0100 Subject: [PATCH 2/2] rndr_with_fallback: Add a RNDR backend with a Linux fallback Currently, detecting whether FEAT_RNG is available without std relies on the Linux Kernel's MRS emulation. This commit adds a safe rndr_with_fallback backend for Linux systems. With this backend, getrandom will use the RNDR register on Linux systems where it is available and automatically fallback onto using Linux's getrandom syscall on systems where it is not. This implementation allows the crate to be build for Linux with this feature in advance and then run without having to know whether FEAT_RNG is implemented or not. For the time being, this backend is not used by default on any platform configuration. The intention is for it to be usable as an opt-in when an opt-in mechanism is available in the crate. --- src/error.rs | 2 +- src/rndr_with_fallback.rs | 49 +++++++++++++++++++++++++++++++++++++ tests/rndr_with_fallback.rs | 32 ++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/rndr_with_fallback.rs create mode 100644 tests/rndr_with_fallback.rs diff --git a/src/error.rs b/src/error.rs index 72c640ff..805b1418 100644 --- a/src/error.rs +++ b/src/error.rs @@ -82,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, 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_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;