Skip to content
Closed
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
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
8 changes: 7 additions & 1 deletion 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 All @@ -78,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,
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
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))] {
Expand Down
62 changes: 62 additions & 0 deletions src/rndr.rs
Original file line number Diff line number Diff line change
@@ -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<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 fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
unsafe { 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(())
}
49 changes: 49 additions & 0 deletions src/rndr_with_fallback.rs
Original file line number Diff line number Diff line change
@@ -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<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 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.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;
32 changes: 32 additions & 0 deletions tests/rndr_with_fallback.rs
Original file line number Diff line number Diff line change
@@ -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;