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 Dec 18, 2024
1 parent e27f6ec commit 1d7165f
Show file tree
Hide file tree
Showing 6 changed files with 151 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 @@ -165,6 +165,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: [
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 @@ -26,6 +26,10 @@ libc = { version = "0.2.154", default-features = false }
[target.'cfg(all(any(target_os = "linux", target_os = "android"), any(target_env = "", getrandom_backend = "linux_rustix")))'.dependencies]
rustix = { version = "0.38.38", default-features = false, features = ["rand"] }

# efi_rng
[target.'cfg(all(target_os = "uefi", getrandom_backend = "efi_rng"))'.dependencies]
r-efi = { version = "5.1", default-features = false }

# apple-other
[target.'cfg(any(target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"))'.dependencies]
libc = { version = "0.2.154", default-features = false }
Expand Down Expand Up @@ -80,7 +84,7 @@ rustc-dep-of-std = ["dep:compiler_builtins", "dep:core"]
[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = [
'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "linux_rustix", "wasm_js", "esp_idf"))',
'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "linux_rustix", "wasm_js", "efi_rng", "esp_idf"))',
'cfg(getrandom_sanitize)',
'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 @@ -82,6 +82,7 @@ of randomness based on their specific needs:
| `linux_rustix` | Linux, Android | `*‑linux‑*` | Same as `linux_getrandom`, but uses [`rustix`] instead of `libc`.
| `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction
| `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register
| `efi_rng` | UEFI | `*-unknown‑uefi` | [`EFI_RNG_PROTOCOL`] (requires `std` and Nigthly compiler)
| `esp_idf` | ESP-IDF | `*‑espidf` | [`esp_fill_random`]. WARNING: can return low-quality entropy without proper hardware configuration!
| `wasm_js` | Web Browser, Node.js | `wasm32‑unknown‑unknown`, `wasm32v1-none` | [`Crypto.getRandomValues`]
| `custom` | All targets | `*` | User-provided custom implementation (see [custom backend])
Expand Down Expand Up @@ -336,6 +337,7 @@ dual licensed as above, without any additional terms or conditions.
[`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
[`EFI_RNG_PROTOCOL`]: https://uefi.org/specs/UEFI/2.10/37_Secure_Technologies.html#efi-rng-protocol
[`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html#_CPPv415esp_fill_randomPv6size_t
[`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
Expand Down
3 changes: 3 additions & 0 deletions src/backends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ cfg_if! {
} else if #[cfg(getrandom_backend = "wasm_js")] {
mod wasm_js;
pub use wasm_js::*;
} else if #[cfg(getrandom_backend = "efi_rng")] {
mod efi_rng;
pub use efi_rng::*;
} else if #[cfg(getrandom_backend = "esp_idf")] {
mod esp_idf;
pub use esp_idf::*;
Expand Down
120 changes: 120 additions & 0 deletions src/backends/efi_rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//! 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 ret = unsafe {
((*protocol.as_ptr()).get_rng)(
protocol.as_ptr(),
ptr::null_mut(),
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 ret = unsafe {
((*protocol.as_ptr()).get_rng)(
protocol.as_ptr(),
ptr::null_mut(),
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 @@ -11,6 +11,7 @@
#![warn(rust_2018_idioms, unused_lifetimes, missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(getrandom_sanitize, feature(cfg_sanitize))]
#![cfg_attr(getrandom_backend = "efi_rng", feature(uefi_std))]
#![deny(
clippy::cast_lossless,
clippy::cast_possible_truncation,
Expand Down

0 comments on commit 1d7165f

Please sign in to comment.