From e7eb146b24535eb88c51b194274f3fef471e0c14 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: Mon, 3 Feb 2025 13:32:46 +0300 Subject: [PATCH] Add `efi_rng` opt-in backend --- .github/workflows/build.yml | 20 ++++++ Cargo.toml | 6 +- README.md | 2 + src/backends.rs | 3 + src/backends/efi_rng.rs | 122 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 6 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/backends/efi_rng.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 204da807..d84081ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -166,6 +166,26 @@ jobs: - name: Build run: cargo build --target ${{ matrix.target.target }} ${{ matrix.feature.feature }} -Zbuild-std=${{ matrix.feature.build-std }} + efi-rng: + name: UEFI RNG Protocol + runs-on: ubuntu-24.04 + strategy: + matrix: + target: [ + aarch64-unknown-uefi, + x86_64-unknown-uefi, + i686-unknown-uefi, + ] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly # Required to build libstd + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="efi_rng" + run: cargo build -Z build-std=std --target=${{ matrix.target }} --features std + rdrand-uefi: name: RDRAND UEFI runs-on: ubuntu-24.04 diff --git a/Cargo.toml b/Cargo.toml index ee22ce15..92f79b12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,10 @@ libc = { version = "0.2.154", default-features = false } [target.'cfg(any(target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"))'.dependencies] libc = { version = "0.2.154", default-features = false } +# efi_rng +[target.'cfg(all(target_os = "uefi", getrandom_backend = "efi_rng"))'.dependencies] +r-efi = { version = "5.1", default-features = false } + # getentropy [target.'cfg(any(target_os = "macos", target_os = "openbsd", target_os = "vita", target_os = "emscripten"))'.dependencies] libc = { version = "0.2.154", default-features = false } @@ -81,7 +85,7 @@ wasm-bindgen-test = "0.3" [lints.rust.unexpected_cfgs] level = "warn" check-cfg = [ - 'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "wasm_js"))', + 'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "wasm_js"))', 'cfg(getrandom_msan)', 'cfg(getrandom_test_linux_fallback)', 'cfg(getrandom_test_netbsd_fallback)', diff --git a/README.md b/README.md index 79082409..3b58d675 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ of randomness based on their specific needs: | `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction | `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register | `wasm_js` | Web Browser, Node.js | `wasm32‑unknown‑unknown`, `wasm32v1-none` | [`Crypto.getRandomValues`]. Requires feature `wasm_js` ([see below](#webassembly-support)). +| `efi_rng` | UEFI | `*-unknown‑uefi` | [`EFI_RNG_PROTOCOL`] with `EFI_RNG_ALGORITHM_RAW` (requires `std` and Nigthly compiler) | `custom` | All targets | `*` | User-provided custom implementation (see [custom backend]) Opt-in backends can be enabled using the `getrandom_backend` configuration flag. @@ -351,6 +352,7 @@ dual licensed as above, without any additional terms or conditions. [`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html#functions [esp-idf-rng]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html [esp-trng-docs]: https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#rng +[`EFI_RNG_PROTOCOL`]: https://uefi.org/specs/UEFI/2.10/37_Secure_Technologies.html#efi-rng-protocol [`random_get`]: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno [`get-random-u64`]: https://github.com/WebAssembly/WASI/blob/v0.2.1/wasip2/random/random.wit#L23-L28 [configuration flags]: #configuration-flags diff --git a/src/backends.rs b/src/backends.rs index 1509af11..65dc7e2f 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -19,6 +19,9 @@ cfg_if! { } else if #[cfg(getrandom_backend = "rndr")] { mod rndr; pub use rndr::*; + } else if #[cfg(getrandom_backend = "efi_rng")] { + mod rdrand; + pub use rdrand::*; } else if #[cfg(all(getrandom_backend = "wasm_js"))] { cfg_if! { if #[cfg(feature = "wasm_js")] { diff --git a/src/backends/efi_rng.rs b/src/backends/efi_rng.rs new file mode 100644 index 00000000..fb89e59b --- /dev/null +++ b/src/backends/efi_rng.rs @@ -0,0 +1,122 @@ +//! Implementation for UEFI using EFI_RNG_PROTOCOL +use crate::Error; +use core::{ + mem::MaybeUninit, + ptr::{self, null_mut, NonNull}, + sync::atomic::{AtomicPtr, Ordering::Relaxed}, +}; +use r_efi::{ + efi::{BootServices, Handle}, + protocols::rng, +}; + +extern crate std; + +pub use crate::util::{inner_u32, inner_u64}; + +#[cfg(not(target_os = "uefi"))] +compile_error!("`efi_rng` backend can be enabled only for UEFI targets!"); + +static RNG_PROTOCOL: AtomicPtr = AtomicPtr::new(null_mut()); + +fn init() -> Result, Error> { + const HANDLE_SIZE: usize = size_of::(); + + let boot_services = std::os::uefi::env::boot_services() + .ok_or(Error::BOOT_SERVICES_UNAVAILABLE)? + .cast::(); + + let mut handles = [ptr::null_mut(); 16]; + // `locate_handle` operates with length in bytes + let mut buf_size = handles.len() * HANDLE_SIZE; + let mut guid = rng::PROTOCOL_GUID; + let ret = unsafe { + ((*boot_services.as_ptr()).locate_handle)( + r_efi::efi::BY_PROTOCOL, + &mut guid, + null_mut(), + &mut buf_size, + handles.as_mut_ptr(), + ) + }; + + if ret.is_error() { + return Err(Error::TEMP_EFI_ERROR); + } + + let handles_len = buf_size / HANDLE_SIZE; + let handles = handles.get(..handles_len).ok_or(Error::UNEXPECTED)?; + + let system_handle = std::os::uefi::env::image_handle(); + for &handle in handles { + let mut protocol: MaybeUninit<*mut rng::Protocol> = MaybeUninit::uninit(); + + let mut protocol_guid = rng::PROTOCOL_GUID; + let ret = unsafe { + ((*boot_services.as_ptr()).open_protocol)( + handle, + &mut protocol_guid, + protocol.as_mut_ptr().cast(), + system_handle.as_ptr(), + ptr::null_mut(), + r_efi::system::OPEN_PROTOCOL_GET_PROTOCOL, + ) + }; + + let protocol = if ret.is_error() { + continue; + } else { + let protocol = unsafe { protocol.assume_init() }; + NonNull::new(protocol).ok_or(Error::UNEXPECTED)? + }; + + // Try to use the acquired protocol handle + let mut buf = [0u8; 8]; + let mut alg_guid = rng::ALGORITHM_RAW; + let ret = unsafe { + ((*protocol.as_ptr()).get_rng)( + protocol.as_ptr(), + &mut alg_guid, + buf.len(), + buf.as_mut_ptr(), + ) + }; + + if ret.is_error() { + continue; + } + + RNG_PROTOCOL.store(protocol.as_ptr(), Relaxed); + return Ok(protocol); + } + Err(Error::NO_RNG_HANDLE) +} + +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let protocol = match NonNull::new(RNG_PROTOCOL.load(Relaxed)) { + Some(p) => p, + None => init()?, + }; + + let mut alg_guid = rng::ALGORITHM_RAW; + let ret = unsafe { + ((*protocol.as_ptr()).get_rng)( + protocol.as_ptr(), + &mut alg_guid, + dest.len(), + dest.as_mut_ptr().cast::(), + ) + }; + + if ret.is_error() { + Err(Error::TEMP_EFI_ERROR) + } else { + Ok(()) + } +} + +impl Error { + pub(crate) const BOOT_SERVICES_UNAVAILABLE: Error = Self::new_internal(10); + pub(crate) const NO_RNG_HANDLE: Error = Self::new_internal(11); + pub(crate) const TEMP_EFI_ERROR: Error = Self::new_internal(12); +} diff --git a/src/lib.rs b/src/lib.rs index 2ac0ad0b..51c494e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ #![doc = include_str!("../README.md")] #![warn(rust_2018_idioms, unused_lifetimes, missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(getrandom_backend = "efi_rng", feature(uefi_std))] #![deny( clippy::cast_lossless, clippy::cast_possible_truncation,