Skip to content

Add AArch64 RNDR register backend #512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,29 @@ jobs:
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"
run: cargo build -Z build-std=core --target=${{ matrix.target }}

build-rndr:
name: RNDR Build
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: aarch64-unknown-linux-gnu, aarch64-apple-darwin
- uses: Swatinem/rust-cache@v2
- name: RNDR enabled at compile time (Linux)
env:
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" -C target-feature=+rand
run: cargo build --target=aarch64-unknown-linux-gnu
- name: Runtime RNDR detection without std (Linux)
env:
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr"
run: cargo build --target=aarch64-unknown-linux-gnu
- name: Runtime RNDR detection with std (macOS)
env:
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr"
run: cargo build --target=aarch64-unknown-linux-gnu --features std

build-esp-idf:
name: ESP-IDF Build
runs-on: ubuntu-22.04
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ jobs:
run: cargo clippy -Zbuild-std=core --target x86_64-unknown-netbsd
- name: Fortranix SGX (rdrand.rs)
run: cargo clippy -Zbuild-std=core --target x86_64-fortanix-unknown-sgx
- name: RNDR (rndr.rs)
env:
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr"
run: cargo clippy -Zbuild-std=core --target aarch64-unknown-linux-gnu
- name: Solaris (solaris.rs)
run: cargo clippy -Zbuild-std=core --target x86_64-pc-solaris
- name: SOLID (solid.rs)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ rustc-dep-of-std = ["compiler_builtins", "core"]
[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = [
'cfg(getrandom_backend, values("custom", "rdrand", "linux_getrandom", "wasm_js", "esp_idf"))',
'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "wasm_js", "esp_idf"))',
'cfg(getrandom_browser_test)',
'cfg(getrandom_test_linux_fallback)',
]
Expand Down
41 changes: 24 additions & 17 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ impl Error {
pub const NODE_ES_MODULE: Error = Self::new_internal(14);
/// Calling Windows ProcessPrng failed.
pub const WINDOWS_PROCESS_PRNG: Error = Self::new_internal(15);
/// RNDR register read failed due to a hardware issue.
pub const RNDR_FAILURE: Error = Self::new_internal(16);
/// RNDR register is not supported on this target.
pub const RNDR_NOT_AVAILABLE: Error = Self::new_internal(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 @@ -149,23 +153,26 @@ impl fmt::Display for Error {
}

fn internal_desc(error: Error) -> Option<&'static str> {
match error {
Error::UNSUPPORTED => Some("getrandom: this target is not supported"),
Error::ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"),
Error::UNEXPECTED => Some("unexpected situation"),
Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Security framework failure"),
Error::WINDOWS_RTL_GEN_RANDOM => Some("RtlGenRandom: Windows system function failure"),
Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"),
Error::NO_RDRAND => Some("RDRAND: instruction not supported"),
Error::WEB_CRYPTO => Some("Web Crypto API is unavailable"),
Error::WEB_GET_RANDOM_VALUES => Some("Calling Web API crypto.getRandomValues failed"),
Error::VXWORKS_RAND_SECURE => Some("randSecure: VxWorks RNG module is not initialized"),
Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"),
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"),
_ => None,
}
let desc = match error {
Error::UNSUPPORTED => "getrandom: this target is not supported",
Error::ERRNO_NOT_POSITIVE => "errno: did not return a positive value",
Error::UNEXPECTED => "unexpected situation",
Error::IOS_SEC_RANDOM => "SecRandomCopyBytes: iOS Security framework failure",
Error::WINDOWS_RTL_GEN_RANDOM => "RtlGenRandom: Windows system function failure",
Error::FAILED_RDRAND => "RDRAND: failed multiple times: CPU issue likely",
Error::NO_RDRAND => "RDRAND: instruction not supported",
Error::WEB_CRYPTO => "Web Crypto API is unavailable",
Error::WEB_GET_RANDOM_VALUES => "Calling Web API crypto.getRandomValues failed",
Error::VXWORKS_RAND_SECURE => "randSecure: VxWorks RNG module is not initialized",
Error::NODE_CRYPTO => "Node.js crypto CommonJS module is unavailable",
Error::NODE_RANDOM_FILL_SYNC => "Calling Node.js API crypto.randomFillSync failed",
Error::NODE_ES_MODULE => "Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support",
Error::WINDOWS_PROCESS_PRNG => "ProcessPrng: Windows system function failure",
Error::RNDR_FAILURE => "RNDR: Could not generate a random number",
Error::RNDR_NOT_AVAILABLE => "RNDR: Register not supported",
_ => return None,
};
Some(desc)
}

#[cfg(test)]
Expand Down
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
//! | ----------------- | -------------------- | -------------------- | --------------
//! | `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).
//! | `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction
//! | `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register
//! | `esp_idf` | ESP-IDF | `*‑espidf` | [`esp_fill_random`]. WARNING: can return low quality entropy without proper hardware configuration!
//! | `wasm_js` | Web Browser, Node.js | `wasm*‑*‑unknown` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js (see [WebAssembly support])
//! | `custom` | All targets | `*` | User-provided custom implementation (see [custom backend])
Expand All @@ -51,7 +52,7 @@
//! `RUSTFLAGS` environment variable:
//!
//! ```sh
//! RUSTFLAGS='getrandom_backend="linux_getrandom"' cargo build
//! RUSTFLAGS='--cfg getrandom_backend="linux_getrandom"' cargo build
//! ```
//!
//! Enabling an opt-in backend will replace backend used by default. Doing it for a wrong target
Expand Down Expand Up @@ -215,6 +216,7 @@
//! [`RtlGenRandom`]: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
//! [`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues
//! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide
//! [`RNDR`]: https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/RNDR--Random-Number
//! [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html
//! [`cprng_draw`]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw
//! [`crypto.randomFillSync`]: https://nodejs.org/api/crypto.html#cryptorandomfillsyncbuffer-offset-size
Expand Down Expand Up @@ -292,6 +294,8 @@ cfg_if! {

mod lazy;
#[path = "rdrand.rs"] mod imp;
} else if #[cfg(getrandom_backend = "rndr")] {
#[path = "rndr.rs"] mod imp;
} else if #[cfg(getrandom_backend = "wasm_js")] {
#[cfg(not(all(
any(target_arch = "wasm32", target_arch = "wasm64"),
Expand Down
111 changes: 111 additions & 0 deletions src/rndr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! 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(not(target_arch = "aarch64"))]
compile_error!("the `rndr` backend can be enabled only for AArch64 targets!");

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
}

