Skip to content
Merged
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
14 changes: 14 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ jobs:
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"
run: cargo test --features=std,sys_rng

extern_item_impls:
name: Extern Item Implementations
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- uses: Swatinem/rust-cache@v2
- env:
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="extern_item_impls"
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="extern_item_impls"
run: cargo test --features=std --test mod extern_item_impls

ios:
name: iOS Simulator
runs-on: macos-14
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ wasm-bindgen-test = "0.3"
[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = [
'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported"))',
'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported", "extern_item_impls"))',
'cfg(getrandom_msan)',
'cfg(getrandom_test_linux_fallback)',
'cfg(getrandom_test_linux_without_fallback)',
Expand Down
53 changes: 43 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,17 @@ or `js-sys` on "unknown" WASM targets then it's acceptable to enable this featur
`getrandom` also provides optional (opt-in) backends, which allow users to customize the source
of randomness based on their specific needs:

| Backend name | Target | Target Triple | Implementation
| ----------------- | -------------------- | ------------------------ | --------------
| `linux_getrandom` | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call (without `/dev/urandom` fallback). Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow).
| `linux_raw` | Linux, Android | `*‑linux‑*` | Same as `linux_getrandom`, but uses raw `asm!`-based syscalls 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 Nightly compiler)
| `windows_legacy` | Windows | `*-windows-*` | [`RtlGenRandom`]
| `custom` | All targets | `*` | User-provided custom implementation (see [custom backend])
| `unsupported` | All targets | `*` | Always returns `Err(Error::UNSUPPORTED)` (see [unsupported backend])
| Backend name | Target | Target Triple | Implementation
| ------------------- | -------------------- | ------------------------ | --------------
| `linux_getrandom` | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call (without `/dev/urandom` fallback). Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow).
| `linux_raw` | Linux, Android | `*‑linux‑*` | Same as `linux_getrandom`, but uses raw `asm!`-based syscalls 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 Nightly compiler)
| `windows_legacy` | Windows | `*-windows-*` | [`RtlGenRandom`]
| `custom` | All targets | `*` | User-provided custom implementation (see [custom backend])
| `unsupported` | All targets | `*` | Always returns `Err(Error::UNSUPPORTED)` (see [unsupported backend])
| `extern_item_impls` | All targets | `*` | User or library provided custom implementation (see [externally implemented interface])

Opt-in backends can be enabled using the `getrandom_backend` configuration flag.
The flag can be set either by specifying the `rustflags` field in [`.cargo/config.toml`]:
Expand Down Expand Up @@ -201,6 +202,37 @@ unsafe extern "Rust" fn __getrandom_v03_custom(
}
```

### Externally Implemented Interface

Using the nightly-only feature [`extern_item_impls`](https://github.com/rust-lang/rust/issues/125418)
it is possible to provide a custom backend for `getrandom`, even to override
an existing first-party implementation. First, enable the `extern_item_impls`
opt-in backend to allow usage of this nightly feature. Then, you may provide
implementations for `fill_uninit`, `u32`, and/or `u64` with an attribute macro
from the `implementation` module.

```rust
use core::mem::MaybeUninit;

