Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add efi_rng opt-in backend #570

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
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
4 changes: 4 additions & 0 deletions .github/workflows/workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ jobs:
env:
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr"
run: cargo clippy -Zbuild-std=core --target aarch64-unknown-linux-gnu
- name: EFI RNG (efi_rng.rs)
env:
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="efi_rng"
run: cargo clippy -Zbuild-std=std --target x86_64-unknown-uefi
- name: Solaris (solaris.rs)
run: cargo clippy -Zbuild-std=core --target x86_64-pc-solaris
- name: SOLID (solid.rs)
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 efi_rng;
pub use efi_rng::*;
} else if #[cfg(all(getrandom_backend = "wasm_js"))] {
cfg_if! {
if #[cfg(feature = "wasm_js")] {
Expand Down
125 changes: 125 additions & 0 deletions src/backends/efi_rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! 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());

#[cold]
#[inline(never)]
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)
}

#[inline]
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need TEMP_EFI_ERROR now if we have #569 ?

Copy link
Member Author

@newpavlov newpavlov Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I wrote in the OP, to encapsulate the UEFI status codes we need a wider state than 32-bits. Technically, on x86-64 we need 64 bits, but we probably can use NonZeroU64 since status codes with prefixes 0x1, 0x3, and 0x7 are used for "warning" codes.

}
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