#[target_feature(enable = "rand")]
unsafe fn rndr_fill(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(())
}

fn is_rndr_available() -> bool {
cfg_if::cfg_if! {
if #[cfg(target_feature = "rand")] {
true
} else if #[cfg(target_os = "linux")] {
/// 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.
///
/// Relying on runtime detection bumps minimum supported Linux kernel version to 4.11.
fn mrs_check() -> 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
}

#[path = "../src/lazy.rs"] mod lazy;
static RNDR_GOOD: lazy::LazyBool = lazy::LazyBool::new();
RNDR_GOOD.unsync_init(mrs_check)
} else if #[cfg(feature = "std")] {
extern crate std;
#[path = "../src/lazy.rs"] mod lazy;
static RNDR_GOOD: lazy::LazyBool = lazy::LazyBool::new();
RNDR_GOOD.unsync_init(|| std::arch::is_aarch64_feature_detected!("rand"))
} else {
compile_error!(
"RNDR `no_std` runtime detection is currently supported only on Linux targets. \
Either enable the `std` crate feature, or `rand` target feature at compile time."
);
}
}
}

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
if is_rndr_available() {
// SAFETY: after this point, we know the `rand` target feature is enabled
unsafe { rndr_fill(dest).ok_or(Error::RNDR_FAILURE) }
} else {
Err(Error::RNDR_NOT_AVAILABLE)
}
}
Loading