Skip to content

Commit

Permalink
rndr: Add support for aarch64 RNDR register backend
Browse files Browse the repository at this point in the history
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.

Currently, detecting whether FEAT_RNG is available without std relies
on the Linux Kernel's MRS emulation. For that reason the `rndr`
implementation is marked as unsafe, because we cannot always detect
whether the register is available or not.

This commit also 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.
  • Loading branch information
mrkajetanp committed Jul 25, 2024
1 parent 5edb045 commit badb406
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,18 @@ cfg_if! {
))] {
mod util_libc;
#[path = "getrandom.rs"] mod imp;
} else if #[cfg(all(
not(feature = "linux_disable_fallback"),
any(target_os = "linux", target_os = "android"),
target_arch = "aarch64",
feature = "rndr"
))] {
mod util_libc;
mod use_file;
mod linux_android;
mod linux_android_with_fallback;
mod rndr;
#[path = "rndr_with_fallback.rs"] mod imp;
} else if #[cfg(all(
not(feature = "linux_disable_fallback"),
any(
Expand Down
59 changes: 59 additions & 0 deletions src/rndr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! 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

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<u64> {
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 unsafe fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
rndr_exact(dest).ok_or(Error::FAILED_RNDR)
}

#[target_feature(enable = "rand")]
unsafe fn rndr_exact(dest: &mut [MaybeUninit<u8>]) -> Option<()> {
let mut chunks = dest.chunks_exact_mut(size_of::<u64>());
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(())
}
40 changes: 40 additions & 0 deletions src/rndr_with_fallback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Linux-only safe RNDR register backend for aarch64 targets with fallback

use crate::{lazy::LazyBool, linux_android_with_fallback, rndr, Error};
use core::arch::asm;
use core::mem::MaybeUninit;

#[cfg(any(target_os = "linux", target_os = "android"))]
// 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<u8>]) -> 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 unsafe { rndr::getrandom_inner(dest) }.is_ok() {
Ok(())
} else {
linux_android_with_fallback::getrandom_inner(dest)
}
}
13 changes: 13 additions & 0 deletions tests/rndr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#![cfg(all(target_os = "linux", target_arch = "aarch64", feature = "rndr"))]

use getrandom::Error;
#[path = "../src/rndr_with_fallback.rs"]
mod rndr_with_fallback;
#[path = "../src/util.rs"]
mod util;

fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> {
rndr_with_fallback::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?;
Ok(())
}
mod common;

0 comments on commit badb406

Please sign in to comment.