Skip to content

Commit badb406

Browse files
committed
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. 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.
1 parent 5edb045 commit badb406

6 files changed

Lines changed: 132 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ rustc-dep-of-std = [
5252
]
5353
# Unstable/test-only feature to run wasm-bindgen tests in a browser
5454
test-in-browser = []
55+
# Feature to enable the RNDR register-based implementation on aarch64 linux
56+
rndr = []
5557

5658
[[test]]
5759
name = "custom"

src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ impl Error {
5858
pub const NODE_ES_MODULE: Error = internal_error(14);
5959
/// Calling Windows ProcessPrng failed.
6060
pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15);
61+
/// RNDR register read failed due to a hardware issue.
62+
pub const FAILED_RNDR: Error = internal_error(16);
63+
/// RNDR register is not supported on this target.
64+
pub const NO_RNDR: Error = internal_error(17);
6165

6266
/// Codes below this point represent OS Errors (i.e. positive i32 values).
6367
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
@@ -175,6 +179,8 @@ fn internal_desc(error: Error) -> Option<&'static str> {
175179
Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"),
176180
Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"),
177181
Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"),
182+
Error::NO_RNDR => Some("RNDR: Register not supported"),
183+
Error::FAILED_RNDR => Some("RNDR: Could not generate a random number"),
178184
_ => None,
179185
}
180186
}

src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,18 @@ cfg_if! {
257257
))] {
258258
mod util_libc;
259259
#[path = "getrandom.rs"] mod imp;
260+
} else if #[cfg(all(
261+
not(feature = "linux_disable_fallback"),
262+
any(target_os = "linux", target_os = "android"),
263+
target_arch = "aarch64",
264+
feature = "rndr"
265+
))] {
266+
mod util_libc;
267+
mod use_file;
268+
mod linux_android;
269+
mod linux_android_with_fallback;
270+
mod rndr;
271+
#[path = "rndr_with_fallback.rs"] mod imp;
260272
} else if #[cfg(all(
261273
not(feature = "linux_disable_fallback"),
262274
any(

src/rndr.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//! RNDR register backend for aarch64 targets
2+
// Arm Architecture Reference Manual for A-profile architecture
3+
// ARM DDI 0487K.a, ID032224, D23.2.147 RNDR, Random Number
4+
5+
use crate::{util::slice_as_uninit, Error};
6+
use core::arch::asm;
7+
use core::mem::{size_of, MaybeUninit};
8+
9+
const RETRY_LIMIT: usize = 5;
10+
11+
// Read a random number from the aarch64 rndr register
12+
//
13+
// Callers must ensure that FEAT_RNG is available on the system
14+
// The function assumes that the RNDR register is available
15+
// If it fails to read a random number, it will retry up to 5 times
16+
// After 5 failed reads the function will return None
17+
#[target_feature(enable = "rand")]
18+
unsafe fn rndr() -> Option<u64> {
19+
for _ in 0..RETRY_LIMIT {
20+
let mut x: u64;
21+
let mut nzcv: u64;
22+
23+
// AArch64 RNDR register is accessible by s3_3_c2_c4_0
24+
asm!(
25+
"mrs {x}, RNDR",
26+
"mrs {nzcv}, NZCV",
27+
x = out(reg) x,
28+
nzcv = out(reg) nzcv,
29+
);
30+
31+
// If the hardware returns a genuine random number, PSTATE.NZCV is set to 0b0000
32+
if nzcv == 0 {
33+
return Some(x);
34+
}
35+
}
36+
37+
None
38+
}
39+
40+
pub unsafe fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
41+
rndr_exact(dest).ok_or(Error::FAILED_RNDR)
42+
}
43+
44+
#[target_feature(enable = "rand")]
45+
unsafe fn rndr_exact(dest: &mut [MaybeUninit<u8>]) -> Option<()> {
46+
let mut chunks = dest.chunks_exact_mut(size_of::<u64>());
47+
for chunk in chunks.by_ref() {
48+
let src = rndr()?.to_ne_bytes();
49+
chunk.copy_from_slice(slice_as_uninit(&src));
50+
}
51+
52+
let tail = chunks.into_remainder();
53+
let n = tail.len();
54+
if n > 0 {
55+
let src = rndr()?.to_ne_bytes();
56+
tail.copy_from_slice(slice_as_uninit(&src[..n]));
57+
}
58+
Some(())
59+
}

src/rndr_with_fallback.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//! Linux-only safe RNDR register backend for aarch64 targets with fallback
2+
3+
use crate::{lazy::LazyBool, linux_android_with_fallback, rndr, Error};
4+
use core::arch::asm;
5+
use core::mem::MaybeUninit;
6+
7+
#[cfg(any(target_os = "linux", target_os = "android"))]
8+
// Check whether FEAT_RNG is available on the system
9+
//
10+
// Requires the caller either be running in EL1 or be on a system supporting MRS emulation.
11+
// Due to the above, the implementation is currently restricted to Linux.
12+
fn is_rndr_available() -> bool {
13+
let mut id_aa64isar0: u64;
14+
15+
// If FEAT_RNG is implemented, ID_AA64ISAR0_EL1.RNDR (bits 60-63) are 0b0001
16+
// This is okay to do from EL0 in Linux because Linux will emulate MRS as per
17+
// https://docs.kernel.org/arch/arm64/cpu-feature-registers.html
18+
unsafe {
19+
asm!(
20+
"mrs {id}, ID_AA64ISAR0_EL1",
21+
id = out(reg) id_aa64isar0,
22+
);
23+
}
24+
25+
(id_aa64isar0 >> 60) & 0xf >= 1
26+
}
27+
28+
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
29+
static RNDR_AVAILABLE: LazyBool = LazyBool::new();
30+
if !RNDR_AVAILABLE.unsync_init(is_rndr_available) {
31+
return Err(Error::NO_RNDR);
32+
}
33+
34+
// We've already checked that RNDR is available
35+
if unsafe { rndr::getrandom_inner(dest) }.is_ok() {
36+
Ok(())
37+
} else {
38+
linux_android_with_fallback::getrandom_inner(dest)
39+
}
40+
}

tests/rndr.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![cfg(all(target_os = "linux", target_arch = "aarch64", feature = "rndr"))]
2+
3+
use getrandom::Error;
4+
#[path = "../src/rndr_with_fallback.rs"]
5+
mod rndr_with_fallback;
6+
#[path = "../src/util.rs"]
7+
mod util;
8+
9+
fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> {
10+
rndr_with_fallback::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?;
11+
Ok(())
12+
}
13+
mod common;

0 commit comments

Comments
 (0)