Skip to content

Commit 74ea595

Browse files
authored
Add AArch64 RNDR register backend (#512)
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 added as `rdrnd` opt-in backend. Currently, runtime detection of FEAT_RNG relies on the Linux Kernel's MRS emulation, on other targets users have to either enable the `std` crate feature, or the `rand` target feature at compile time.
1 parent c060d65 commit 74ea595

File tree

6 files changed

+168
-19
lines changed

6 files changed

+168
-19
lines changed

.github/workflows/tests.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,29 @@ jobs:
382382
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"
383383
run: cargo build -Z build-std=core --target=${{ matrix.target }}
384384

385+
build-rndr:
386+
name: RNDR Build
387+
runs-on: ubuntu-22.04
388+
steps:
389+
- uses: actions/checkout@v4
390+
- uses: dtolnay/rust-toolchain@master
391+
with:
392+
toolchain: stable
393+
targets: aarch64-unknown-linux-gnu, aarch64-apple-darwin
394+
- uses: Swatinem/rust-cache@v2
395+
- name: RNDR enabled at compile time (Linux)
396+
env:
397+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" -C target-feature=+rand
398+
run: cargo build --target=aarch64-unknown-linux-gnu
399+
- name: Runtime RNDR detection without std (Linux)
400+
env:
401+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr"
402+
run: cargo build --target=aarch64-unknown-linux-gnu
403+
- name: Runtime RNDR detection with std (macOS)
404+
env:
405+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr"
406+
run: cargo build --target=aarch64-unknown-linux-gnu --features std
407+
385408
build-esp-idf:
386409
name: ESP-IDF Build
387410
runs-on: ubuntu-22.04

.github/workflows/workspace.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ jobs:
5959
run: cargo clippy -Zbuild-std=core --target x86_64-unknown-netbsd
6060
- name: Fortranix SGX (rdrand.rs)
6161
run: cargo clippy -Zbuild-std=core --target x86_64-fortanix-unknown-sgx
62+
- name: RNDR (rndr.rs)
63+
env:
64+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr"
65+
run: cargo clippy -Zbuild-std=core --target aarch64-unknown-linux-gnu
6266
- name: Solaris (solaris.rs)
6367
run: cargo clippy -Zbuild-std=core --target x86_64-pc-solaris
6468
- name: SOLID (solid.rs)

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ rustc-dep-of-std = ["compiler_builtins", "core"]
4242
[lints.rust.unexpected_cfgs]
4343
level = "warn"
4444
check-cfg = [
45-
'cfg(getrandom_backend, values("custom", "rdrand", "linux_getrandom", "wasm_js", "esp_idf"))',
45+
'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "wasm_js", "esp_idf"))',
4646
'cfg(getrandom_browser_test)',
4747
'cfg(getrandom_test_linux_fallback)',
4848
]

src/error.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ impl Error {
5252
pub const NODE_ES_MODULE: Error = Self::new_internal(14);
5353
/// Calling Windows ProcessPrng failed.
5454
pub const WINDOWS_PROCESS_PRNG: Error = Self::new_internal(15);
55+
/// RNDR register read failed due to a hardware issue.
56+
pub const RNDR_FAILURE: Error = Self::new_internal(16);
57+
/// RNDR register is not supported on this target.
58+
pub const RNDR_NOT_AVAILABLE: Error = Self::new_internal(17);
5559

5660
/// Codes below this point represent OS Errors (i.e. positive i32 values).
5761
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
@@ -149,23 +153,26 @@ impl fmt::Display for Error {
149153
}
150154

151155
fn internal_desc(error: Error) -> Option<&'static str> {
152-
match error {
153-
Error::UNSUPPORTED => Some("getrandom: this target is not supported"),
154-
Error::ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"),
155-
Error::UNEXPECTED => Some("unexpected situation"),
156-
Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Security framework failure"),
157-
Error::WINDOWS_RTL_GEN_RANDOM => Some("RtlGenRandom: Windows system function failure"),
158-
Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"),
159-
Error::NO_RDRAND => Some("RDRAND: instruction not supported"),
160-
Error::WEB_CRYPTO => Some("Web Crypto API is unavailable"),
161-
Error::WEB_GET_RANDOM_VALUES => Some("Calling Web API crypto.getRandomValues failed"),
162-
Error::VXWORKS_RAND_SECURE => Some("randSecure: VxWorks RNG module is not initialized"),
163-
Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"),
164-
Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"),
165-
Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"),
166-
Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"),
167-
_ => None,
168-
}
156+
let desc = match error {
157+
Error::UNSUPPORTED => "getrandom: this target is not supported",
158+
Error::ERRNO_NOT_POSITIVE => "errno: did not return a positive value",
159+
Error::UNEXPECTED => "unexpected situation",
160+
Error::IOS_SEC_RANDOM => "SecRandomCopyBytes: iOS Security framework failure",
161+
Error::WINDOWS_RTL_GEN_RANDOM => "RtlGenRandom: Windows system function failure",
162+
Error::FAILED_RDRAND => "RDRAND: failed multiple times: CPU issue likely",
163+
Error::NO_RDRAND => "RDRAND: instruction not supported",
164+
Error::WEB_CRYPTO => "Web Crypto API is unavailable",
165+
Error::WEB_GET_RANDOM_VALUES => "Calling Web API crypto.getRandomValues failed",
166+
Error::VXWORKS_RAND_SECURE => "randSecure: VxWorks RNG module is not initialized",
167+
Error::NODE_CRYPTO => "Node.js crypto CommonJS module is unavailable",
168+
Error::NODE_RANDOM_FILL_SYNC => "Calling Node.js API crypto.randomFillSync failed",
169+
Error::NODE_ES_MODULE => "Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support",
170+
Error::WINDOWS_PROCESS_PRNG => "ProcessPrng: Windows system function failure",
171+
Error::RNDR_FAILURE => "RNDR: Could not generate a random number",
172+
Error::RNDR_NOT_AVAILABLE => "RNDR: Register not supported",
173+
_ => return None,
174+
};
175+
Some(desc)
169176
}
170177

171178
#[cfg(test)]

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
//! | ----------------- | -------------------- | -------------------- | --------------
4343
//! | `linux_getrandom` | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call (without `/dev/urandom` fallback). Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow).
4444
//! | `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction
45+
//! | `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register
4546
//! | `esp_idf` | ESP-IDF | `*‑espidf` | [`esp_fill_random`]. WARNING: can return low quality entropy without proper hardware configuration!
4647
//! | `wasm_js` | Web Browser, Node.js | `wasm*‑*‑unknown` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js (see [WebAssembly support])
4748
//! | `custom` | All targets | `*` | User-provided custom implementation (see [custom backend])
@@ -51,7 +52,7 @@
5152
//! `RUSTFLAGS` environment variable:
5253
//!
5354
//! ```sh
54-
//! RUSTFLAGS='getrandom_backend="linux_getrandom"' cargo build
55+
//! RUSTFLAGS='--cfg getrandom_backend="linux_getrandom"' cargo build
5556
//! ```
5657
//!
5758
//! Enabling an opt-in backend will replace backend used by default. Doing it for a wrong target
@@ -215,6 +216,7 @@
215216
//! [`RtlGenRandom`]: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
216217
//! [`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues
217218
//! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide
219+
//! [`RNDR`]: https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/RNDR--Random-Number
218220
//! [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html
219221
//! [`cprng_draw`]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw
220222
//! [`crypto.randomFillSync`]: https://nodejs.org/api/crypto.html#cryptorandomfillsyncbuffer-offset-size
@@ -292,6 +294,8 @@ cfg_if! {
292294

293295
mod lazy;
294296
#[path = "rdrand.rs"] mod imp;
297+
} else if #[cfg(getrandom_backend = "rndr")] {
298+
#[path = "rndr.rs"] mod imp;
295299
} else if #[cfg(getrandom_backend = "wasm_js")] {
296300
#[cfg(not(all(
297301
any(target_arch = "wasm32", target_arch = "wasm64"),