#[cfg(getrandom_backend = "extern_item_impls")]
#[getrandom::implementation::fill_uninit]
fn my_fill_uninit_implementation(
dest: &mut [MaybeUninit<u8>]
) -> Result<(), getrandom::Error> {
// ...
Ok(())
}
```

For further details on what a suitable implementation for `fill_uninit` may look
like, see [custom backend].

`getrandom` will provide a default implementation for `u32` and `u64`, but does
not currently provide a default for `fill_uninit`, even if one is normally
available for the current target. If no implementation is available,
a compilation error will be raised with instructions for how to provide
an implementation.

### Unsupported backend

In some rare scenarios, you might be compiling this crate for an unsupported
Expand Down Expand Up @@ -373,6 +405,7 @@ dual licensed as above, without any additional terms or conditions.
[WASI]: https://github.com/WebAssembly/WASI
[Emscripten]: https://emscripten.org
[opt-in]: #opt-in-backends
[externally implemented interface]: #externally-implemented-interface

[//]: # (licenses)

Expand Down
3 changes: 3 additions & 0 deletions src/backends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ cfg_if! {
} else if #[cfg(getrandom_backend = "unsupported")] {
mod unsupported;
pub use unsupported::*;
} else if #[cfg(getrandom_backend = "extern_item_impls")] {
pub(crate) mod extern_item_impls;
pub use extern_item_impls::*;
} else if #[cfg(all(target_os = "linux", target_env = ""))] {
mod linux_raw;
pub use linux_raw::*;
Expand Down
19 changes: 19 additions & 0 deletions src/backends/extern_item_impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! An implementation which calls out to an externally defined function.
use crate::Error;
use core::mem::MaybeUninit;

/// Declares this function as an external implementation of [`fill_uninit`](crate::fill_uninit).
#[eii(fill_uninit)]
pub(crate) fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error>;

/// Declares this function as an external implementation of [`u32`](crate::u32).
#[eii(u32)]
pub(crate) fn inner_u32() -> Result<u32, crate::Error> {
crate::util::inner_u32()
}

/// Declares this function as an external implementation of [`u64`](crate::u64).
#[eii(u64)]
pub(crate) fn inner_u64() -> Result<u64, crate::Error> {
crate::util::inner_u64()
}
27 changes: 27 additions & 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")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(getrandom_backend = "efi_rng", feature(uefi_std))]
#![cfg_attr(getrandom_backend = "extern_item_impls", feature(extern_item_impls))]

#[macro_use]
extern crate cfg_if;
Expand All @@ -34,6 +35,32 @@ pub use sys_rng::SysRng;

pub use crate::error::{Error, RawOsError};

/// Provides Externally Implementable Interfaces for the core functionality of this crate.
/// This allows `getrandom` to provide a default implementation and a common interface
/// for all crates to use, while giving users a safe way to override that default where required.
///
/// Must be enabled via the `extern_item_impls` opt-in backend, as this functionality
/// is currently limited to nightly.
///
/// # Examples
///
/// ```rust
/// # use core::mem::MaybeUninit;
/// # #[cfg(getrandom_backend = "extern_item_impls")]
/// #[getrandom::implementation::fill_uninit]
/// fn my_fill_uninit_implementation(
/// dest: &mut [MaybeUninit<u8>]
/// ) -> Result<(), getrandom::Error> {
/// // ...
/// # let _ = dest;
/// # Err(Error::UNSUPPORTED)
/// }
/// ```
#[cfg(getrandom_backend = "extern_item_impls")]
pub mod implementation {
pub use crate::backends::extern_item_impls::{fill_uninit, u32, u64};
}

/// Fill `dest` with random bytes from the system's preferred random number source.
///
/// This function returns an error on any failure, including partial reads. We
Expand Down
33 changes: 33 additions & 0 deletions tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,36 @@ mod custom {
assert!(res.is_err());
}
}

#[cfg(getrandom_backend = "extern_item_impls")]
mod extern_item_impls {
use core::mem::MaybeUninit;
use getrandom::Error;

// This implementation for fill_uninit will always fail.
//
// WARNING: this custom implementation is for testing purposes ONLY!

#[getrandom::implementation::fill_uninit]
fn my_fill_uninit_implementation(_dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
Err(Error::new_custom(4))
}

// This implementation returns a fixed value to demonstrate overriding defaults.
//
// WARNING: this custom implementation is for testing purposes ONLY!

#[getrandom::implementation::u32]
fn my_u32_implementation() -> Result<u32, Error> {
// Chosen by fair dice roll
Ok(4)
}

// Test that enabling the custom feature indeed uses the custom implementation
#[test]
fn test_extern_item_impls() {
let mut buf = [0u8; 123];
assert_eq!(getrandom::fill(&mut buf), Err(Error::new_custom(4)));
assert_eq!(getrandom::u32(), Ok(4));
}
}