diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 24465a1a..9c114d7b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 99fa41fe..6f377e6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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)', diff --git a/README.md b/README.md index 9675c67f..cfa1a4bc 100644 --- a/README.md +++ b/README.md @@ -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`]: @@ -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] +) -> 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 @@ -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) diff --git a/src/backends.rs b/src/backends.rs index b5d5bd1d..db06e033 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -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::*; diff --git a/src/backends/extern_item_impls.rs b/src/backends/extern_item_impls.rs new file mode 100644 index 00000000..f08d4fd0 --- /dev/null +++ b/src/backends/extern_item_impls.rs @@ -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]) -> Result<(), Error>; + +/// Declares this function as an external implementation of [`u32`](crate::u32). +#[eii(u32)] +pub(crate) fn inner_u32() -> Result { + crate::util::inner_u32() +} + +/// Declares this function as an external implementation of [`u64`](crate::u64). +#[eii(u64)] +pub(crate) fn inner_u64() -> Result { + crate::util::inner_u64() +} diff --git a/src/lib.rs b/src/lib.rs index d25780b5..f679f603 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -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] +/// ) -> 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 diff --git a/tests/mod.rs b/tests/mod.rs index 6033774c..3c6bf990 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -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]) -> 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 { + // 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)); + } +}