From f0d2c3f66fd30f50ea701501f723dbea282232b1 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Mon, 7 Oct 2024 17:11:06 +0300 Subject: [PATCH] Use configuration flags to switch between backends (#504) This PR removes `linux_disable_fallback`, `rdrand`, `js`, `test-in-browser`, and `custom` crate features. As their replacement two new configuration flags are introduced: `getrandom_browser_test` and `getrandom_backend`. The latter can have the following values: `custom`, `rdrand`, `linux_getrandom`, `wasm_js`, `esp_idf`. `getrandom_backend` allows to change default backends which resolves issues like #346 and provides more flexibility to users. For example, it allows to use RDRAND or RDRND (see #494) directly instead of syscall-based interfaces. We previously did not allow such overwrites because of security concerns, but they do not apply in this case since configuration flags used by a project can not be controlled by its upstream dependencies. The `register_custom_getrandom!` macro is removed in favor of explicitly defining the `__getrandom_custom` function. It does not look like the macro was widely used in practice and it's probably easier to explain the `extern fn` approach (especially to embedded developers) than the "magical" registration macro. The new configuration flags also allow a great simplification of our testing code. Finally, ESP-IDF support is no longer enabled by default because of the concerns raised in #397. Users can enable it by enabling the `esp_idf` opt-in backend. Closes #230 Closes #346 Closes #397 Closes #498 --- .github/workflows/tests.yml | 83 ++++++-- .github/workflows/workspace.yml | 18 +- Cargo.toml | 30 ++- src/custom.rs | 95 +-------- src/lib.rs | 337 ++++++++++++++++++-------------- tests/common/mod.rs | 100 ---------- tests/custom.rs | 52 ----- tests/mod.rs | 182 +++++++++++++++++ tests/normal.rs | 11 -- tests/rdrand.rs | 22 --- 10 files changed, 461 insertions(+), 469 deletions(-) delete mode 100644 tests/common/mod.rs delete mode 100644 tests/custom.rs create mode 100644 tests/mod.rs delete mode 100644 tests/normal.rs delete mode 100644 tests/rdrand.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c4e1cf53..49828c8e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,9 +38,8 @@ jobs: toolchain: ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - run: cargo test - # Make sure enabling the std and custom features don't break anything - - run: cargo test --features=std,custom - - run: cargo test --features=linux_disable_fallback + # Make sure enabling the std feature doesn't break anything + - run: cargo test --features=std - if: ${{ matrix.toolchain == 'nightly' }} run: cargo test --benches @@ -69,6 +68,12 @@ jobs: sudo apt-get install --no-install-recommends ${{ matrix.packages }} - uses: Swatinem/rust-cache@v2 - run: cargo test ${{ matrix.cargo_test_opts }} --target=${{ matrix.target }} --features=std + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" + run: cargo test ${{ matrix.cargo_test_opts }} --target=${{ matrix.target }} --features=std + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" + run: cargo test --features=std ios-tests: name: iOS Simulator Test @@ -242,21 +247,27 @@ jobs: wasm-pack --version - uses: Swatinem/rust-cache@v2 - name: Test (Node) - run: wasm-pack test --node --features=js + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" + run: wasm-pack test --node - name: Test (Firefox) - run: wasm-pack test --headless --firefox --features=js,test-in-browser + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" --cfg getrandom_browser_test + run: wasm-pack test --headless --firefox - name: Test (Chrome) - run: wasm-pack test --headless --chrome --features=js,test-in-browser + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" --cfg getrandom_browser_test + run: wasm-pack test --headless --chrome - name: Test (Edge) if: runner.os == 'Windows' - run: wasm-pack test --headless --chrome --chromedriver $Env:EDGEWEBDRIVER\msedgedriver.exe --features=js,test-in-browser + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" --cfg getrandom_browser_test + run: wasm-pack test --headless --chrome --chromedriver $Env:EDGEWEBDRIVER\msedgedriver.exe - name: Test (Safari) if: runner.os == 'macOS' - run: wasm-pack test --headless --safari --features=js,test-in-browser - - name: Test (custom getrandom) - run: wasm-pack test --node --features=custom - - name: Test (JS overrides custom) - run: wasm-pack test --node --features=custom,js + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" --cfg getrandom_browser_test + run: wasm-pack test --headless --safari wasm64-tests: name: wasm64 Build/Link @@ -268,10 +279,12 @@ jobs: components: rust-src - uses: Swatinem/rust-cache@v2 - name: Build and Link tests (build-std) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" --cfg getrandom_browser_test # This target is Tier 3, so we have to build libstd ourselves. # We currently cannot run these tests because wasm-bindgen-test-runner # does not yet support memory64. - run: cargo test --no-run -Z build-std=std,panic_abort --target=wasm64-unknown-unknown --features=js + run: cargo test --no-run -Z build-std=std,panic_abort --target=wasm64-unknown-unknown wasi-tests: name: WASI Tests @@ -325,24 +338,18 @@ jobs: aarch64-kmc-solid_asp3, armv6k-nintendo-3ds, armv7-sony-vita-newlibeabihf, - riscv32imc-esp-espidf, aarch64-unknown-nto-qnx710, ] + # Supported tier 3 targets with libstd support include: - target: aarch64-unknown-nto-qnx710 features: ["std"] - # Supported tier 3 targets with libstd support - target: x86_64-unknown-openbsd features: ["std"] - target: x86_64-unknown-dragonfly features: ["std"] - target: x86_64-unknown-haiku features: ["std"] - # Unsupported tier 3 targets to test the rdrand feature - - target: x86_64-unknown-uefi - features: ["rdrand"] - - target: x86_64-unknown-l4re-uclibc - features: ["rdrand"] - target: i686-unknown-hurd-gnu features: ["std"] steps: @@ -353,6 +360,38 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo build -Z build-std=${{ contains(matrix.features, 'std') && 'std' || 'core'}} --target=${{ matrix.target }} --features="${{ join(matrix.features, ',') }}" + build-rdrand: + name: RDRAND Build + runs-on: ubuntu-22.04 + strategy: + matrix: + target: [ + x86_64-unknown-uefi, + x86_64-unknown-l4re-uclibc, + ] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly # Required to build libcore + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" + run: cargo build -Z build-std=core --target=${{ matrix.target }} + + build-esp-idf: + name: ESP-IDF Build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly # Required to build libcore + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="esp_idf" + run: cargo build -Z build-std=core --target=riscv32imc-esp-espidf + build-no-atomics: name: No Atomics Build runs-on: ubuntu-22.04 @@ -362,4 +401,6 @@ jobs: with: targets: riscv32i-unknown-none-elf - uses: Swatinem/rust-cache@v2 - - run: cargo build --features custom --target riscv32i-unknown-none-elf + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="custom" + run: cargo build --target riscv32i-unknown-none-elf diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index db7b1568..a230e1c9 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -27,11 +27,15 @@ jobs: components: clippy,rust-src - name: std feature run: cargo clippy --features std - - name: custom feature - run: cargo clippy -Zbuild-std=core --target riscv32i-unknown-none-elf --features custom + - name: custom backend + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="custom" + run: cargo clippy -Zbuild-std=core --target riscv32i-unknown-none-elf - name: iOS (apple-other.rs) run: cargo clippy -Zbuild-std=core --target x86_64-apple-ios - name: ESP-IDF (espidf.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="esp_idf" run: cargo clippy -Zbuild-std=core --target riscv32imc-esp-espidf - name: Fuchsia (fuchsia.rs) run: cargo clippy -Zbuild-std=core --target x86_64-unknown-fuchsia @@ -42,9 +46,13 @@ jobs: - name: Hermit (hermit.rs) run: cargo clippy -Zbuild-std=core --target x86_64-unknown-hermit - name: Web WASM (js.rs) - run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown --features js + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" + run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown - name: Linux (linux_android.rs) - run: cargo clippy --target x86_64-unknown-linux-gnu --features linux_disable_fallback + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" + run: cargo clippy --target x86_64-unknown-linux-gnu - name: Linux (linux_android_with_fallback.rs) run: cargo clippy --target x86_64-unknown-linux-gnu - name: NetBSD (netbsd.rs) @@ -93,4 +101,4 @@ jobs: - name: Generate Docs env: RUSTDOCFLAGS: "-Dwarnings --cfg docsrs" - run: cargo doc --no-deps --features custom + run: cargo doc --no-deps --features std diff --git a/Cargo.toml b/Cargo.toml index 2d8baa62..a688779e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,35 +27,27 @@ wasi = { version = "0.13", default-features = false } [target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies] windows-targets = "0.52" -[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] -wasm-bindgen = { version = "0.2.89", default-features = false, optional = true } -js-sys = { version = "0.3", optional = true } -[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies] +[target.'cfg(all(getrandom_backend = "wasm_js", any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] +wasm-bindgen = { version = "0.2.89", default-features = false } +js-sys = "0.3" +[target.'cfg(all(getrandom_backend = "wasm_js", getrandom_browser_test, any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies] wasm-bindgen-test = "0.3.39" [features] # Implement std-only traits for getrandom::Error std = [] -# Disable `/dev/urandom` fallback for Linux and Android targets. -# Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow). -linux_disable_fallback = [] -# Feature to enable fallback RDRAND-based implementation on x86/x86_64 -rdrand = [] -# Feature to enable JavaScript bindings on wasm*-unknown-unknown -js = ["wasm-bindgen", "js-sys"] -# Feature to enable custom RNG implementations -custom = [] # Unstable feature to support being a libstd dependency rustc-dep-of-std = ["compiler_builtins", "core"] -# Unstable/test-only feature to run wasm-bindgen tests in a browser -test-in-browser = [] -[[test]] -name = "custom" -required-features = ["custom"] +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(getrandom_backend, values("custom", "rdrand", "linux_getrandom", "wasm_js", "esp_idf"))', + 'cfg(getrandom_browser_test)', +] [package.metadata.docs.rs] -features = ["std", "custom"] +features = ["std"] rustdoc-args = ["--cfg", "docsrs"] # workaround for https://github.com/cross-rs/cross/issues/1345 diff --git a/src/custom.rs b/src/custom.rs index 79be7fc2..923b1cf8 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -1,103 +1,12 @@ //! An implementation which calls out to an externally defined function. -use crate::{util::uninit_slice_fill_zero, Error}; +use crate::Error; use core::{mem::MaybeUninit, num::NonZeroU32}; -/// Register a function to be invoked by `getrandom` on unsupported targets. -/// -/// ## Writing a custom `getrandom` implementation -/// -/// The function to register must have the same signature as -/// [`getrandom::getrandom`](crate::getrandom). The function can be defined -/// wherever you want, either in root crate or a dependent crate. -/// -/// For example, if we wanted a `failure-getrandom` crate containing an -/// implementation that always fails, we would first depend on `getrandom` -/// (for the [`Error`] type) in `failure-getrandom/Cargo.toml`: -/// ```toml -/// [dependencies] -/// getrandom = "0.2" -/// ``` -/// Note that the crate containing this function does **not** need to enable the -/// `"custom"` Cargo feature. -/// -/// Next, in `failure-getrandom/src/lib.rs`, we define our function: -/// ```rust -/// use core::num::NonZeroU32; -/// use getrandom::Error; -/// -/// // Some application-specific error code -/// const MY_CUSTOM_ERROR_CODE: u32 = Error::CUSTOM_START + 42; -/// pub fn always_fail(buf: &mut [u8]) -> Result<(), Error> { -/// let code = NonZeroU32::new(MY_CUSTOM_ERROR_CODE).unwrap(); -/// Err(Error::from(code)) -/// } -/// ``` -/// -/// ## Registering a custom `getrandom` implementation -/// -/// Functions can only be registered in the root binary crate. Attempting to -/// register a function in a non-root crate will result in a linker error. -/// This is similar to -/// [`#[panic_handler]`](https://doc.rust-lang.org/nomicon/panic-handler.html) or -/// [`#[global_allocator]`](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html), -/// where helper crates define handlers/allocators but only the binary crate -/// actually _uses_ the functionality. -/// -/// To register the function, we first depend on `failure-getrandom` _and_ -/// `getrandom` in `Cargo.toml`: -/// ```toml -/// [dependencies] -/// failure-getrandom = "0.1" -/// getrandom = { version = "0.2", features = ["custom"] } -/// ``` -/// -/// Then, we register the function in `src/main.rs`: -/// ```rust -/// # mod failure_getrandom { pub fn always_fail(_: &mut [u8]) -> Result<(), getrandom::Error> { unimplemented!() } } -/// use failure_getrandom::always_fail; -/// use getrandom::register_custom_getrandom; -/// -/// register_custom_getrandom!(always_fail); -/// ``` -/// -/// Now any user of `getrandom` (direct or indirect) on this target will use the -/// registered function. As noted in the -/// [top-level documentation](index.html#custom-implementations) this -/// registration only has an effect on unsupported targets. -#[macro_export] -macro_rules! register_custom_getrandom { - ($path:path) => { - // TODO(MSRV 1.37): change to unnamed block - const __GETRANDOM_INTERNAL: () = { - // We use Rust ABI to be safe against potential panics in the passed function. - #[no_mangle] - unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { - // Make sure the passed function has the type of getrandom::getrandom - type F = fn(&mut [u8]) -> ::core::result::Result<(), $crate::Error>; - let _: F = $crate::getrandom; - let f: F = $path; - let slice = ::core::slice::from_raw_parts_mut(dest, len); - match f(slice) { - Ok(()) => 0, - Err(e) => e.code().get(), - } - } - }; - }; -} - -#[allow(dead_code)] pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { extern "Rust" { fn __getrandom_custom(dest: *mut u8, len: usize) -> u32; } - // Previously we always passed a valid, initialized slice to - // `__getrandom_custom`. Ensure `dest` has been initialized for backward - // compatibility with implementations that rely on that (e.g. Rust - // implementations that construct a `&mut [u8]` slice from `dest` and - // `len`). - let dest = uninit_slice_fill_zero(dest); - let ret = unsafe { __getrandom_custom(dest.as_mut_ptr(), dest.len()) }; + let ret = unsafe { __getrandom_custom(dest.as_mut_ptr().cast(), dest.len()) }; match NonZeroU32::new(ret) { None => Ok(()), Some(code) => Err(Error::from(code)), diff --git a/src/lib.rs b/src/lib.rs index 97622ecf..62c2dab3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,104 +2,88 @@ //! //! # Supported targets //! -//! | Target | Target Triple | Implementation -//! | ----------------- | ------------------ | -------------- -//! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` -//! | Windows 10+ | `*‑windows‑*` | [`ProcessPrng`] -//! | Windows 7 and 8 | `*-win7‑windows‑*` | [`RtlGenRandom`] -//! | macOS | `*‑apple‑darwin` | [`getentropy`][3] +//! | Target | Target Triple | Implementation +//! | ------------------ | ------------------ | -------------- +//! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` +//! | Windows 10+ | `*‑windows‑*` | [`ProcessPrng`] +//! | Windows 7 and 8 | `*-win7‑windows‑*` | [`RtlGenRandom`] +//! | macOS | `*‑apple‑darwin` | [`getentropy`][3] //! | iOS, tvOS, watchOS | `*‑apple‑ios`, `*-apple-tvos`, `*-apple-watchos` | [`CCRandomGenerateBytes`] -//! | FreeBSD | `*‑freebsd` | [`getrandom`][5] -//! | OpenBSD | `*‑openbsd` | [`getentropy`][7] -//! | NetBSD | `*‑netbsd` | [`getrandom`][16] if available, otherwise [`kern.arandom`][8] -//! | Dragonfly BSD | `*‑dragonfly` | [`getrandom`][9] -//! | Solaris | `*‑solaris` | [`getrandom`][11] (with `GRND_RANDOM`) -//! | illumos | `*‑illumos` | [`getrandom`][12] -//! | Fuchsia OS | `*‑fuchsia` | [`cprng_draw`] -//! | Redox | `*‑redox` | `/dev/urandom` -//! | Haiku | `*‑haiku` | `/dev/urandom` (identical to `/dev/random`) -//! | Hermit | `*-hermit` | [`sys_read_entropy`] -//! | Hurd | `*-hurd-*` | [`getrandom`][17] -//! | SGX | `x86_64‑*‑sgx` | [`RDRAND`] -//! | VxWorks | `*‑wrs‑vxworks‑*` | `randABytes` after checking entropy pool initialization with `randSecure` -//! | ESP-IDF | `*‑espidf` | [`esp_fill_random`] -//! | Emscripten | `*‑emscripten` | [`getentropy`][13] -//! | WASI 0.1 | `wasm32‑wasip1` | [`random_get`] -//! | WASI 0.2 | `wasm32‑wasip2` | [`get-random-u64`] -//! | Web Browser and Node.js | `wasm*‑*‑unknown` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js, see [WebAssembly support] -//! | SOLID | `*-kmc-solid_*` | `SOLID_RNG_SampleRandomBytes` -//! | Nintendo 3DS | `*-nintendo-3ds` | [`getrandom`][18] -//! | PS Vita | `*-vita-*` | [`getentropy`][13] -//! | QNX Neutrino | `*‑nto-qnx*` | [`/dev/urandom`][14] (identical to `/dev/random`) -//! | AIX | `*-ibm-aix` | [`/dev/urandom`][15] +//! | FreeBSD | `*‑freebsd` | [`getrandom`][5] +//! | OpenBSD | `*‑openbsd` | [`getentropy`][7] +//! | NetBSD | `*‑netbsd` | [`getrandom`][16] if available, otherwise [`kern.arandom`][8] +//! | Dragonfly BSD | `*‑dragonfly` | [`getrandom`][9] +//! | Solaris | `*‑solaris` | [`getrandom`][11] (with `GRND_RANDOM`) +//! | illumos | `*‑illumos` | [`getrandom`][12] +//! | Fuchsia OS | `*‑fuchsia` | [`cprng_draw`] +//! | Redox | `*‑redox` | `/dev/urandom` +//! | Haiku | `*‑haiku` | `/dev/urandom` (identical to `/dev/random`) +//! | Hermit | `*-hermit` | [`sys_read_entropy`] +//! | Hurd | `*-hurd-*` | [`getrandom`][17] +//! | SGX | `x86_64‑*‑sgx` | [`RDRAND`] +//! | VxWorks | `*‑wrs‑vxworks‑*` | `randABytes` after checking entropy pool initialization with `randSecure` +//! | Emscripten | `*‑emscripten` | [`getentropy`][13] +//! | WASI 0.1 | `wasm32‑wasip1` | [`random_get`] +//! | WASI 0.2 | `wasm32‑wasip2` | [`get-random-u64`] +//! | SOLID | `*-kmc-solid_*` | `SOLID_RNG_SampleRandomBytes` +//! | Nintendo 3DS | `*-nintendo-3ds` | [`getrandom`][18] +//! | PS Vita | `*-vita-*` | [`getentropy`][13] +//! | QNX Neutrino | `*‑nto-qnx*` | [`/dev/urandom`][14] (identical to `/dev/random`) +//! | AIX | `*-ibm-aix` | [`/dev/urandom`][15] //! //! Pull Requests that add support for new targets to `getrandom` are always welcome. //! -//! ## Unsupported targets +//! ## Opt-in backends //! -//! By default, `getrandom` will not compile on unsupported targets, but certain -//! features allow a user to select a "fallback" implementation if no supported -//! implementation exists. +//! `getrandom` also provides optional backends which can be enabled using `getrandom_backend` +//! configuration flag: //! -//! All of the below mechanisms only affect unsupported -//! targets. Supported targets will _always_ use their supported implementations. -//! This prevents a crate from overriding a secure source of randomness -//! (either accidentally or intentionally). +//! | 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). +//! | `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction +//! | `esp_idf` | ESP-IDF | `*‑espidf` | [`esp_fill_random`]. WARNING: can return low quality entropy without proper hardware configuration! +//! | `wasm_js` | Web Browser, Node.js | `wasm*‑*‑unknown` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js (see [WebAssembly support]) +//! | `custom` | All targets | `*` | User-provided custom implementation (see [custom backend]) //! -//! ## `/dev/urandom` fallback on Linux and Android +//! The configuration flag can be enabled either by specifying the `rustflags` field in +//! [`.cargo/config.toml`] (note that it can be done on a per-target basis), or by using +//! `RUSTFLAGS` environment variable: //! -//! On Linux targets the fallback is present only if either `target_env` is `musl`, -//! or `target_arch` is one of the following: `aarch64`, `arm`, `powerpc`, `powerpc64`, -//! `s390x`, `x86`, `x86_64`. Other supported targets [require][platform-support] -//! kernel versions which support `getrandom` system call, so fallback is not needed. -//! -//! On Android targets the fallback is present only for the following `target_arch`es: -//! `aarch64`, `arm`, `x86`, `x86_64`. Other `target_arch`es (e.g. RISC-V) require -//! sufficiently high API levels. +//! ```sh +//! RUSTFLAGS='getrandom_backend="linux_getrandom"' cargo build +//! ``` //! -//! The fallback can be disabled by enabling the `linux_disable_fallback` crate feature. -//! Note that doing so will bump minimum supported Linux kernel version to 3.17 and -//! Android API level to 23 (Marshmallow). +//! Enabling an opt-in backend will replace backend used by default. Doing it for a wrong target +//! (e.g. using `linux_getrandom` while compiling for a Windows target) will result +//! in a compilation error. Be extremely carefull while using opt-in backends, since incorrect +//! configuration may result in vulnerable or in always panicking applications. //! -//! ### RDRAND on x86 +//! Note that using an opt-in backend in a library (e.g. for tests or benchmarks) +//! WILL NOT have any effect on its downstream users. //! -//! *If the `rdrand` Cargo feature is enabled*, `getrandom` will fallback to using -//! the [`RDRAND`] instruction to get randomness on `no_std` `x86`/`x86_64` -//! targets. This feature has no effect on other CPU architectures. +//! [`.cargo/config.toml`]: https://doc.rust-lang.org/cargo/reference/config.html //! //! ### WebAssembly support //! -//! This crate fully supports the -//! [`wasm32-wasi`](https://github.com/CraneStation/wasi) and -//! [`wasm32-unknown-emscripten`](https://www.hellorust.com/setup/emscripten/) -//! targets. However, the `wasm32-unknown-unknown` target (i.e. the target used -//! by `wasm-pack`) is not automatically -//! supported since, from the target name alone, we cannot deduce which -//! JavaScript interface is in use (or if JavaScript is available at all). +//! This crate fully supports the [WASI] and [Emscripten] targets. However, +//! the `wasm32-unknown-unknown` target (i.e. the target used by `wasm-pack`) +//! is not automatically supported since, from the target name alone, we cannot deduce +//! which JavaScript interface should be used (or if JavaScript is available at all). //! -//! Instead, *if the `js` Cargo feature is enabled*, this crate will assume +//! Instead, *if the `wasm_js` backend is enabled*, this crate will assume //! that you are building for an environment containing JavaScript, and will //! call the appropriate methods. Both web browser (main window and Web Workers) //! and Node.js environments are supported, invoking the methods -//! [described above](#supported-targets) using the [`wasm-bindgen`] toolchain. +//! [described above](#opt-in-backends) using the [`wasm-bindgen`] toolchain. //! -//! To enable the `js` Cargo feature, add the following to the `dependencies` -//! section in your `Cargo.toml` file: +//! To enable the `wasm_js` backend, you can add the following lines to your +//! project's `.cargo/config.toml` file: //! ```toml -//! [dependencies] -//! getrandom = { version = "0.2", features = ["js"] } +//! [target.wasm32-unknown-unknown] +//! rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] //! ``` //! -//! This can be done even if `getrandom` is not a direct dependency. Cargo -//! allows crates to enable features for indirect dependencies. -//! -//! This feature should only be enabled for binary, test, or benchmark crates. -//! Library crates should generally not enable this feature, leaving such a -//! decision to *users* of their library. Also, libraries should not introduce -//! their own `js` features *just* to enable `getrandom`'s `js` feature. -//! -//! This feature has no effect on targets other than `wasm32-unknown-unknown`. -//! //! #### Node.js ES module support //! //! Node.js supports both [CommonJS modules] and [ES modules]. Due to @@ -112,6 +96,42 @@ //! ``` //! This crate will then use the provided `webcrypto` implementation. //! +//! ### Custom backend +//! +//! If this crate does not support your target out of box or you have to use +//! a non-default entropy source, then you can provide a custom implementation. +//! You need to enable the custom backend as described in the [configuration flags] +//! section. Next, you need to define an `extern` function with the following +//! signature: +//! +//! ``` +//! #[no_mangle] +//! unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { +//! todo!() +//! } +//! ``` +//! +//! This function ideally should be defined in the root crate of your project, +//! e.g. in your `main.rs`. This function MUST be defined only once for your +//! project, i.e. upstream library crates SHOULD NOT define it outside of +//! tests and benchmarks. Improper configuration of this backend may result +//! in linking errors. +//! +//! The function accepts pointer to buffer which should be filled with random +//! data and length in bytes. Note that the buffer MAY be uninitialized. +//! On success the function should return 0 and fully fill the input buffer, +//! every other return result will be interpreted as an error code. +//! +//! If you are confident that `getrandom` is not used in your project, but +//! it gets pulled nevertheless by one of your dependencies, then you can +//! use the following custom backend which always returns "unsupported" error: +//! ``` +//! #[no_mangle] +//! unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { +//! getrandom::Error::UNSUPPORTED.code().get() +//! } +//! ``` +//! //! ### Platform Support //! This crate generally supports the same operating system and platform versions //! that the Rust standard library does. Additional targets may be supported using @@ -122,17 +142,20 @@ //! `getrandom` may create new patch releases (`0.N.x`) that remove support for //! outdated platform versions. //! -//! ### Custom implementations +//! ## `/dev/urandom` fallback on Linux and Android +//! +//! On Linux targets the fallback is present only if either `target_env` is `musl`, +//! or `target_arch` is one of the following: `aarch64`, `arm`, `powerpc`, `powerpc64`, +//! `s390x`, `x86`, `x86_64`. Other supported targets [require][platform-support] +//! kernel versions which support `getrandom` system call, so fallback is not needed. //! -//! The [`register_custom_getrandom!`] macro allows a user to mark their own -//! function as the backing implementation for [`getrandom`]. See the macro's -//! documentation for more information about writing and registering your own -//! custom implementations. +//! On Android targets the fallback is present only for the following `target_arch`es: +//! `aarch64`, `arm`, `x86`, `x86_64`. Other `target_arch`es (e.g. RISC-V) require +//! sufficiently high API levels. //! -//! Note that registering a custom implementation only has an effect on targets -//! that would otherwise not compile. Any supported targets (including those -//! using `rdrand` and `js` Cargo features) continue using their normal -//! implementations even if a function is registered. +//! The fallback can be disabled by enabling the `linux_getrandom` opt-in backend. +//! Note that doing so will bump minimum supported Linux kernel version to 3.17 and +//! Android API level to 23 (Marshmallow). //! //! ## Early boot //! @@ -195,12 +218,16 @@ //! [`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 //! [WebAssembly support]: #webassembly-support +//! [configuration flags]: #configuration-flags +//! [custom backend]: #custom-backend //! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen //! [`module`]: https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/module.html //! [CommonJS modules]: https://nodejs.org/api/modules.html //! [ES modules]: https://nodejs.org/api/esm.html //! [`sys_read_entropy`]: https://github.com/hermit-os/kernel/blob/315f58ff5efc81d9bf0618af85a59963ff55f8b1/src/syscalls/entropy.rs#L47-L55 //! [platform-support]: https://doc.rust-lang.org/stable/rustc/platform-support.html +//! [WASI]: https://github.com/CraneStation/wasi +//! [Emscripten]: https://www.hellorust.com/setup/emscripten/ #![doc( html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", @@ -214,20 +241,17 @@ #[macro_use] extern crate cfg_if; -use crate::util::{slice_as_uninit_mut, slice_assume_init_mut}; use core::mem::MaybeUninit; mod error; mod lazy; mod util; -// To prevent a breaking change when targets are added, we always export the -// register_custom_getrandom macro, so old Custom RNG crates continue to build. -#[cfg(feature = "custom")] -mod custom; + #[cfg(feature = "std")] mod error_impls; pub use crate::error::Error; +use crate::util::{slice_as_uninit_mut, slice_assume_init_mut}; // System-specific implementations. // @@ -237,7 +261,34 @@ pub use crate::error::Error; // The function MUST NOT ever write uninitialized bytes into `dest`, // regardless of what value it returns. cfg_if! { - if #[cfg(any(target_os = "haiku", target_os = "redox", target_os = "nto", target_os = "aix"))] { + if #[cfg(getrandom_backend = "custom")] { + #[path = "custom.rs"] mod imp; + } else if #[cfg(getrandom_backend = "linux_getrandom")] { + #[cfg(not(any(target_os = "android", target_os = "linux")))] + compile_error!("`linux_getrandom` backend can be enabled only for Linux/Android targets!"); + mod util_libc; + #[path = "linux_android.rs"] mod imp; + } else if #[cfg(getrandom_backend = "rdrand")] { + #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] + compile_error!("`rdrand` backend can be enabled only for x86 and x86-64 targets!"); + #[path = "rdrand.rs"] mod imp; + } else if #[cfg(getrandom_backend = "wasm_js")] { + #[cfg(not(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown", + )))] + compile_error!("`wasm_js` backend can be enabled only on OS-less WASM targets!"); + #[path = "js.rs"] mod imp; + } else if #[cfg(getrandom_backend = "esp_idf")] { + #[cfg(not(target_os = "espidf"))] + compile_error!("`esp_idf` backend can be enabled only for ESP-IDF targets!"); + #[path = "espidf.rs"] mod imp; + } else if #[cfg(any( + target_os = "haiku", + target_os = "redox", + target_os = "nto", + target_os = "aix", + ))] { mod util_libc; #[path = "use_file.rs"] mod imp; } else if #[cfg(any( @@ -259,45 +310,42 @@ cfg_if! { ))] { mod util_libc; #[path = "getrandom.rs"] mod imp; - } else if #[cfg(all( - not(feature = "linux_disable_fallback"), - any( - // Rust supports Android API level 19 (KitKat) [0] and the next upgrade targets - // level 21 (Lollipop) [1], while `getrandom(2)` was added only in - // level 23 (Marshmallow). Note that it applies only to the "old" `target_arch`es, - // RISC-V Android targets sufficiently new API level, same will apply for potential - // new Android `target_arch`es. - // [0]: https://blog.rust-lang.org/2023/01/09/android-ndk-update-r25.html - // [1]: https://github.com/rust-lang/rust/pull/120593 - all( - target_os = "android", - any( - target_arch = "aarch64", - target_arch = "arm", - target_arch = "x86", - target_arch = "x86_64", - ), + } else if #[cfg(any( + // Rust supports Android API level 19 (KitKat) [0] and the next upgrade targets + // level 21 (Lollipop) [1], while `getrandom(2)` was added only in + // level 23 (Marshmallow). Note that it applies only to the "old" `target_arch`es, + // RISC-V Android targets sufficiently new API level, same will apply for potential + // new Android `target_arch`es. + // [0]: https://blog.rust-lang.org/2023/01/09/android-ndk-update-r25.html + // [1]: https://github.com/rust-lang/rust/pull/120593 + all( + target_os = "android", + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "x86", + target_arch = "x86_64", ), - // Only on these `target_arch`es Rust supports Linux kernel versions (3.2+) - // that precede the version (3.17) in which `getrandom(2)` was added: - // https://doc.rust-lang.org/stable/rustc/platform-support.html - all( - target_os = "linux", - any( - target_arch = "aarch64", - target_arch = "arm", - target_arch = "powerpc", - target_arch = "powerpc64", - target_arch = "s390x", - target_arch = "x86", - target_arch = "x86_64", - // Minimum supported Linux kernel version for MUSL targets - // is not specified explicitly (as of Rust 1.77) and they - // are used in practice to target pre-3.17 kernels. - target_env = "musl", - ), - ) ), + // Only on these `target_arch`es Rust supports Linux kernel versions (3.2+) + // that precede the version (3.17) in which `getrandom(2)` was added: + // https://doc.rust-lang.org/stable/rustc/platform-support.html + all( + target_os = "linux", + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + // Minimum supported Linux kernel version for MUSL targets + // is not specified explicitly (as of Rust 1.77) and they + // are used in practice to target pre-3.17 kernels. + target_env = "musl", + ), + ) ))] { mod util_libc; mod use_file; @@ -314,7 +362,12 @@ cfg_if! { #[path = "netbsd.rs"] mod imp; } else if #[cfg(target_os = "fuchsia")] { #[path = "fuchsia.rs"] mod imp; - } else if #[cfg(any(target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"))] { + } else if #[cfg(any( + target_os = "ios", + target_os = "visionos", + target_os = "watchos", + target_os = "tvos", + ))] { #[path = "apple-other.rs"] mod imp; } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { #[path = "wasi.rs"] mod imp; @@ -325,32 +378,24 @@ cfg_if! { #[path = "vxworks.rs"] mod imp; } else if #[cfg(target_os = "solid_asp3")] { #[path = "solid.rs"] mod imp; - } else if #[cfg(target_os = "espidf")] { - #[path = "espidf.rs"] mod imp; } else if #[cfg(all(windows, target_vendor = "win7"))] { #[path = "windows7.rs"] mod imp; } else if #[cfg(windows)] { #[path = "windows.rs"] mod imp; } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { #[path = "rdrand.rs"] mod imp; - } else if #[cfg(all(feature = "rdrand", - any(target_arch = "x86_64", target_arch = "x86")))] { - #[path = "rdrand.rs"] mod imp; - } else if #[cfg(all(feature = "js", - any(target_arch = "wasm32", target_arch = "wasm64"), - target_os = "unknown"))] { - #[path = "js.rs"] mod imp; - } else if #[cfg(feature = "custom")] { - use custom as imp; - } else if #[cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), - target_os = "unknown"))] { + } else if #[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown", + ))] { compile_error!("the wasm*-unknown-unknown targets are not supported by \ - default, you may need to enable the \"js\" feature. \ - For more information see: \ + default, you may need to enable the \"wasm_js\" \ + configuration flag. For more information see: \ https://docs.rs/getrandom/#webassembly-support"); } else { - compile_error!("target is not supported, for more information see: \ - https://docs.rs/getrandom/#unsupported-targets"); + compile_error!("target is not supported. You may need to define \ + a custom backend see: \ + https://docs.rs/getrandom/#custom-backends"); } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs deleted file mode 100644 index 666f7f57..00000000 --- a/tests/common/mod.rs +++ /dev/null @@ -1,100 +0,0 @@ -use super::getrandom_impl; - -#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -use wasm_bindgen_test::wasm_bindgen_test as test; - -#[cfg(feature = "test-in-browser")] -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -fn test_zero() { - // Test that APIs are happy with zero-length requests - getrandom_impl(&mut [0u8; 0]).unwrap(); -} - -// Return the number of bits in which s1 and s2 differ -#[cfg(not(feature = "custom"))] -fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { - assert_eq!(s1.len(), s2.len()); - s1.iter() - .zip(s2.iter()) - .map(|(a, b)| (a ^ b).count_ones() as usize) - .sum() -} - -// Tests the quality of calling getrandom on two large buffers -#[test] -#[cfg(not(feature = "custom"))] -fn test_diff() { - let mut v1 = [0u8; 1000]; - getrandom_impl(&mut v1).unwrap(); - - let mut v2 = [0u8; 1000]; - getrandom_impl(&mut v2).unwrap(); - - // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: - // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] - let d = num_diff_bits(&v1, &v2); - assert!(d > 3500); - assert!(d < 4500); -} - -// Tests the quality of calling getrandom repeatedly on small buffers -#[test] -#[cfg(not(feature = "custom"))] -fn test_small() { - // For each buffer size, get at least 256 bytes and check that between - // 3 and 5 bits per byte differ. Probability of failure: - // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] - for size in 1..=64 { - let mut num_bytes = 0; - let mut diff_bits = 0; - while num_bytes < 256 { - let mut s1 = vec![0u8; size]; - getrandom_impl(&mut s1).unwrap(); - let mut s2 = vec![0u8; size]; - getrandom_impl(&mut s2).unwrap(); - - num_bytes += size; - diff_bits += num_diff_bits(&s1, &s2); - } - assert!(diff_bits > 3 * num_bytes); - assert!(diff_bits < 5 * num_bytes); - } -} - -#[test] -fn test_huge() { - let mut huge = [0u8; 100_000]; - getrandom_impl(&mut huge).unwrap(); -} - -// On WASM, the thread API always fails/panics -#[cfg(not(target_arch = "wasm32"))] -#[test] -fn test_multithreading() { - extern crate std; - use std::{sync::mpsc::channel, thread, vec}; - - let mut txs = vec![]; - for _ in 0..20 { - let (tx, rx) = channel(); - txs.push(tx); - - thread::spawn(move || { - // wait until all the tasks are ready to go. - rx.recv().unwrap(); - let mut v = [0u8; 1000]; - - for _ in 0..100 { - getrandom_impl(&mut v).unwrap(); - thread::yield_now(); - } - }); - } - - // start all the tasks - for tx in txs.iter() { - tx.send(()).unwrap(); - } -} diff --git a/tests/custom.rs b/tests/custom.rs deleted file mode 100644 index 30934e94..00000000 --- a/tests/custom.rs +++ /dev/null @@ -1,52 +0,0 @@ -use core::num::NonZeroU32; -use getrandom::{getrandom, register_custom_getrandom, Error}; -#[cfg(all(target_family = "wasm", target_os = "unknown"))] -use wasm_bindgen_test::wasm_bindgen_test as test; - -const LEN7_CODE: u32 = Error::CUSTOM_START + 7; -// Returns a custom error if input is length 7, otherwise fills with 0x55. -fn mock_rng(buf: &mut [u8]) -> Result<(), Error> { - // `getrandom` guarantees it will not call any implementation if the output - // buffer is empty. - assert!(!buf.is_empty()); - if buf.len() == 7 { - return Err(NonZeroU32::new(LEN7_CODE).unwrap().into()); - } - buf.fill(0x55); - Ok(()) -} - -// Test registering a custom implementation, even on supported platforms. -register_custom_getrandom!(mock_rng); - -// Invoking with an empty buffer should never call the custom implementation. -#[test] -fn custom_empty() { - getrandom(&mut []).unwrap(); -} - -// On a supported platform, make sure the custom implementation isn't used. We -// test on a few common platfroms, rather than duplicating the lib.rs logic. -#[cfg(any( - target_os = "linux", - target_os = "windows", - target_os = "macos", - target_os = "espidf", - target_os = "wasi", - all(target_family = "wasm", target_os = "unknown", feature = "js"), -))] -#[test] -fn custom_not_used() { - getrandom(&mut [0; 7]).unwrap(); -} -// On an unsupported platform, make sure the custom implementation is used. -#[cfg(all(target_family = "wasm", target_os = "unknown", not(feature = "js")))] -#[test] -fn custom_used() { - let err = getrandom(&mut [0; 7]).unwrap_err(); - assert_eq!(err.code().get(), LEN7_CODE); - - let mut buf = [0; 12]; - getrandom(&mut buf).unwrap(); - assert_eq!(buf, [0x55; 12]); -} diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 00000000..6738c0e1 --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1,182 @@ +use getrandom::getrandom; + +#[cfg(getrandom_browser_test)] +use wasm_bindgen_test::wasm_bindgen_test as test; +#[cfg(getrandom_browser_test)] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[test] +fn test_zero() { + // Test that APIs are happy with zero-length requests + getrandom(&mut [0u8; 0]).unwrap(); +} + +// Return the number of bits in which s1 and s2 differ +fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { + assert_eq!(s1.len(), s2.len()); + s1.iter() + .zip(s2.iter()) + .map(|(a, b)| (a ^ b).count_ones() as usize) + .sum() +} + +// Tests the quality of calling getrandom on two large buffers +#[test] +fn test_diff() { + let mut v1 = [0u8; 1000]; + getrandom(&mut v1).unwrap(); + + let mut v2 = [0u8; 1000]; + getrandom(&mut v2).unwrap(); + + // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: + // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] + let d = num_diff_bits(&v1, &v2); + assert!(d > 3500); + assert!(d < 4500); +} + +// Tests the quality of calling getrandom repeatedly on small buffers +#[test] +fn test_small() { + let mut buf1 = [0u8; 64]; + let mut buf2 = [0u8; 64]; + // For each buffer size, get at least 256 bytes and check that between + // 3 and 5 bits per byte differ. Probability of failure: + // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] + for size in 1..=64 { + let mut num_bytes = 0; + let mut diff_bits = 0; + while num_bytes < 256 { + let s1 = &mut buf1[..size]; + let s2 = &mut buf2[..size]; + + getrandom(s1).unwrap(); + getrandom(s2).unwrap(); + + num_bytes += size; + diff_bits += num_diff_bits(s1, s2); + } + assert!(diff_bits > 3 * num_bytes); + assert!(diff_bits < 5 * num_bytes); + } +} + +#[test] +fn test_huge() { + let mut huge = [0u8; 100_000]; + getrandom(&mut huge).unwrap(); +} + +#[test] +#[cfg_attr( + target_arch = "wasm32", + ignore = "The thread API always fails/panics on WASM" +)] +fn test_multithreading() { + extern crate std; + use std::{sync::mpsc::channel, thread, vec}; + + let mut txs = vec![]; + for _ in 0..20 { + let (tx, rx) = channel(); + txs.push(tx); + + thread::spawn(move || { + // wait until all the tasks are ready to go. + rx.recv().unwrap(); + let mut v = [0u8; 1000]; + + for _ in 0..100 { + getrandom(&mut v).unwrap(); + thread::yield_now(); + } + }); + } + + // start all the tasks + for tx in txs.iter() { + tx.send(()).unwrap(); + } +} + +#[cfg(getrandom_backend = "custom")] +mod custom { + struct Xoshiro128PlusPlus { + s: [u32; 4], + } + + impl Xoshiro128PlusPlus { + fn new(mut seed: u64) -> Self { + const PHI: u64 = 0x9e3779b97f4a7c15; + let mut s = [0u32; 4]; + for val in s.iter_mut() { + seed = seed.wrapping_add(PHI); + let mut z = seed; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); + z = z ^ (z >> 31); + *val = z as u32; + } + Self { s } + } + + fn next_u32(&mut self) -> u32 { + let res = self.s[0] + .wrapping_add(self.s[3]) + .rotate_left(7) + .wrapping_add(self.s[0]); + + let t = self.s[1] << 9; + + self.s[2] ^= self.s[0]; + self.s[3] ^= self.s[1]; + self.s[1] ^= self.s[2]; + self.s[0] ^= self.s[3]; + + self.s[2] ^= t; + + self.s[3] = self.s[3].rotate_left(11); + + res + } + } + + // This implementation uses current timestamp as a PRNG seed. + // + // WARNING: this custom implementation is for testing purposes ONLY! + #[no_mangle] + unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { + use std::time::{SystemTime, UNIX_EPOCH}; + + assert_ne!(len, 0); + + if len == 142 { + return getrandom::Error::CUSTOM_START + 142; + } + + let dest_u32 = dest.cast::(); + let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mut rng = Xoshiro128PlusPlus::new(ts.as_nanos() as u64); + for i in 0..len / 4 { + let val = rng.next_u32(); + core::ptr::write_unaligned(dest_u32.add(i), val); + } + if len % 4 != 0 { + let start = 4 * (len / 4); + for i in start..len { + let val = rng.next_u32(); + core::ptr::write_unaligned(dest.add(i), val as u8); + } + } + 0 + } + + // Test that enabling the custom feature indeed uses the custom implementation + #[test] + fn test_custom() { + let mut buf = [0u8; 142]; + let res = getrandom::getrandom(&mut buf); + assert!(res.is_err()); + } +} diff --git a/tests/normal.rs b/tests/normal.rs deleted file mode 100644 index 5fff13b3..00000000 --- a/tests/normal.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Don't test on custom wasm32-unknown-unknown -#![cfg(not(all( - target_arch = "wasm32", - target_os = "unknown", - feature = "custom", - not(feature = "js") -)))] - -// Use the normal getrandom implementation on this architecture. -use getrandom::getrandom as getrandom_impl; -mod common; diff --git a/tests/rdrand.rs b/tests/rdrand.rs deleted file mode 100644 index a355c31e..00000000 --- a/tests/rdrand.rs +++ /dev/null @@ -1,22 +0,0 @@ -// We only test the RDRAND-based RNG source on supported architectures. -#![cfg(any(target_arch = "x86_64", target_arch = "x86"))] - -// rdrand.rs expects to be part of the getrandom main crate, so we need these -// additional imports to get rdrand.rs to compile. -use getrandom::Error; -#[macro_use] -extern crate cfg_if; -#[path = "../src/lazy.rs"] -mod lazy; -#[path = "../src/rdrand.rs"] -mod rdrand; -#[path = "../src/util.rs"] -mod util; - -// The rdrand implementation has the signature of getrandom_uninit(), but our -// tests expect getrandom_impl() to have the signature of getrandom(). -fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> { - rdrand::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?; - Ok(()) -} -mod common;