src/rndr.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
#[cfg(not(target_arch = "aarch64"))]
6+
compile_error!("the `rndr` backend can be enabled only for AArch64 targets!");
7+
8+
use crate::{util::slice_as_uninit, Error};
9+
use core::arch::asm;
10+
use core::mem::{size_of, MaybeUninit};
11+
12+
const RETRY_LIMIT: usize = 5;
13+
14+
/// Read a random number from the aarch64 RNDR register
15+
///
16+
/// Callers must ensure that FEAT_RNG is available on the system
17+
/// The function assumes that the RNDR register is available
18+
/// If it fails to read a random number, it will retry up to 5 times
19+
/// After 5 failed reads the function will return `None`
20+
#[target_feature(enable = "rand")]
21+
unsafe fn rndr() -> Option<u64> {
22+
for _ in 0..RETRY_LIMIT {
23+
let mut x: u64;
24+
let mut nzcv: u64;
25+
26+
// AArch64 RNDR register is accessible by s3_3_c2_c4_0
27+
asm!(
28+
"mrs {x}, RNDR",
29+
"mrs {nzcv}, NZCV",
30+
x = out(reg) x,
31+
nzcv = out(reg) nzcv,
32+
);
33+
34+
// If the hardware returns a genuine random number, PSTATE.NZCV is set to 0b0000
35+
if nzcv == 0 {
36+
return Some(x);
37+
}
38+
}
39+
40+
None
41+
}
42+
43+
#[target_feature(enable = "rand")]
44+
unsafe fn rndr_fill(dest: &mut [MaybeUninit<u8>]) -> Option<()> {
45+
let mut chunks = dest.chunks_exact_mut(size_of::<u64>());
46+
for chunk in chunks.by_ref() {
47+
let src = rndr()?.to_ne_bytes();
48+
chunk.copy_from_slice(slice_as_uninit(&src));
49+
}
50+
51+
let tail = chunks.into_remainder();
52+
let n = tail.len();
53+
if n > 0 {
54+
let src = rndr()?.to_ne_bytes();
55+
tail.copy_from_slice(slice_as_uninit(&src[..n]));
56+
}
57+
Some(())
58+
}
59+
60+
fn is_rndr_available() -> bool {
61+
cfg_if::cfg_if! {
62+
if #[cfg(target_feature = "rand")] {
63+
true
64+
} else if #[cfg(target_os = "linux")] {
65+
/// Check whether FEAT_RNG is available on the system
66+
///
67+
/// Requires the caller either be running in EL1 or be on a system supporting MRS
68+
/// emulation. Due to the above, the implementation is currently restricted to Linux.
69+
///
70+
/// Relying on runtime detection bumps minimum supported Linux kernel version to 4.11.
71+
fn mrs_check() -> bool {
72+
let mut id_aa64isar0: u64;
73+
74+
// If FEAT_RNG is implemented, ID_AA64ISAR0_EL1.RNDR (bits 60-63) are 0b0001
75+
// This is okay to do from EL0 in Linux because Linux will emulate MRS as per
76+
// https://docs.kernel.org/arch/arm64/cpu-feature-registers.html
77+
unsafe {
78+
asm!(
79+
"mrs {id}, ID_AA64ISAR0_EL1",
80+
id = out(reg) id_aa64isar0,
81+
);
82+
}
83+
84+
(id_aa64isar0 >> 60) & 0xf >= 1
85+
}
86+
87+
#[path = "../src/lazy.rs"] mod lazy;
88+
static RNDR_GOOD: lazy::LazyBool = lazy::LazyBool::new();
89+
RNDR_GOOD.unsync_init(mrs_check)
90+
} else if #[cfg(feature = "std")] {
91+
extern crate std;
92+
#[path = "../src/lazy.rs"] mod lazy;
93+
static RNDR_GOOD: lazy::LazyBool = lazy::LazyBool::new();
94+
RNDR_GOOD.unsync_init(|| std::arch::is_aarch64_feature_detected!("rand"))
95+
} else {
96+
compile_error!(
97+
"RNDR `no_std` runtime detection is currently supported only on Linux targets. \
98+
Either enable the `std` crate feature, or `rand` target feature at compile time."
99+
);
100+
}
101+
}
102+
}
103+
104+
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
105+
if is_rndr_available() {
106+
// SAFETY: after this point, we know the `rand` target feature is enabled
107+
unsafe { rndr_fill(dest).ok_or(Error::RNDR_FAILURE) }
108+
} else {
109+
Err(Error::RNDR_NOT_AVAILABLE)
110+
}
111+
}

0 commit comments

Comments
 (0)