Skip to content

Commit 0428d50

Browse files
authored
Add efi_rng opt-in backend (#570)
The implementation is based on the [`std` code][0]. I am not sure why we need the try loop over protocol handles instead of just getting the first handle, but I decided to just follow `std` here. [0]: https://github.com/rust-lang/rust/blob/master/library/std/src/sys/random/uefi.rs Because the backend requires access to `BootServices` it has to rely on the unstable `uefi_std` feature. We could alternatively use the `uefi` crate, but it would tie the backend to the `uefi` crate environment, which is probably not desirable. `TEMP_EFI_ERROR` is used as a temporary solution and will be fixed in a separate PR.
1 parent 0ce3de1 commit 0428d50

File tree

9 files changed

+189
-21
lines changed

9 files changed

+189
-21
lines changed

.github/workflows/build.yml

+20
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,26 @@ jobs:
192192
- name: Build
193193
run: cargo build --target ${{ matrix.target.target }} ${{ matrix.feature.feature }} -Zbuild-std=${{ matrix.feature.build-std }}
194194

195+
efi-rng:
196+
name: UEFI RNG Protocol
197+
runs-on: ubuntu-24.04
198+
strategy:
199+
matrix:
200+
target: [
201+
aarch64-unknown-uefi,
202+
x86_64-unknown-uefi,
203+
i686-unknown-uefi,
204+
]
205+
steps:
206+
- uses: actions/checkout@v4
207+
- uses: dtolnay/rust-toolchain@nightly # Required to build libstd
208+
with:
209+
components: rust-src
210+
- uses: Swatinem/rust-cache@v2
211+
- env:
212+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="efi_rng"
213+
run: cargo build -Z build-std=std --target=${{ matrix.target }} --features std
214+
195215
rdrand-uefi:
196216
name: RDRAND UEFI
197217
runs-on: ubuntu-24.04

.github/workflows/workspace.yml

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ jobs:
6969
env:
7070
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr"
7171
run: cargo clippy -Zbuild-std=core --target aarch64-unknown-linux-gnu
72+
- name: EFI RNG (efi_rng.rs)
73+
env:
74+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="efi_rng"
75+
run: cargo clippy -Zbuild-std=std --target x86_64-unknown-uefi
7276
- name: Solaris (solaris.rs)
7377
run: cargo clippy -Zbuild-std=core --target x86_64-pc-solaris
7478
- name: SOLID (solid.rs)

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [0.3.2] - UNRELEASED
88

99
### Added
10+
- `efi_rng` opt-in backend [#570]
1011
- `linux_raw` opt-in backend [#572]
1112
- `.cargo/config.toml` example in the crate-level docs [#591]
1213
- `getrandom_test_linux_without_fallback` configuration flag to test that file fallback
@@ -21,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2122
- Remove `linux_android.rs` and use `getrandom.rs` instead [#603]
2223
- Always use `RtlGenRandom` on Windows targets when compiling with pre-1.78 Rust [#610]
2324

25+
[#570]: https://github.com/rust-random/getrandom/pull/570
2426
[#572]: https://github.com/rust-random/getrandom/pull/572
2527
[#591]: https://github.com/rust-random/getrandom/pull/591
2628
[#593]: https://github.com/rust-random/getrandom/pull/593

Cargo.lock

+27-20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ libc = { version = "0.2.154", default-features = false }
3838
[target.'cfg(any(target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"))'.dependencies]
3939
libc = { version = "0.2.154", default-features = false }
4040

41+
# efi_rng
42+
[target.'cfg(all(target_os = "uefi", getrandom_backend = "efi_rng"))'.dependencies]
43+
r-efi = { version = "5.1", default-features = false }
44+
4145
# getentropy
4246
[target.'cfg(any(target_os = "macos", target_os = "openbsd", target_os = "vita", target_os = "emscripten"))'.dependencies]
4347
libc = { version = "0.2.154", default-features = false }
@@ -81,7 +85,7 @@ wasm-bindgen-test = "0.3"
8185
[lints.rust.unexpected_cfgs]
8286
level = "warn"
8387
check-cfg = [
84-
'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "linux_raw", "wasm_js"))',
88+
'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "wasm_js"))',
8589
'cfg(getrandom_msan)',
8690
'cfg(getrandom_windows_legacy)',
8791
'cfg(getrandom_test_linux_fallback)',

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ of randomness based on their specific needs:
8484
| `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction
8585
| `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register
8686
| `wasm_js` | Web Browser, Node.js | `wasm32‑unknown‑unknown`, `wasm32v1-none` | [`Crypto.getRandomValues`]. Requires feature `wasm_js` ([see below](#webassembly-support)).
87+
| `efi_rng` | UEFI | `*-unknown‑uefi` | [`EFI_RNG_PROTOCOL`] with `EFI_RNG_ALGORITHM_RAW` (requires `std` and Nigthly compiler)
8788
| `custom` | All targets | `*` | User-provided custom implementation (see [custom backend])
8889

8990
Opt-in backends can be enabled using the `getrandom_backend` configuration flag.
@@ -361,6 +362,7 @@ dual licensed as above, without any additional terms or conditions.
361362
[`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html#functions
362363
[esp-idf-rng]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html
363364
[esp-trng-docs]: https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#rng
365+
[`EFI_RNG_PROTOCOL`]: https://uefi.org/specs/UEFI/2.10/37_Secure_Technologies.html#efi-rng-protocol
364366
[`random_get`]: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno
365367
[`get-random-u64`]: https://github.com/WebAssembly/WASI/blob/v0.2.1/wasip2/random/random.wit#L23-L28
366368
[configuration flags]: #configuration-flags

src/backends.rs

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ cfg_if! {
2222
} else if #[cfg(getrandom_backend = "rndr")] {
2323
mod rndr;
2424
pub use rndr::*;
25+
} else if #[cfg(getrandom_backend = "efi_rng")] {
26+
mod efi_rng;
27+
pub use efi_rng::*;
2528
} else if #[cfg(all(getrandom_backend = "wasm_js"))] {
2629
cfg_if! {
2730
if #[cfg(feature = "wasm_js")] {

src/backends/efi_rng.rs

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! Implementation for UEFI using EFI_RNG_PROTOCOL
2+
use crate::Error;
3+
use core::{
4+
mem::MaybeUninit,
5+
ptr::{self, null_mut, NonNull},
6+
sync::atomic::{AtomicPtr, Ordering::Relaxed},
7+
};
8+
use r_efi::{
9+
efi::{BootServices, Handle},
10+
protocols::rng,
11+
};
12+
13+
extern crate std;
14+
15+
pub use crate::util::{inner_u32, inner_u64};
16+
17+
#[cfg(not(target_os = "uefi"))]
18+
compile_error!("`efi_rng` backend can be enabled only for UEFI targets!");
19+
20+
static RNG_PROTOCOL: AtomicPtr<rng::Protocol> = AtomicPtr::new(null_mut());
21+
22+
#[cold]
23+
#[inline(never)]
24+
fn init() -> Result<NonNull<rng::Protocol>, Error> {
25+
const HANDLE_SIZE: usize = size_of::<Handle>();
26+
27+
let boot_services = std::os::uefi::env::boot_services()
28+
.ok_or(Error::BOOT_SERVICES_UNAVAILABLE)?
29+
.cast::<BootServices>();
30+
31+
let mut handles = [ptr::null_mut(); 16];
32+
// `locate_handle` operates with length in bytes
33+
let mut buf_size = handles.len() * HANDLE_SIZE;
34+
let mut guid = rng::PROTOCOL_GUID;
35+
let ret = unsafe {
36+
((*boot_services.as_ptr()).locate_handle)(
37+
r_efi::efi::BY_PROTOCOL,
38+
&mut guid,
39+
null_mut(),
40+
&mut buf_size,
41+
handles.as_mut_ptr(),
42+
)
43+
};
44+
45+
if ret.is_error() {
46+
return Err(Error::TEMP_EFI_ERROR);
47+
}
48+
49+
let handles_len = buf_size / HANDLE_SIZE;
50+
let handles = handles.get(..handles_len).ok_or(Error::UNEXPECTED)?;
51+
52+
let system_handle = std::os::uefi::env::image_handle();
53+
for &handle in handles {
54+
let mut protocol: MaybeUninit<*mut rng::Protocol> = MaybeUninit::uninit();
55+
56+
let mut protocol_guid = rng::PROTOCOL_GUID;
57+
let ret = unsafe {
58+
((*boot_services.as_ptr()).open_protocol)(
59+
handle,
60+
&mut protocol_guid,
61+
protocol.as_mut_ptr().cast(),
62+
system_handle.as_ptr(),
63+
ptr::null_mut(),
64+
r_efi::system::OPEN_PROTOCOL_GET_PROTOCOL,
65+
)
66+
};
67+
68+
let protocol = if ret.is_error() {
69+
continue;
70+
} else {
71+
let protocol = unsafe { protocol.assume_init() };
72+
NonNull::new(protocol).ok_or(Error::UNEXPECTED)?
73+
};
74+
75+
// Try to use the acquired protocol handle
76+
let mut buf = [0u8; 8];
77+
let mut alg_guid = rng::ALGORITHM_RAW;
78+
let ret = unsafe {
79+
((*protocol.as_ptr()).get_rng)(
80+
protocol.as_ptr(),
81+
&mut alg_guid,
82+
buf.len(),
83+
buf.as_mut_ptr(),
84+
)
85+
};
86+
87+
if ret.is_error() {
88+
continue;
89+
}
90+
91+
RNG_PROTOCOL.store(protocol.as_ptr(), Relaxed);
92+
return Ok(protocol);
93+
}
94+
Err(Error::NO_RNG_HANDLE)
95+
}
96+
97+
#[inline]
98+
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
99+
let protocol = match NonNull::new(RNG_PROTOCOL.load(Relaxed)) {
100+
Some(p) => p,
101+
None => init()?,
102+
};
103+
104+
let mut alg_guid = rng::ALGORITHM_RAW;
105+
let ret = unsafe {
106+
((*protocol.as_ptr()).get_rng)(
107+
protocol.as_ptr(),
108+
&mut alg_guid,
109+
dest.len(),
110+
dest.as_mut_ptr().cast::<u8>(),
111+
)
112+
};
113+
114+
if ret.is_error() {
115+
Err(Error::TEMP_EFI_ERROR)
116+
} else {
117+
Ok(())
118+
}
119+
}
120+
121+
impl Error {
122+
pub(crate) const BOOT_SERVICES_UNAVAILABLE: Error = Self::new_internal(10);
123+
pub(crate) const NO_RNG_HANDLE: Error = Self::new_internal(11);
124+
pub(crate) const TEMP_EFI_ERROR: Error = Self::new_internal(12);
125+
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#![doc = include_str!("../README.md")]
1111
#![warn(rust_2018_idioms, unused_lifetimes, missing_docs)]
1212
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
13+
#![cfg_attr(getrandom_backend = "efi_rng", feature(uefi_std))]
1314
#![deny(
1415
clippy::cast_lossless,
1516
clippy::cast_possible_truncation,

0 commit comments

Comments
 (0)