From e9d0c430fad3b0190d3ee9b4b74f94115dcdca36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 11 Oct 2024 17:52:56 +0300 Subject: [PATCH 1/7] Add RNDR backend --- .github/workflows/tests.yml | 16 ++++++ Cargo.toml | 2 +- src/error.rs | 41 ++++++++------ src/lib.rs | 4 ++ src/rndr.rs | 104 ++++++++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 src/rndr.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 469925b7..c73fec6e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -382,6 +382,22 @@ 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: + targets: aarch64-unknown-linux-gnu + - uses: Swatinem/rust-cache@v2 + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" --target-feature=+rand + run: cargo build --target=aarch64-unknown-linux-gnu + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" + run: cargo build --target=aarch64-unknown-linux-gnu + build-esp-idf: name: ESP-IDF Build runs-on: ubuntu-22.04 diff --git a/Cargo.toml b/Cargo.toml index 83e22501..88ce5637 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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)', ] diff --git a/src/error.rs b/src/error.rs index d0299267..b27ef26b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 @@ -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)] diff --git a/src/lib.rs b/src/lib.rs index 5cc40941..02c8c0cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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]) @@ -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 @@ -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"), diff --git a/src/rndr.rs b/src/rndr.rs new file mode 100644 index 00000000..f5fce011 --- /dev/null +++ b/src/rndr.rs @@ -0,0 +1,104 @@ +//! 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 { + 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]) -> Option<()> { + let mut chunks = dest.chunks_exact_mut(size_of::()); + 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. + 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 { + compile_error!( + "RNDR runtime detection is currently supported only on Linux targets. \ + You can enable the `rand` target feature at compile time." + ); + } + } +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> 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) + } +} From 27d3ef3dd52a9f52921eac51809ef54962a70325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 11 Oct 2024 17:53:57 +0300 Subject: [PATCH 2/7] tweak crate docs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 02c8c0cc..e2084d96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,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 From be07524284d361eb9d3f947aff0046a344bcfe0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 11 Oct 2024 18:00:30 +0300 Subject: [PATCH 3/7] Add note about Linux kernel version --- src/rndr.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rndr.rs b/src/rndr.rs index f5fce011..ecc0f732 100644 --- a/src/rndr.rs +++ b/src/rndr.rs @@ -66,6 +66,8 @@ fn is_rndr_available() -> bool { /// /// 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; From e56df86bef00c9ff5dc9201181ffc08dc5abd09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 11 Oct 2024 18:05:51 +0300 Subject: [PATCH 4/7] Tweak CI config --- .github/workflows/tests.yml | 1 + .github/workflows/workspace.yml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c73fec6e..6988155b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -389,6 +389,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: + toolchain: stable targets: aarch64-unknown-linux-gnu - uses: Swatinem/rust-cache@v2 - env: diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 13dba99a..80e4b6ef 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -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) From 851fc03bc2e3a3a6a4de51689f83371d969eabe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 11 Oct 2024 18:10:29 +0300 Subject: [PATCH 5/7] Fix CI config --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6988155b..6a29f369 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -393,7 +393,7 @@ jobs: targets: aarch64-unknown-linux-gnu - uses: Swatinem/rust-cache@v2 - env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" --target-feature=+rand + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" -C target-feature=+rand run: cargo build --target=aarch64-unknown-linux-gnu - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" From 5bea716c6f747b04ea65eb74f1f7c12313558574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 11 Oct 2024 18:41:59 +0300 Subject: [PATCH 6/7] Use `is_aarch64_feature_detected` with enabled `std` --- .github/workflows/tests.yml | 12 +++++++++--- src/rndr.rs | 6 ++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6a29f369..10027577 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -390,14 +390,20 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: stable - targets: aarch64-unknown-linux-gnu + targets: aarch64-unknown-linux-gnu, aarch64-apple-darwin - uses: Swatinem/rust-cache@v2 - - env: + - 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 - - env: + - 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 diff --git a/src/rndr.rs b/src/rndr.rs index ecc0f732..51c92544 100644 --- a/src/rndr.rs +++ b/src/rndr.rs @@ -61,6 +61,8 @@ fn is_rndr_available() -> bool { cfg_if::cfg_if! { if #[cfg(target_feature = "rand")] { true + } else if #[cfg(feature = "std")] { + std::arch::is_aarch64_feature_detected!("rand") } else if #[cfg(target_os = "linux")] { /// Check whether FEAT_RNG is available on the system /// @@ -89,8 +91,8 @@ fn is_rndr_available() -> bool { RNDR_GOOD.unsync_init(mrs_check) } else { compile_error!( - "RNDR runtime detection is currently supported only on Linux targets. \ - You can enable the `rand` target feature at compile time." + "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." ); } } From 44bc66d33c23a214f1c0df945c35cf2206fe3d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 11 Oct 2024 18:46:33 +0300 Subject: [PATCH 7/7] Fix detection code --- src/rndr.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rndr.rs b/src/rndr.rs index 51c92544..f27d9d28 100644 --- a/src/rndr.rs +++ b/src/rndr.rs @@ -61,8 +61,6 @@ fn is_rndr_available() -> bool { cfg_if::cfg_if! { if #[cfg(target_feature = "rand")] { true - } else if #[cfg(feature = "std")] { - std::arch::is_aarch64_feature_detected!("rand") } else if #[cfg(target_os = "linux")] { /// Check whether FEAT_RNG is available on the system /// @@ -89,6 +87,11 @@ fn is_rndr_available() -> bool { #[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. \