Skip to content

Commit 557c77a

Browse files
committed
Add optional support for extern_item_impls
Adds support for the nightly-only feature `extern_item_impls` gated behind a new fRust flag `getrandom_extern_item_impls`. This allows overriding the default backend implementation or providing one where it would otherwise not be available from safe Rust code in a user-friendly and idiomatic way. Offering this PR largely as a hypothetical since there are open questions (e.g., if this was stable, should it be optional or default? should there be a way to prevent overriding the default backend to guard against malicious libraries?, etc.)
1 parent 22082f2 commit 557c77a

4 files changed

Lines changed: 120 additions & 27 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ wasm-bindgen-test = "0.3"
9494
[lints.rust.unexpected_cfgs]
9595
level = "warn"
9696
check-cfg = [
97+
'cfg(getrandom_extern_item_impls)',
9798
'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported"))',
9899
'cfg(getrandom_msan)',
99100
'cfg(getrandom_test_linux_fallback)',

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,32 @@ unsafe extern "Rust" fn __getrandom_v03_custom(
201201
}
202202
```
203203

204+
### Externally Implemented Interface (Nightly)
205+
206+
Using the nightly-only feature [`extern_item_impls`](https://github.com/rust-lang/rust/issues/125418)
207+
it is possible to provide a custom backend for `getrandom`, even to override
208+
an existing first-party implementation. First, enable the `getrandom_extern_item_impls`
209+
Rust flag to allow usage of this nightly feature. Then, you may provide
210+
implementations for `fill_uninit`, `u32`, and/or u64` with an attribute macro
211+
from the `implemtation` module.
212+
213+
```rust
214+
use core::mem::MaybeUninit;
215+
216+
#[getrandom::implementation::fill_uninit]
217+
fn my_fill_uninit_implementation(
218+
dest: &mut [MaybeUninit<u8>]
219+
) -> Result<(), getrandom::Error> {
220+
// ...
221+
Ok(())
222+
}
223+
```
224+
225+
If `getrandom` is able to provide a backend implemtation, it will be a weak
226+
symbol that can be overridden as above. If no implementation is available,
227+
a compilation error will be raised with instructions for how to provide
228+
an implementation.
229+
204230
### Unsupported backend
205231

206232
In some rare scenarios, you might be compiling this crate for an unsupported

src/backends.rs

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,53 +7,88 @@
77
//! The function MUST NOT ever write uninitialized bytes into `dest`,
88
//! regardless of what value it returns.
99
10+
/// Declares this function as an external implementation of [`u32`](crate::u32).
11+
#[cfg_attr(getrandom_extern_item_impls, eii(u32))]
12+
pub(crate) fn inner_u32() -> Result<u32, crate::Error> {
13+
default_u32()
14+
}
15+
16+
/// Declares this function as an external implementation of [`u64`](crate::u64).
17+
#[cfg_attr(getrandom_extern_item_impls, eii(u64))]
18+
pub(crate) fn inner_u64() -> Result<u64, crate::Error> {
19+
default_u64()
20+
}
21+
22+
macro_rules! implementation {
23+
() => {
24+
use $crate::util::{inner_u32 as default_u32, inner_u64 as default_u64};
25+
26+
/// Declares this function as an external implementation of [`fill_uninit`](crate::fill_uninit).
27+
#[cfg_attr(getrandom_extern_item_impls, eii(fill_uninit))]
28+
pub(crate) fn fill_inner(
29+
dest: &mut [::core::mem::MaybeUninit<u8>],
30+
) -> Result<(), $crate::Error>;
31+
};
32+
($backend:ident) => {
33+
use $backend::{inner_u32 as default_u32, inner_u64 as default_u64};
34+
35+
/// Declares this function as an external implementation of [`fill_uninit`](crate::fill_uninit).
36+
#[cfg_attr(getrandom_extern_item_impls, eii(fill_uninit))]
37+
pub(crate) fn fill_inner(
38+
dest: &mut [::core::mem::MaybeUninit<u8>],
39+
) -> Result<(), $crate::Error> {
40+
$backend::fill_inner(dest)
41+
}
42+
};
43+
}
44+
1045
cfg_if! {
1146
if #[cfg(getrandom_backend = "custom")] {
1247
mod custom;
13-
pub use custom::*;
48+
implementation!(custom);
1449
} else if #[cfg(getrandom_backend = "linux_getrandom")] {
1550
mod getrandom;
16-
pub use getrandom::*;
51+
implementation!(getrandom);
1752
} else if #[cfg(getrandom_backend = "linux_raw")] {
1853
mod linux_raw;
19-
pub use linux_raw::*;
54+
implementation!(linux_raw);
2055
} else if #[cfg(getrandom_backend = "rdrand")] {
2156
mod rdrand;
22-
pub use rdrand::*;
57+
implementation!(rdrand);
2358
} else if #[cfg(getrandom_backend = "rndr")] {
2459
mod rndr;
25-
pub use rndr::*;
60+
implementation!(rndr);
2661
} else if #[cfg(getrandom_backend = "efi_rng")] {
2762
mod efi_rng;
28-
pub use efi_rng::*;
63+
implementation!(efi_rng);
2964
} else if #[cfg(getrandom_backend = "windows_legacy")] {
3065
mod windows_legacy;
31-
pub use windows_legacy::*;
66+
implementation!(windows_legacy);
3267
} else if #[cfg(getrandom_backend = "unsupported")] {
3368
mod unsupported;
34-
pub use unsupported::*;
69+
implementation!(unsupported);
3570
} else if #[cfg(all(target_os = "linux", target_env = ""))] {
3671
mod linux_raw;
37-
pub use linux_raw::*;
72+
implementation!(linux_raw);
3873
} else if #[cfg(target_os = "espidf")] {
3974
mod esp_idf;
40-
pub use esp_idf::*;
75+
implementation!(esp_idf);
4176
} else if #[cfg(any(
4277
target_os = "haiku",
4378
target_os = "redox",
4479
target_os = "nto",
4580
target_os = "aix",
4681
))] {
4782
mod use_file;
48-
pub use use_file::*;
83+
implementation!(use_file);
4984
} else if #[cfg(any(
5085
target_os = "macos",
5186
target_os = "openbsd",
5287
target_os = "vita",
5388
target_os = "emscripten",
5489
))] {
5590
mod getentropy;
56-
pub use getentropy::*;
91+
implementation!(getentropy);
5792
} else if #[cfg(any(
5893
// Rust supports Android API level 19 (KitKat) [0] and the next upgrade targets
5994
// level 21 (Lollipop) [1], while `getrandom(2)` was added only in
@@ -101,7 +136,7 @@ cfg_if! {
101136
))] {
102137
mod use_file;
103138
mod linux_android_with_fallback;
104-
pub use linux_android_with_fallback::*;
139+
implementation!(linux_android_with_fallback);
105140
} else if #[cfg(any(
106141
target_os = "android",
107142
target_os = "linux",
@@ -115,57 +150,59 @@ cfg_if! {
115150
all(target_os = "horizon", target_arch = "arm"),
116151
))] {
117152
mod getrandom;
118-
pub use getrandom::*;
153+
implementation!(getrandom);
119154
} else if #[cfg(target_os = "solaris")] {
120155
mod solaris;
121-
pub use solaris::*;
156+
implementation!(solaris);
122157
} else if #[cfg(target_os = "netbsd")] {
123158
mod netbsd;
124-
pub use netbsd::*;
159+
implementation!(netbsd);
125160
} else if #[cfg(target_os = "fuchsia")] {
126161
mod fuchsia;
127-
pub use fuchsia::*;
162+
implementation!(fuchsia);
128163
} else if #[cfg(any(
129164
target_os = "ios",
130165
target_os = "visionos",
131166
target_os = "watchos",
132167
target_os = "tvos",
133168
))] {
134169
mod apple_other;
135-
pub use apple_other::*;
170+
implementation!(apple_other);
136171
} else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] {
137172
cfg_if! {
138173
if #[cfg(target_env = "p1")] {
139174
mod wasi_p1;
140-
pub use wasi_p1::*;
175+
implementation!(wasi_p1);
141176
} else {
142177
mod wasi_p2_3;
143-
pub use wasi_p2_3::*;
178+
implementation!(wasi_p2_3);
144179
}
145180
}
146181
} else if #[cfg(target_os = "hermit")] {
147182
mod hermit;
148-
pub use hermit::*;
183+
implementation!(hermit);
149184
} else if #[cfg(target_os = "vxworks")] {
150185
mod vxworks;
151-
pub use vxworks::*;
186+
implementation!(vxworks);
152187
} else if #[cfg(target_os = "solid_asp3")] {
153188
mod solid;
154-
pub use solid::*;
189+
implementation!(solid);
155190
} else if #[cfg(all(windows, target_vendor = "win7"))] {
156191
mod windows_legacy;
157-
pub use windows_legacy::*;
192+
implementation!(windows_legacy);
158193
} else if #[cfg(windows)] {
159194
mod windows;
160-
pub use windows::*;
195+
implementation!(windows);
161196
} else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] {
162197
mod rdrand;
163-
pub use rdrand::*;
198+
implementation!(rdrand);
164199
} else if #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] {
165200
cfg_if! {
166201
if #[cfg(feature = "wasm_js")] {
167202
mod wasm_js;
168-
pub use wasm_js::*;
203+
implementation!(wasm_js);
204+
} else if #[cfg(getrandom_extern_item_impls)] {
205+
implementation!();
169206
} else {
170207
compile_error!(concat!(
171208
"The wasm32-unknown-unknown targets are not supported by default; \
@@ -175,6 +212,8 @@ cfg_if! {
175212
));
176213
}
177214
}
215+
} else if #[cfg(getrandom_extern_item_impls)] {
216+
implementation!();
178217
} else {
179218
compile_error!(concat!(
180219
"target is not supported. You may need to define a custom backend see: \

src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
clippy::unnecessary_cast,
2727
clippy::useless_conversion
2828
)]
29+
#![cfg_attr(getrandom_extern_item_impls, feature(extern_item_impls))]
2930

3031
#[macro_use]
3132
extern crate cfg_if;
@@ -50,6 +51,32 @@ pub use sys_rng::SysRng;
5051

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

54+
#[cfg(getrandom_extern_item_impls)]
55+
pub mod implementation {
56+
//! Provides Externally Implementable Iterfaces for the core functionality of this crate.
57+
//! This allows `getrandom` to provide a default implementation and a common interface
58+
//! for all crates to use, while giving users a safe way to override that default where required.
59+
//!
60+
//! Must be enabled via the `getrandom_extern_item_impls` compiler flag, as this functionality
61+
//! is currently limited to nightly.
62+
//!
63+
//! # Examples
64+
//!
65+
//! ```rust
66+
//! # use core::mem::MaybeUninit;
67+
//! #[getrandom::implementation::fill_uninit]
68+
//! fn my_fill_uninit_implementation(
69+
//! dest: &mut [MaybeUninit<u8>]
70+
//! ) -> Result<(), getrandom::Error> {
71+
//! // ...
72+
//! # let _ = dest;
73+
//! # Err(Error::UNSUPPORTED)
74+
//! }
75+
//! ```
76+
77+
pub use crate::backends::{fill_uninit, u32, u64};
78+
}
79+
5380
/// Fill `dest` with random bytes from the system's preferred random number source.
5481
///
5582
/// This function returns an error on any failure, including partial reads. We

0 commit comments

Comments
 (0)