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 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
21 changes: 21 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,27 @@ 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `linux_rustix` opt-in backend [#520]
- Memory sanitizer support gated behind `getrandom_sanitize` configuration flag [#521]
- `u32` and `u64` functions for generating random values of the respective type [#544]
- `efi_rng` opt-in backend [#570]

### Fixed
- NetBSD fallback code based on `KERN_ARND` [#555]
Expand All @@ -61,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#554]: https://github.com/rust-random/getrandom/pull/554
[#555]: https://github.com/rust-random/getrandom/pull/555
[#557]: https://github.com/rust-random/getrandom/pull/557
[#570]: https://github.com/rust-random/getrandom/pull/570

## [0.2.15] - 2024-05-06
### Added
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`] with `EFI_RNG_ALGORITHM_RAW` (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
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);
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 @@ -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