Skip to content

Commit

Permalink
Add efi_rng opt-in backend
Browse files Browse the repository at this point in the history
  • Loading branch information
newpavlov committed Feb 3, 2025
1 parent ce3b017 commit e7eb146
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 1 deletion.
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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)',
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/backends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")] {
Expand Down
122 changes: 122 additions & 0 deletions src/backends/efi_rng.rs
Original file line number Diff line number Diff line change
@@ -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<rng::Protocol> = AtomicPtr::new(null_mut());

fn init() -> Result<NonNull<rng::Protocol>, Error> {
const HANDLE_SIZE: usize = size_of::<Handle>();

let boot_services = std::os::uefi::env::boot_services()
.ok_or(Error::BOOT_SERVICES_UNAVAILABLE)?
.cast::<BootServices>();

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<u8>]) -> 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::<u8>(),
)
};

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);
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit e7eb146

Please sign in to comment.