diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 53144945e..974f8e257 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,14 +27,14 @@ jobs: targets: aarch64-apple-darwin, aarch64-apple-ios components: rust-src - uses: Swatinem/rust-cache@v2 - - run: cargo test --no-run --target=aarch64-apple-darwin --features=std - - run: cargo test --no-run --target=aarch64-apple-ios --features=std - - run: cargo test --no-run --target=aarch64-apple-tvos -Zbuild-std --features=std - - run: cargo test --no-run --target=aarch64-apple-watchos -Zbuild-std --features=std + - run: cargo test --no-run --target=aarch64-apple-darwin --features=std,default-backends + - run: cargo test --no-run --target=aarch64-apple-ios --features=std,default-backends + - run: cargo test --no-run --target=aarch64-apple-tvos -Zbuild-std --features=std,default-backends + - run: cargo test --no-run --target=aarch64-apple-watchos -Zbuild-std --features=std,default-backends # visionOS requires Xcode 15.2+, GitHub Actions defaults to an older version. - run: sudo xcode-select -switch /Applications/Xcode_15.2.app # std is broken on visionOS right now - #- run: cargo test --no-run --target=aarch64-apple-visionos -Zbuild-std --features=std + #- run: cargo test --no-run --target=aarch64-apple-visionos -Zbuild-std --features=std,default-backends cross: name: Cross @@ -56,7 +56,7 @@ jobs: wget -O - $URL | tar -xz -C ~/.cargo/bin cross --version - name: Build Tests - run: cross test --no-run --target=${{ matrix.target }} --features=std + run: cross test --no-run --target=${{ matrix.target }} --features=std,default-backends tier2: name: Tier 2 @@ -76,7 +76,7 @@ jobs: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - name: Build - run: cargo build --target=${{ matrix.target }} --features=std + run: cargo build --target=${{ matrix.target }} --features=std,default-backends tier3: name: Tier 3 @@ -105,7 +105,7 @@ jobs: with: components: rust-src - uses: Swatinem/rust-cache@v2 - - run: cargo build -Z build-std=core --target=${{ matrix.target }} + - run: cargo build -Z build-std=core --target=${{ matrix.target }} --features default-backends # Ubuntu does not support running x32 binaries: # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1994516/comments/21 @@ -125,16 +125,16 @@ jobs: sudo apt-get update sudo apt-get install --no-install-recommends libc6-dev-x32 libx32gcc-11-dev - uses: Swatinem/rust-cache@v2 - - run: cargo build --target=${{ matrix.target }} --features=std + - run: cargo build --target=${{ matrix.target }} --features=std,default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" - run: cargo build --target=${{ matrix.target }} --features=std + run: cargo build --target=${{ matrix.target }} --features=std,default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback - run: cargo build --features=std + run: cargo build --features=std,default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" - run: cargo build --features=std + run: cargo build --features=std,default-backends linux-raw: name: Build Raw Linux @@ -160,7 +160,7 @@ jobs: components: rust-src - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" - run: cargo build -Zbuild-std=core --target=${{ matrix.target }} + run: cargo build -Zbuild-std=core --target=${{ matrix.target }} --features default-backends web: name: ${{ matrix.target.description }} ${{ matrix.feature.description }} ${{ matrix.atomic.description }} @@ -194,7 +194,7 @@ jobs: components: rust-src - uses: Swatinem/rust-cache@v2 - name: Build - run: cargo build --target ${{ matrix.target.target }} ${{ matrix.feature.feature }} -Zbuild-std=${{ matrix.feature.build-std }} + run: cargo build --target ${{ matrix.target.target }} ${{ matrix.feature.feature }} -Zbuild-std=${{ matrix.feature.build-std }} --features default-backends efi-rng: name: UEFI RNG Protocol @@ -214,7 +214,7 @@ jobs: - 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 + run: cargo build -Z build-std=std --target=${{ matrix.target }} --features std,default-backends rdrand-uefi: name: RDRAND UEFI @@ -233,10 +233,10 @@ jobs: - uses: Swatinem/rust-cache@v2 - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" - run: cargo build -Z build-std=core --target=${{ matrix.target }} + run: cargo build -Z build-std=core --target=${{ matrix.target }} --features default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" - run: cargo build -Z build-std=std --target=${{ matrix.target }} --features std + run: cargo build -Z build-std=std --target=${{ matrix.target }} --features std,default-backends rndr: name: RNDR @@ -251,15 +251,15 @@ jobs: - name: RNDR enabled at compile time (Linux) env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" -C target-feature=+rand - run: cargo build --target=aarch64-unknown-linux-gnu + run: cargo build --target=aarch64-unknown-linux-gnu --features default-backends - name: Runtime RNDR detection without std (Linux) env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" - run: cargo build --target=aarch64-unknown-linux-gnu + run: cargo build --target=aarch64-unknown-linux-gnu --features default-backends - name: Runtime RNDR detection with std (macOS) env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" - run: cargo build --target=aarch64-unknown-linux-gnu --features std + run: cargo build --target=aarch64-unknown-linux-gnu --features std,default-backends no-atomics: name: No Atomics @@ -272,7 +272,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="custom" - run: cargo build --target riscv32i-unknown-none-elf + run: cargo build --target riscv32i-unknown-none-elf --features default-backends unsupported: name: Runtime error @@ -285,4 +285,4 @@ jobs: - uses: Swatinem/rust-cache@v2 - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="unsupported" - run: cargo build --target wasm32-unknown-unknown + run: cargo build --target wasm32-unknown-unknown --features default-backends diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8132b61eb..06513689d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,11 +35,11 @@ jobs: with: toolchain: ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - - run: cargo test + - run: cargo test --features default-backends # Make sure enabling the std feature doesn't break anything - - run: cargo test --features=std + - run: cargo test --features=std,default-backends - if: ${{ matrix.toolchain == 'nightly' }} - run: cargo test --benches + run: cargo test --benches --features default-backends linux: name: Linux @@ -53,27 +53,27 @@ jobs: with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - - run: cargo test --target=${{ matrix.target }} --features=std + - run: cargo test --target=${{ matrix.target }} --features=std,default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" - run: cargo test --target=${{ matrix.target }} --features=std + run: cargo test --target=${{ matrix.target }} --features=std,default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" - run: cargo test --target=${{ matrix.target }} --features=std + run: cargo test --target=${{ matrix.target }} --features=std,default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback RUSTDOCFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback - run: cargo test --features=std + run: cargo test --features=std,default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_without_fallback RUSTDOCFLAGS: -Dwarnings --cfg getrandom_test_linux_without_fallback - run: cargo test --features=std + run: cargo test --features=std,default-backends - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" - run: cargo test --features=std + run: cargo test --features=std,default-backends ios: name: iOS Simulator @@ -105,7 +105,7 @@ jobs: echo "device=$SIM_ID" >> $GITHUB_ENV - uses: Swatinem/rust-cache@v2 - name: Run tests - run: cargo dinghy -p auto-ios-aarch64-sim -d ${{ env.device }} test + run: cargo dinghy -p auto-ios-aarch64-sim -d ${{ env.device }} test --features default-backends windows: name: Windows @@ -123,7 +123,7 @@ jobs: with: toolchain: ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - - run: cargo test --features=std + - run: cargo test --features=std,default-backends windows7: name: Windows 7 (on Windows 10) @@ -136,8 +136,8 @@ jobs: toolchain: nightly-2024-05-20 components: rust-src - uses: Swatinem/rust-cache@v2 - - run: cargo test --target=x86_64-win7-windows-msvc -Z build-std --features=std - - run: cargo test --target=i686-win7-windows-msvc -Z build-std --features=std + - run: cargo test --target=x86_64-win7-windows-msvc -Z build-std --features=std,default-backends + - run: cargo test --target=i686-win7-windows-msvc -Z build-std --features=std,default-backends sanitizer: name: Sanitizer @@ -151,7 +151,7 @@ jobs: - env: RUSTFLAGS: -Dwarnings -Zsanitizer=memory RUSTDOCFLAGS: -Dwarnings -Zsanitizer=memory - run: cargo test -Zbuild-std --target=x86_64-unknown-linux-gnu + run: cargo test -Zbuild-std --target=x86_64-unknown-linux-gnu --features default-backends cross: name: Cross @@ -180,7 +180,7 @@ jobs: wget -O - $URL | tar -xz -C ~/.cargo/bin cross --version - name: Test - run: cross test --no-fail-fast --target=${{ matrix.target }} --features=std + run: cross test --no-fail-fast --target=${{ matrix.target }} --features=std,default-backends freebsd: name: FreeBSD VM @@ -194,7 +194,7 @@ jobs: usesh: true prepare: | pkg install -y rust - run: cargo test + run: cargo test --features default-backends openbsd: name: OpenBSD VM @@ -208,7 +208,7 @@ jobs: usesh: true prepare: | pkg_add rust - run: cargo test + run: cargo test --features default-backends netbsd: name: NetBSD VM @@ -223,8 +223,8 @@ jobs: prepare: | /usr/sbin/pkg_add rust run: | - cargo test - RUSTFLAGS="--cfg getrandom_test_netbsd_fallback -D warnings" cargo test + cargo test --features default-backends + RUSTFLAGS="--cfg getrandom_test_netbsd_fallback -D warnings" cargo test --features default-backends web: name: ${{ matrix.rust.description }} @@ -237,14 +237,14 @@ jobs: description: Web, version: stable, flags: '-Dwarnings --cfg getrandom_backend="wasm_js"', - args: '--features=std,wasm_js', + args: '--features=std,wasm_js,default-backends', } - { description: Web with Atomics, version: nightly, components: rust-src, flags: '-Dwarnings --cfg getrandom_backend="wasm_js" -Ctarget-feature=+atomics,+bulk-memory', - args: '--features=std,wasm_js -Zbuild-std=panic_abort,std', + args: '--features=std,wasm_js,default-backends -Zbuild-std=panic_abort,std', } steps: - uses: actions/checkout@v4 @@ -314,6 +314,6 @@ jobs: wasmtime --version - uses: Swatinem/rust-cache@v2 - name: WASI 0.1 Test - run: cargo test --target wasm32-wasip1 + run: cargo test --target wasm32-wasip1 --features default-backends - name: WASI 0.2 Test - run: cargo test --target wasm32-wasip2 + run: cargo test --target wasm32-wasip2 --features default-backends diff --git a/Cargo.lock b/Cargo.lock index 565926899..ef58a23c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ checksum = "448068da8f2326b2a0472353cb401dd8795a89c007ef30fff90f50706e862e72" [[package]] name = "getrandom" -version = "0.3.3" +version = "0.4.0" dependencies = [ "cfg-if", "compiler_builtins", diff --git a/Cargo.toml b/Cargo.toml index 4a982ee7c..d96aea037 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "getrandom" -version = "0.3.3" +version = "0.4.0" edition = "2021" rust-version = "1.63" # Sync tests.yml and README.md. authors = ["The Rand Project Developers"] @@ -18,12 +18,14 @@ std = [] # Unstable feature to support being a libstd dependency rustc-dep-of-std = ["dep:compiler_builtins", "dep:core"] +# Provides a default implementations of the backend for many supported targets. +default-backends = ["dep:libc", "dep:r-efi", "dep:wasi"] # Optional backend: wasm_js # This flag enables the backend but does not select it. To use the backend, use # this flag *and* set getrandom_backend=wasm_js (see README). # WARNING: It is highly recommended to enable this feature only for binary crates and tests, # i.e. avoid unconditionally enabling it in library crates. -wasm_js = ["dep:wasm-bindgen", "dep:js-sys"] +wasm_js = ["dep:wasm-bindgen", "dep:js-sys", "default-backends"] [dependencies] cfg-if = "1" @@ -34,43 +36,43 @@ core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" # getrandom / linux_android_with_fallback [target.'cfg(all(any(target_os = "linux", target_os = "android"), not(any(all(target_os = "linux", target_env = ""), getrandom_backend = "custom", getrandom_backend = "linux_raw", getrandom_backend = "rdrand", getrandom_backend = "rndr"))))'.dependencies] -libc = { version = "0.2.154", default-features = false } +libc = { version = "0.2.154", default-features = false, optional = true } # 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 } +libc = { version = "0.2.154", default-features = false, optional = true } # efi_rng [target.'cfg(all(target_os = "uefi", getrandom_backend = "efi_rng"))'.dependencies] -r-efi = { version = "5.1", default-features = false } +r-efi = { version = "5.1", default-features = false, optional = true } # getentropy [target.'cfg(any(target_os = "macos", target_os = "openbsd", target_os = "vita", target_os = "emscripten"))'.dependencies] -libc = { version = "0.2.154", default-features = false } +libc = { version = "0.2.154", default-features = false, optional = true } # getrandom [target.'cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "hurd", target_os = "illumos", target_os = "cygwin", all(target_os = "horizon", target_arch = "arm")))'.dependencies] -libc = { version = "0.2.154", default-features = false } +libc = { version = "0.2.154", default-features = false, optional = true } # netbsd [target.'cfg(target_os = "netbsd")'.dependencies] -libc = { version = "0.2.154", default-features = false } +libc = { version = "0.2.154", default-features = false, optional = true } # solaris [target.'cfg(target_os = "solaris")'.dependencies] -libc = { version = "0.2.154", default-features = false } +libc = { version = "0.2.154", default-features = false, optional = true } # use_file [target.'cfg(any(target_os = "haiku", target_os = "redox", target_os = "nto", target_os = "aix"))'.dependencies] -libc = { version = "0.2.154", default-features = false } +libc = { version = "0.2.154", default-features = false, optional = true } # vxworks [target.'cfg(target_os = "vxworks")'.dependencies] -libc = { version = "0.2.154", default-features = false } +libc = { version = "0.2.154", default-features = false, optional = true } # wasi (0.2 only) [target.'cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))'.dependencies] -wasi = { version = "0.14", default-features = false } +wasi = { version = "0.14", default-features = false, optional = true } # wasm_js [target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dependencies] diff --git a/nopanic_check/Cargo.toml b/nopanic_check/Cargo.toml index 0b838eaa2..7424df601 100644 --- a/nopanic_check/Cargo.toml +++ b/nopanic_check/Cargo.toml @@ -12,7 +12,7 @@ name = "getrandom_wrapper" crate-type = ["cdylib"] [dependencies] -getrandom = { path = ".." } +getrandom = { path = "..", features = ["default-backends"]} [profile.release] panic = "abort" diff --git a/src/backend.rs b/src/backend.rs new file mode 100644 index 000000000..128a4b825 --- /dev/null +++ b/src/backend.rs @@ -0,0 +1,180 @@ +//! An implementation which calls out to an externally defined function. +use crate::Error; +use core::{mem::MaybeUninit, slice}; + +/// Uses the provided [`Backend`]. +/// This macro must be called exactly once, otherwise the final linking will fail due to either +/// duplicated or missing symbols. +#[macro_export] +macro_rules! set_backend { + ($t: ty) => { + const _: () = { + #[no_mangle] + unsafe fn __getrandom_v04_backend_fill_ptr( + dest: *mut u8, + len: usize, + ) -> Result<(), $crate::Error> { + <$t as $crate::Backend>::fill_ptr(dest, len) + } + + #[no_mangle] + unsafe fn __getrandom_v04_backend_fill_uninit( + dest: &mut [core::mem::MaybeUninit], + ) -> Result<(), $crate::Error> { + <$t as $crate::Backend>::fill_uninit(dest) + } + + #[no_mangle] + unsafe fn __getrandom_v04_backend_fill(dest: &mut [u8]) -> Result<(), $crate::Error> { + <$t as $crate::Backend>::fill(dest) + } + + #[no_mangle] + unsafe fn __getrandom_v04_backend_u32() -> Result { + <$t as $crate::Backend>::u32() + } + + #[no_mangle] + unsafe fn __getrandom_v04_backend_u64() -> Result { + <$t as $crate::Backend>::u64() + } + + #[no_mangle] + unsafe fn __getrandom_v04_backend_describe_custom_error( + n: u16, + ) -> Option<&'static str> { + <$t as $crate::Backend>::describe_custom_error(n) + } + }; + }; +} + +/// Describes how `getrandom` can collect random values from a particular backend. +/// +/// Implementers can pair this with [`set_backend`] to always use their [`Backend`], +/// or allow users to call it themselves if that's more appropriate. +/// +/// # Safety +/// +/// The implementation of this trait must produce sufficiently randomized values. +pub unsafe trait Backend { + /// Writes `len` random values starting at `dest`. + /// + /// # Safety + /// + /// - `dest` must be a valid pointer at least `len` bytes long. + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error>; + + /// Fill a slice of [`MaybeUninit`] bytes with random values. + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let ptr = dest.as_mut_ptr().cast(); + let len = dest.len(); + + // SAFETY: `ptr` is valid and exactly `len` bytes in size + unsafe { Self::fill_ptr(ptr, len) } + } + + /// Fill a slice of bytes with random values. + #[inline] + fn fill(dest: &mut [u8]) -> Result<(), Error> { + // SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, + // and `fill_uninit` guarantees it will never de-initialize + // any part of `dest`. + Self::fill_uninit(unsafe { crate::util::slice_as_uninit_mut(dest) })?; + Ok(()) + } + + /// Generates a single random [`u32`] value. + #[inline] + fn u32() -> Result { + let mut res = MaybeUninit::::uninit(); + // SAFETY: the created slice has the same size as `res` + let dst = unsafe { + let p: *mut MaybeUninit = res.as_mut_ptr().cast(); + slice::from_raw_parts_mut(p, core::mem::size_of::()) + }; + Self::fill_uninit(dst)?; + // SAFETY: `dst` has been fully initialized by `imp::fill_inner` + // since it returned `Ok`. + Ok(unsafe { res.assume_init() }) + } + + /// Generates a single random [`u64`] value. + #[inline] + fn u64() -> Result { + let mut res = MaybeUninit::::uninit(); + // SAFETY: the created slice has the same size as `res` + let dst = unsafe { + let p: *mut MaybeUninit = res.as_mut_ptr().cast(); + slice::from_raw_parts_mut(p, core::mem::size_of::()) + }; + Self::fill_uninit(dst)?; + // SAFETY: `dst` has been fully initialized by `imp::fill_inner` + // since it returned `Ok`. + Ok(unsafe { res.assume_init() }) + } + + /// Describes a custom [`Error`] code reported by this [`Backend`]. + #[inline] + #[allow(unused_variables)] + fn describe_custom_error(n: u16) -> Option<&'static str> { + None + } +} + +/// An implementation of [`Backend`] that relies on some other external implementation, paired +/// with a call to [`set_backend`]. +pub(crate) struct ExternBackend; + +unsafe impl Backend for ExternBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + extern "Rust" { + fn __getrandom_v04_backend_fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error>; + } + __getrandom_v04_backend_fill_ptr(dest, len) + } + + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + extern "Rust" { + fn __getrandom_v04_backend_fill_uninit( + dest: &mut [MaybeUninit], + ) -> Result<(), Error>; + } + unsafe { __getrandom_v04_backend_fill_uninit(dest) } + } + + #[inline] + fn fill(dest: &mut [u8]) -> Result<(), Error> { + extern "Rust" { + fn __getrandom_v04_backend_fill(dest: &mut [u8]) -> Result<(), Error>; + } + unsafe { __getrandom_v04_backend_fill(dest) } + } + + #[inline] + fn u32() -> Result { + extern "Rust" { + fn __getrandom_v04_backend_u32() -> Result; + } + unsafe { __getrandom_v04_backend_u32() } + } + + #[inline] + fn u64() -> Result { + extern "Rust" { + fn __getrandom_v04_backend_u64() -> Result; + } + unsafe { __getrandom_v04_backend_u64() } + } + + #[inline] + fn describe_custom_error(n: u16) -> Option<&'static str> { + extern "Rust" { + fn __getrandom_v04_backend_describe_custom_error(n: u16) -> Option<&'static str>; + } + unsafe { __getrandom_v04_backend_describe_custom_error(n) } + } +} diff --git a/src/backends.rs b/src/backends.rs index dbe934565..fed093660 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -9,44 +9,34 @@ cfg_if! { if #[cfg(getrandom_backend = "custom")] { mod custom; - pub use custom::*; + crate::set_backend!(custom::LegacyCustomBackend); } else if #[cfg(getrandom_backend = "linux_getrandom")] { mod getrandom; - pub use getrandom::*; + crate::set_backend!(getrandom::GetrandomBackend); } else if #[cfg(getrandom_backend = "linux_raw")] { mod linux_raw; - pub use linux_raw::*; + crate::set_backend!(linux_raw::LinuxRawBackend); } else if #[cfg(getrandom_backend = "rdrand")] { mod rdrand; - pub use rdrand::*; + crate::set_backend!(rdrand::RdrandBackend); } else if #[cfg(getrandom_backend = "rndr")] { mod rndr; - pub use rndr::*; + crate::set_backend!(rndr::RndrBackend); } else if #[cfg(getrandom_backend = "efi_rng")] { mod efi_rng; - pub use efi_rng::*; - } else if #[cfg(all(getrandom_backend = "wasm_js"))] { - cfg_if! { - if #[cfg(feature = "wasm_js")] { - mod wasm_js; - pub use wasm_js::*; - } else { - compile_error!(concat!( - "The \"wasm_js\" backend requires the `wasm_js` feature \ - for `getrandom`. For more information see: \ - https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#webassembly-support" - )); - } - } + crate::set_backend!(efi_rng::UefiBackend); + } else if #[cfg(all(getrandom_backend = "wasm_js", feature = "wasm_js"))] { + mod wasm_js; + crate::set_backend!(wasm_js::WasmJsBackend); } else if #[cfg(getrandom_backend = "unsupported")] { mod unsupported; - pub use unsupported::*; + crate::set_backend!(unsupported::UnsupportedBackend); } else if #[cfg(all(target_os = "linux", target_env = ""))] { mod linux_raw; - pub use linux_raw::*; + crate::set_backend!(linux_raw::LinuxRawBackend); } else if #[cfg(target_os = "espidf")] { mod esp_idf; - pub use esp_idf::*; + crate::set_backend!(esp_idf::EspIdfBackend); } else if #[cfg(any( target_os = "haiku", target_os = "redox", @@ -54,7 +44,7 @@ cfg_if! { target_os = "aix", ))] { mod use_file; - pub use use_file::*; + crate::set_backend!(use_file::UseFileBackend); } else if #[cfg(any( target_os = "macos", target_os = "openbsd", @@ -62,7 +52,7 @@ cfg_if! { target_os = "emscripten", ))] { mod getentropy; - pub use getentropy::*; + crate::set_backend!(getentropy::GetentropyBackend); } 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 @@ -102,7 +92,7 @@ cfg_if! { ))] { mod use_file; mod linux_android_with_fallback; - pub use linux_android_with_fallback::*; + crate::set_backend!(linux_android_with_fallback::LinuxBackend); } else if #[cfg(any( target_os = "android", target_os = "linux", @@ -116,16 +106,16 @@ cfg_if! { all(target_os = "horizon", target_arch = "arm"), ))] { mod getrandom; - pub use getrandom::*; + crate::set_backend!(getrandom::GetrandomBackend); } else if #[cfg(target_os = "solaris")] { mod solaris; - pub use solaris::*; + crate::set_backend!(solaris::SolarisBackend); } else if #[cfg(target_os = "netbsd")] { mod netbsd; - pub use netbsd::*; + crate::set_backend!(netbsd::NetBsdBackend); } else if #[cfg(target_os = "fuchsia")] { mod fuchsia; - pub use fuchsia::*; + crate::set_backend!(fuchsia::FuchsiaBackend); } else if #[cfg(any( target_os = "ios", target_os = "visionos", @@ -133,48 +123,35 @@ cfg_if! { target_os = "tvos", ))] { mod apple_other; - pub use apple_other::*; + crate::set_backend!(apple_other::AppleOtherBackend); } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { cfg_if! { if #[cfg(target_env = "p1")] { mod wasi_p1; - pub use wasi_p1::*; + crate::set_backend!(wasi_p1::WasiP1Backend); } else if #[cfg(target_env = "p2")] { mod wasi_p2; - pub use wasi_p2::*; - } else { - compile_error!( - "Unknown version of WASI (only previews 1 and 2 are supported) \ - or Rust version older than 1.80 was used" - ); + crate::set_backend!(wasi_p2::WasiP2Backend); } } } else if #[cfg(target_os = "hermit")] { mod hermit; - pub use hermit::*; + crate::set_backend!(hermit::HermitBackend); } else if #[cfg(target_os = "vxworks")] { mod vxworks; - pub use vxworks::*; + crate::set_backend!(vxworks::VxWorksBackend); } else if #[cfg(target_os = "solid_asp3")] { mod solid; - pub use solid::*; + crate::set_backend!(solid::SolidBackend); } else if #[cfg(all(windows, any(target_vendor = "win7", getrandom_windows_legacy)))] { mod windows7; - pub use windows7::*; + crate::set_backend!(windows7::WindowsLegacyBackend); } else if #[cfg(windows)] { mod windows; - pub use windows::*; + crate::set_backend!(windows::WindowsBackend); } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { mod rdrand; - pub use rdrand::*; - } else if #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] { - compile_error!(concat!( - "The wasm32-unknown-unknown targets are not supported by default; \ - you may need to enable the \"wasm_js\" configuration flag. Note \ - that enabling the `wasm_js` feature flag alone is insufficient. \ - For more information see: \ - https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#webassembly-support" - )); + crate::set_backend!(rdrand::RdrandBackend); } else { compile_error!(concat!( "target is not supported. You may need to define a custom backend see: \ diff --git a/src/backends/apple_other.rs b/src/backends/apple_other.rs index c7b51c0e0..af747554a 100644 --- a/src/backends/apple_other.rs +++ b/src/backends/apple_other.rs @@ -1,21 +1,31 @@ //! Implementation for iOS, tvOS, and watchOS where `getentropy` is unavailable. +use crate::Backend; use crate::Error; -use core::{ffi::c_void, mem::MaybeUninit}; +use core::ffi::c_void; -pub use crate::util::{inner_u32, inner_u64}; +pub struct AppleOtherBackend; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let dst_ptr = dest.as_mut_ptr().cast::(); - let ret = unsafe { libc::CCRandomGenerateBytes(dst_ptr, dest.len()) }; - if ret == libc::kCCSuccess { - Ok(()) - } else { - Err(Error::IOS_RANDOM_GEN) +unsafe impl Backend for AppleOtherBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let dst_ptr = dest.cast::(); + let ret = unsafe { libc::CCRandomGenerateBytes(dst_ptr, len) }; + if ret == libc::kCCSuccess { + Ok(()) + } else { + Err(Error::new_custom(IOS_RANDOM_GEN)) + } } -} -impl Error { - /// Call to `CCRandomGenerateBytes` failed. - pub(crate) const IOS_RANDOM_GEN: Error = Self::new_internal(10); + #[inline] + fn describe_custom_error(n: u16) -> Option<&'static str> { + if n == IOS_RANDOM_GEN { + Some("SecRandomCopyBytes: iOS Security framework failure") + } else { + None + } + } } + +/// Call to `CCRandomGenerateBytes` failed. +const IOS_RANDOM_GEN: u16 = 10; diff --git a/src/backends/custom.rs b/src/backends/custom.rs index c505481ad..3db65f0d9 100644 --- a/src/backends/custom.rs +++ b/src/backends/custom.rs @@ -1,13 +1,15 @@ //! An implementation which calls out to an externally defined function. +use crate::Backend; use crate::Error; -use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; +pub struct LegacyCustomBackend; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - extern "Rust" { - fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error>; +unsafe impl Backend for LegacyCustomBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + extern "Rust" { + fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error>; + } + __getrandom_v03_custom(dest, len) } - unsafe { __getrandom_v03_custom(dest.as_mut_ptr().cast(), dest.len()) } } diff --git a/src/backends/efi_rng.rs b/src/backends/efi_rng.rs index 768c8cc8c..9dd3ed312 100644 --- a/src/backends/efi_rng.rs +++ b/src/backends/efi_rng.rs @@ -1,4 +1,5 @@ //! Implementation for UEFI using EFI_RNG_PROTOCOL +use crate::Backend; use crate::Error; use core::{ mem::MaybeUninit, @@ -12,8 +13,6 @@ use r_efi::{ 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!"); @@ -25,7 +24,7 @@ fn init() -> Result, Error> { const HANDLE_SIZE: usize = size_of::(); let boot_services = std::os::uefi::env::boot_services() - .ok_or(Error::BOOT_SERVICES_UNAVAILABLE)? + .ok_or(Error::new_custom(BOOT_SERVICES_UNAVAILABLE))? .cast::(); let mut handles = [ptr::null_mut(); 16]; @@ -91,34 +90,39 @@ fn init() -> Result, Error> { RNG_PROTOCOL.store(protocol.as_ptr(), Relaxed); return Ok(protocol); } - Err(Error::NO_RNG_HANDLE) + Err(Error::new_custom(NO_RNG_HANDLE)) } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let protocol = match NonNull::new(RNG_PROTOCOL.load(Relaxed)) { - Some(p) => p, - None => init()?, - }; +pub struct UefiBackend; - 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::(), - ) - }; +unsafe impl Backend for UefiBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let protocol = match NonNull::new(RNG_PROTOCOL.load(Relaxed)) { + Some(p) => p, + None => init()?, + }; - if ret.is_error() { - Err(Error::from_uefi_code(ret.as_usize())) - } else { - Ok(()) + let mut alg_guid = rng::ALGORITHM_RAW; + let ret = + unsafe { ((*protocol.as_ptr()).get_rng)(protocol.as_ptr(), &mut alg_guid, len, dest) }; + + if ret.is_error() { + Err(Error::from_uefi_code(ret.as_usize())) + } 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); + #[inline] + fn describe_custom_error(n: u16) -> Option<&'static str> { + match n { + BOOT_SERVICES_UNAVAILABLE => None, // TODO: Custom error message? + NO_RNG_HANDLE => None, // TODO: Custom error message? + _ => None, + } + } } + +const BOOT_SERVICES_UNAVAILABLE: u16 = 10; +const NO_RNG_HANDLE: u16 = 11; diff --git a/src/backends/esp_idf.rs b/src/backends/esp_idf.rs index 4d1689dc7..c6ee34c6f 100644 --- a/src/backends/esp_idf.rs +++ b/src/backends/esp_idf.rs @@ -1,21 +1,23 @@ //! Implementation for ESP-IDF +use crate::Backend; use crate::Error; -use core::{ffi::c_void, mem::MaybeUninit}; - -pub use crate::util::{inner_u32, inner_u64}; +use core::ffi::c_void; extern "C" { fn esp_fill_random(buf: *mut c_void, len: usize) -> u32; } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Not that NOT enabling WiFi, BT, or the voltage noise entropy source (via `bootloader_random_enable`) - // will cause ESP-IDF to return pseudo-random numbers based on the voltage noise entropy, after the initial boot process: - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html - // - // However tracking if some of these entropy sources is enabled is way too difficult to implement here - unsafe { esp_fill_random(dest.as_mut_ptr().cast(), dest.len()) }; +pub struct EspIdfBackend; - Ok(()) +unsafe impl Backend for EspIdfBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + // Not that NOT enabling WiFi, BT, or the voltage noise entropy source (via `bootloader_random_enable`) + // will cause ESP-IDF to return pseudo-random numbers based on the voltage noise entropy, after the initial boot process: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html + // + // However tracking if some of these entropy sources is enabled is way too difficult to implement here + esp_fill_random(dest.cast(), len); + Ok(()) + } } diff --git a/src/backends/fuchsia.rs b/src/backends/fuchsia.rs index b5f1ade54..6b625080e 100644 --- a/src/backends/fuchsia.rs +++ b/src/backends/fuchsia.rs @@ -1,16 +1,18 @@ //! Implementation for Fuchsia Zircon +use crate::Backend; use crate::Error; -use core::mem::MaybeUninit; - -pub use crate::util::{inner_u32, inner_u64}; #[link(name = "zircon")] extern "C" { fn zx_cprng_draw(buffer: *mut u8, length: usize); } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - unsafe { zx_cprng_draw(dest.as_mut_ptr().cast::(), dest.len()) } - Ok(()) +pub struct FuchsiaBackend; + +unsafe impl Backend for FuchsiaBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + zx_cprng_draw(dest, len); + Ok(()) + } } diff --git a/src/backends/getentropy.rs b/src/backends/getentropy.rs index ed181f019..b9e2008a2 100644 --- a/src/backends/getentropy.rs +++ b/src/backends/getentropy.rs @@ -7,21 +7,30 @@ //! - vita newlib since Dec 2021 //! //! For these targets, we use getentropy(2) because getrandom(2) doesn't exist. +use crate::Backend; use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] mod util_libc; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - for chunk in dest.chunks_mut(256) { - let ret = unsafe { libc::getentropy(chunk.as_mut_ptr().cast::(), chunk.len()) }; - if ret != 0 { - return Err(util_libc::last_os_error()); +pub struct GetentropyBackend; + +unsafe impl Backend for GetentropyBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } + + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(256) { + let ret = unsafe { libc::getentropy(chunk.as_mut_ptr().cast::(), chunk.len()) }; + if ret != 0 { + return Err(util_libc::last_os_error()); + } } + Ok(()) } - Ok(()) } diff --git a/src/backends/getrandom.rs b/src/backends/getrandom.rs index 27d5a1f5d..125c8e8a8 100644 --- a/src/backends/getrandom.rs +++ b/src/backends/getrandom.rs @@ -15,17 +15,26 @@ //! GRND_RANDOM is not recommended. On NetBSD/FreeBSD/Dragonfly/3ds, it does //! nothing. On illumos, the default pool is used to implement getentropy(2), //! so we assume it is acceptable here. +use crate::Backend; use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] mod util_libc; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - util_libc::sys_fill_exact(dest, |buf| unsafe { - libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) - }) +pub struct GetrandomBackend; + +unsafe impl Backend for GetrandomBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } + + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + util_libc::sys_fill_exact(dest, |buf| unsafe { + libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) + }) + } } diff --git a/src/backends/hermit.rs b/src/backends/hermit.rs index 34d7cdbb9..20a6f754d 100644 --- a/src/backends/hermit.rs +++ b/src/backends/hermit.rs @@ -1,4 +1,5 @@ //! Implementation for Hermit +use crate::Backend; use crate::Error; use core::mem::MaybeUninit; @@ -12,42 +13,52 @@ extern "C" { fn sys_secure_rand64(value: *mut u64) -> i32; } -#[inline] -pub fn inner_u32() -> Result { - let mut res = MaybeUninit::uninit(); - let ret = unsafe { sys_secure_rand32(res.as_mut_ptr()) }; - match ret { - 0 => Ok(unsafe { res.assume_init() }), - -1 => Err(Error::UNSUPPORTED), - _ => Err(Error::UNEXPECTED), - } -} +pub struct HermitBackend; -#[inline] -pub fn inner_u64() -> Result { - let mut res = MaybeUninit::uninit(); - let ret = unsafe { sys_secure_rand64(res.as_mut_ptr()) }; - match ret { - 0 => Ok(unsafe { res.assume_init() }), - -1 => Err(Error::UNSUPPORTED), - _ => Err(Error::UNEXPECTED), +unsafe impl Backend for HermitBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) } -} -#[inline] -pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { - while !dest.is_empty() { - let res = unsafe { sys_read_entropy(dest.as_mut_ptr().cast::(), dest.len(), 0) }; - match res { - res if res > 0 => { - let len = usize::try_from(res).map_err(|_| Error::UNEXPECTED)?; - dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; - } - code => { - let code = i32::try_from(code).map_err(|_| Error::UNEXPECTED)?; - return Err(Error::from_neg_error_code(code)); + #[inline] + fn fill_uninit(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { + while !dest.is_empty() { + let res = unsafe { sys_read_entropy(dest.as_mut_ptr().cast::(), dest.len(), 0) }; + match res { + res if res > 0 => { + let len = usize::try_from(res).map_err(|_| Error::UNEXPECTED)?; + dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; + } + code => { + let code = i32::try_from(code).map_err(|_| Error::UNEXPECTED)?; + return Err(Error::from_neg_error_code(code)); + } } } + Ok(()) + } + + #[inline] + fn u32() -> Result { + let mut res = MaybeUninit::uninit(); + let ret = unsafe { sys_secure_rand32(res.as_mut_ptr()) }; + match ret { + 0 => Ok(unsafe { res.assume_init() }), + -1 => Err(Error::UNSUPPORTED), + _ => Err(Error::UNEXPECTED), + } + } + + #[inline] + fn u64() -> Result { + let mut res = MaybeUninit::uninit(); + let ret = unsafe { sys_secure_rand64(res.as_mut_ptr()) }; + match ret { + 0 => Ok(unsafe { res.assume_init() }), + -1 => Err(Error::UNSUPPORTED), + _ => Err(Error::UNEXPECTED), + } } - Ok(()) } diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs index 2ad8f0a46..06545628c 100644 --- a/src/backends/linux_android_with_fallback.rs +++ b/src/backends/linux_android_with_fallback.rs @@ -1,5 +1,6 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback use super::use_file; +use crate::Backend; use crate::Error; use core::{ ffi::c_void, @@ -9,8 +10,6 @@ use core::{ }; use use_file::util_libc; -pub use crate::util::{inner_u32, inner_u64}; - type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; /// Sentinel value which indicates that `libc::getrandom` either not available, @@ -72,30 +71,40 @@ fn init() -> NonNull { // Prevent inlining of the fallback implementation #[inline(never)] fn use_file_fallback(dest: &mut [MaybeUninit]) -> Result<(), Error> { - use_file::fill_inner(dest) + use_file::UseFileBackend::fill_uninit(dest) } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Despite being only a single atomic variable, we still cannot always use - // Ordering::Relaxed, as we need to make sure a successful call to `init` - // is "ordered before" any data read through the returned pointer (which - // occurs when the function is called). Our implementation mirrors that of - // the one in libstd, meaning that the use of non-Relaxed operations is - // probably unnecessary. - let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire); - let fptr = match NonNull::new(raw_ptr) { - Some(p) => p, - None => init(), - }; +pub struct LinuxBackend; + +unsafe impl Backend for LinuxBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } - if fptr == NOT_AVAILABLE { - use_file_fallback(dest) - } else { - // note: `transmute` is currently the only way to convert a pointer into a function reference - let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; - util_libc::sys_fill_exact(dest, |buf| unsafe { - getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0) - }) + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Despite being only a single atomic variable, we still cannot always use + // Ordering::Relaxed, as we need to make sure a successful call to `init` + // is "ordered before" any data read through the returned pointer (which + // occurs when the function is called). Our implementation mirrors that of + // the one in libstd, meaning that the use of non-Relaxed operations is + // probably unnecessary. + let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire); + let fptr = match NonNull::new(raw_ptr) { + Some(p) => p, + None => init(), + }; + + if fptr == NOT_AVAILABLE { + use_file_fallback(dest) + } else { + // note: `transmute` is currently the only way to convert a pointer into a function reference + let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; + util_libc::sys_fill_exact(dest, |buf| unsafe { + getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0) + }) + } } } diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index 4a59eef00..be46351f0 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -1,8 +1,7 @@ //! Implementation for Linux / Android using `asm!`-based syscalls. +use crate::Backend; use crate::{Error, MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; - #[cfg(not(any(target_os = "android", target_os = "linux")))] compile_error!("`linux_raw` backend can be enabled only for Linux/Android targets!"); @@ -111,25 +110,35 @@ unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { r0 } -#[inline] -pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Value of this error code is stable across all target arches. - const EINTR: isize = -4; +pub struct LinuxRawBackend; + +unsafe impl Backend for LinuxRawBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } + + #[inline] + fn fill_uninit(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Value of this error code is stable across all target arches. + const EINTR: isize = -4; - loop { - let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) }; - match usize::try_from(ret) { - Ok(0) => return Err(Error::UNEXPECTED), - Ok(len) => { - dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; - if dest.is_empty() { - return Ok(()); + loop { + let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) }; + match usize::try_from(ret) { + Ok(0) => return Err(Error::UNEXPECTED), + Ok(len) => { + dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; + if dest.is_empty() { + return Ok(()); + } + } + Err(_) if ret == EINTR => continue, + Err(_) => { + let code = i32::try_from(ret).map_err(|_| Error::UNEXPECTED)?; + return Err(Error::from_neg_error_code(code)); } - } - Err(_) if ret == EINTR => continue, - Err(_) => { - let code = i32::try_from(ret).map_err(|_| Error::UNEXPECTED)?; - return Err(Error::from_neg_error_code(code)); } } } diff --git a/src/backends/netbsd.rs b/src/backends/netbsd.rs index f228a8b13..987d25e17 100644 --- a/src/backends/netbsd.rs +++ b/src/backends/netbsd.rs @@ -3,6 +3,7 @@ //! `getrandom(2)` was introduced in NetBSD 10. To support older versions we //! implement our own weak linkage to it, and provide a fallback based on the //! KERN_ARND sysctl. +use crate::Backend; use crate::Error; use core::{ cmp, @@ -12,8 +13,6 @@ use core::{ sync::atomic::{AtomicPtr, Ordering}, }; -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] mod util_libc; @@ -59,20 +58,30 @@ fn init() -> *mut c_void { ptr } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Despite being only a single atomic variable, we still cannot always use - // Ordering::Relaxed, as we need to make sure a successful call to `init` - // is "ordered before" any data read through the returned pointer (which - // occurs when the function is called). Our implementation mirrors that of - // the one in libstd, meaning that the use of non-Relaxed operations is - // probably unnecessary. - let mut fptr = GETRANDOM.load(Ordering::Acquire); - if fptr.is_null() { - fptr = init(); +pub struct NetBsdBackend; + +unsafe impl Backend for NetBsdBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } + + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Despite being only a single atomic variable, we still cannot always use + // Ordering::Relaxed, as we need to make sure a successful call to `init` + // is "ordered before" any data read through the returned pointer (which + // occurs when the function is called). Our implementation mirrors that of + // the one in libstd, meaning that the use of non-Relaxed operations is + // probably unnecessary. + let mut fptr = GETRANDOM.load(Ordering::Acquire); + if fptr.is_null() { + fptr = init(); + } + let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) }; + util_libc::sys_fill_exact(dest, |buf| unsafe { + fptr(buf.as_mut_ptr().cast::(), buf.len(), 0) + }) } - let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) }; - util_libc::sys_fill_exact(dest, |buf| unsafe { - fptr(buf.as_mut_ptr().cast::(), buf.len(), 0) - }) } diff --git a/src/backends/rdrand.rs b/src/backends/rdrand.rs index 609fcc386..67d38a626 100644 --- a/src/backends/rdrand.rs +++ b/src/backends/rdrand.rs @@ -1,4 +1,5 @@ //! RDRAND backend for x86(-64) targets +use crate::Backend; use crate::{util::slice_as_uninit, Error}; use core::mem::{size_of, MaybeUninit}; @@ -147,36 +148,53 @@ unsafe fn rdrand_u64() -> Option { Some((u64::from(a) << 32) | u64::from(b)) } -#[inline] -pub fn inner_u32() -> Result { - if !RDRAND_GOOD.unsync_init(is_rdrand_good) { - return Err(Error::NO_RDRAND); +pub struct RdrandBackend; + +unsafe impl Backend for RdrandBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) } - // SAFETY: After this point, we know rdrand is supported. - unsafe { rdrand_u32() }.ok_or(Error::FAILED_RDRAND) -} -#[inline] -pub fn inner_u64() -> Result { - if !RDRAND_GOOD.unsync_init(is_rdrand_good) { - return Err(Error::NO_RDRAND); + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::new_custom(NO_RDRAND)); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_exact(dest) }.ok_or(Error::new_custom(FAILED_RDRAND)) } - // SAFETY: After this point, we know rdrand is supported. - unsafe { rdrand_u64() }.ok_or(Error::FAILED_RDRAND) -} -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - if !RDRAND_GOOD.unsync_init(is_rdrand_good) { - return Err(Error::NO_RDRAND); + #[inline] + fn u32() -> Result { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::new_custom(NO_RDRAND)); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_u32() }.ok_or(Error::new_custom(FAILED_RDRAND)) } - // SAFETY: After this point, we know rdrand is supported. - unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) -} -impl Error { - /// RDRAND instruction failed due to a hardware issue. - pub(crate) const FAILED_RDRAND: Error = Self::new_internal(10); - /// RDRAND instruction unsupported on this target. - pub(crate) const NO_RDRAND: Error = Self::new_internal(11); + #[inline] + fn u64() -> Result { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::new_custom(NO_RDRAND)); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_u64() }.ok_or(Error::new_custom(FAILED_RDRAND)) + } + + #[inline] + fn describe_custom_error(n: u16) -> Option<&'static str> { + match n { + FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"), + NO_RDRAND => Some("RDRAND: instruction not supported"), + _ => None, + } + } } + +/// RDRAND instruction failed due to a hardware issue. +const FAILED_RDRAND: u16 = 10; +/// RDRAND instruction unsupported on this target. +const NO_RDRAND: u16 = 11; diff --git a/src/backends/rndr.rs b/src/backends/rndr.rs index eea741a2d..4f61f08d4 100644 --- a/src/backends/rndr.rs +++ b/src/backends/rndr.rs @@ -2,6 +2,7 @@ //! //! Arm Architecture Reference Manual for A-profile architecture: //! ARM DDI 0487K.a, ID032224, D23.2.147 RNDR, Random Number +use crate::Backend; use crate::{ util::{slice_as_uninit, truncate}, Error, @@ -108,38 +109,55 @@ fn is_rndr_available() -> bool { } } -#[inline] -pub fn inner_u32() -> Result { - if !is_rndr_available() { - return Err(Error::RNDR_NOT_AVAILABLE); +pub struct RndrBackend; + +unsafe impl Backend for RndrBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) } - // SAFETY: after this point, we know the `rand` target feature is enabled - let res = unsafe { rndr() }; - res.map(truncate).ok_or(Error::RNDR_FAILURE) -} -#[inline] -pub fn inner_u64() -> Result { - if !is_rndr_available() { - return Err(Error::RNDR_NOT_AVAILABLE); + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + if !is_rndr_available() { + return Err(Error::new_custom(RNDR_NOT_AVAILABLE)); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + unsafe { rndr_fill(dest).ok_or(Error::new_custom(RNDR_FAILURE)) } } - // SAFETY: after this point, we know the `rand` target feature is enabled - let res = unsafe { rndr() }; - res.ok_or(Error::RNDR_FAILURE) -} -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - if !is_rndr_available() { - return Err(Error::RNDR_NOT_AVAILABLE); + #[inline] + fn u32() -> Result { + if !is_rndr_available() { + return Err(Error::new_custom(RNDR_NOT_AVAILABLE)); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + let res = unsafe { rndr() }; + res.map(truncate).ok_or(Error::new_custom(RNDR_FAILURE)) } - // SAFETY: after this point, we know the `rand` target feature is enabled - unsafe { rndr_fill(dest).ok_or(Error::RNDR_FAILURE) } -} -impl Error { - /// RNDR register read failed due to a hardware issue. - pub(crate) const RNDR_FAILURE: Error = Self::new_internal(10); - /// RNDR register is not supported on this target. - pub(crate) const RNDR_NOT_AVAILABLE: Error = Self::new_internal(11); + #[inline] + fn u64() -> Result { + if !is_rndr_available() { + return Err(Error::new_custom(RNDR_NOT_AVAILABLE)); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + let res = unsafe { rndr() }; + res.ok_or(Error::new_custom(RNDR_FAILURE)) + } + + #[inline] + fn describe_custom_error(n: u16) -> Option<&'static str> { + match n { + RNDR_FAILURE => Some("RNDR: Could not generate a random number"), + RNDR_NOT_AVAILABLE => Some("RNDR: Register not supported"), + _ => None, + } + } } + +/// RNDR register read failed due to a hardware issue. +const RNDR_FAILURE: u16 = 10; +/// RNDR register is not supported on this target. +const RNDR_NOT_AVAILABLE: u16 = 11; diff --git a/src/backends/solaris.rs b/src/backends/solaris.rs index c27f91a5f..4f5fd6ce4 100644 --- a/src/backends/solaris.rs +++ b/src/backends/solaris.rs @@ -12,31 +12,40 @@ //! For more information, see the man page linked in lib.rs and this blog post: //! https://blogs.oracle.com/solaris/post/solaris-new-system-calls-getentropy2-and-getrandom2 //! which also explains why this crate should not use getentropy(2). +use crate::Backend; use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] mod util_libc; const MAX_BYTES: usize = 1024; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - for chunk in dest.chunks_mut(MAX_BYTES) { - let ptr = chunk.as_mut_ptr().cast::(); - let ret = unsafe { libc::getrandom(ptr, chunk.len(), libc::GRND_RANDOM) }; - // In case the man page has a typo, we also check for negative ret. - // If getrandom(2) succeeds, it should have completely filled chunk. - match usize::try_from(ret) { - // Good. Keep going. - Ok(ret) if ret == chunk.len() => {} - // The syscall failed. - Ok(0) => return Err(util_libc::last_os_error()), - // All other cases should be impossible. - _ => return Err(Error::UNEXPECTED), +pub struct SolarisBackend; + +unsafe impl Backend for SolarisBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } + + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(MAX_BYTES) { + let ptr = chunk.as_mut_ptr().cast::(); + let ret = unsafe { libc::getrandom(ptr, chunk.len(), libc::GRND_RANDOM) }; + // In case the man page has a typo, we also check for negative ret. + // If getrandom(2) succeeds, it should have completely filled chunk. + match usize::try_from(ret) { + // Good. Keep going. + Ok(ret) if ret == chunk.len() => {} + // The syscall failed. + Ok(0) => return Err(util_libc::last_os_error()), + // All other cases should be impossible. + _ => return Err(Error::UNEXPECTED), + } } + Ok(()) } - Ok(()) } diff --git a/src/backends/solid.rs b/src/backends/solid.rs index caa773f81..5685d10ce 100644 --- a/src/backends/solid.rs +++ b/src/backends/solid.rs @@ -1,19 +1,21 @@ //! Implementation for SOLID +use crate::Backend; use crate::Error; -use core::mem::MaybeUninit; - -pub use crate::util::{inner_u32, inner_u64}; extern "C" { pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32; } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr().cast::(), dest.len()) }; - if ret >= 0 { - Ok(()) - } else { - Err(Error::from_neg_error_code(ret)) +pub struct SolidBackend; + +unsafe impl Backend for SolidBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest, len) }; + if ret >= 0 { + Ok(()) + } else { + Err(Error::from_neg_error_code(ret)) + } } } diff --git a/src/backends/unsupported.rs b/src/backends/unsupported.rs index 4ea381fc4..fc15b79b4 100644 --- a/src/backends/unsupported.rs +++ b/src/backends/unsupported.rs @@ -1,9 +1,12 @@ //! Implementation that errors at runtime. +use crate::Backend; use crate::Error; -use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; +pub struct UnsupportedBackend; -pub fn fill_inner(_dest: &mut [MaybeUninit]) -> Result<(), Error> { - Err(Error::UNSUPPORTED) +unsafe impl Backend for UnsupportedBackend { + #[inline] + unsafe fn fill_ptr(_dest: *mut u8, _len: usize) -> Result<(), Error> { + Err(Error::UNSUPPORTED) + } } diff --git a/src/backends/use_file.rs b/src/backends/use_file.rs index 7b48d4338..df377c21f 100644 --- a/src/backends/use_file.rs +++ b/src/backends/use_file.rs @@ -1,4 +1,5 @@ //! Implementations that just need to read from a file +use crate::Backend; use crate::Error; use core::{ ffi::c_void, @@ -6,9 +7,6 @@ use core::{ sync::atomic::{AtomicI32, Ordering}, }; -#[cfg(not(any(target_os = "android", target_os = "linux")))] -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] pub(super) mod util_libc; @@ -40,15 +38,25 @@ const FD_ONGOING_INIT: libc::c_int = -2; // `Ordering::Acquire` to synchronize with it. static FD: AtomicI32 = AtomicI32::new(FD_UNINIT); -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let mut fd = FD.load(Ordering::Acquire); - if fd == FD_UNINIT || fd == FD_ONGOING_INIT { - fd = open_or_wait()?; +pub struct UseFileBackend; + +unsafe impl Backend for UseFileBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } + + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let mut fd = FD.load(Ordering::Acquire); + if fd == FD_UNINIT || fd == FD_ONGOING_INIT { + fd = open_or_wait()?; + } + util_libc::sys_fill_exact(dest, |buf| unsafe { + libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()) + }) } - util_libc::sys_fill_exact(dest, |buf| unsafe { - libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()) - }) } /// Open a file in read-only mode. diff --git a/src/backends/vxworks.rs b/src/backends/vxworks.rs index 5f5e6773b..5db3f1e53 100644 --- a/src/backends/vxworks.rs +++ b/src/backends/vxworks.rs @@ -1,4 +1,5 @@ //! Implementation for VxWorks +use crate::Backend; use crate::Error; use core::{ cmp::Ordering::{Equal, Greater, Less}, @@ -9,7 +10,47 @@ use core::{ #[path = "../util_libc.rs"] mod util_libc; -pub use crate::util::{inner_u32, inner_u64}; +pub struct VxWorksBackend; + +unsafe impl Backend for VxWorksBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } + + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + while !RNG_INIT.load(Relaxed) { + init()?; + } + + // Prevent overflow of i32 + let chunk_size = + usize::try_from(i32::MAX).expect("VxWorks does not support 16-bit targets"); + for chunk in dest.chunks_mut(chunk_size) { + let chunk_len: libc::c_int = chunk + .len() + .try_into() + .expect("chunk size is bounded by i32::MAX"); + let p: *mut libc::c_uchar = chunk.as_mut_ptr().cast(); + let ret = unsafe { libc::randABytes(p, chunk_len) }; + if ret != 0 { + return Err(util_libc::last_os_error()); + } + } + Ok(()) + } + + #[inline] + fn describe_custom_error(n: u16) -> Option<&'static str> { + if n == VXWORKS_RAND_SECURE { + Some("Web Crypto API is unavailable") + } else { + None + } + } +} static RNG_INIT: AtomicBool = AtomicBool::new(false); @@ -21,34 +62,10 @@ fn init() -> Result<(), Error> { Equal => unsafe { libc::usleep(10); }, - Less => return Err(Error::VXWORKS_RAND_SECURE), + Less => return Err(Error::new_custom(VXWORKS_RAND_SECURE)), } Ok(()) } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - while !RNG_INIT.load(Relaxed) { - init()?; - } - - // Prevent overflow of i32 - let chunk_size = usize::try_from(i32::MAX).expect("VxWorks does not support 16-bit targets"); - for chunk in dest.chunks_mut(chunk_size) { - let chunk_len: libc::c_int = chunk - .len() - .try_into() - .expect("chunk size is bounded by i32::MAX"); - let p: *mut libc::c_uchar = chunk.as_mut_ptr().cast(); - let ret = unsafe { libc::randABytes(p, chunk_len) }; - if ret != 0 { - return Err(util_libc::last_os_error()); - } - } - Ok(()) -} - -impl Error { - /// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized). - pub(crate) const VXWORKS_RAND_SECURE: Error = Self::new_internal(10); -} +/// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized). +const VXWORKS_RAND_SECURE: u16 = 10; diff --git a/src/backends/wasi_p1.rs b/src/backends/wasi_p1.rs index 25b5ca3b7..933121284 100644 --- a/src/backends/wasi_p1.rs +++ b/src/backends/wasi_p1.rs @@ -1,8 +1,6 @@ //! Implementation for WASI Preview 1 +use crate::Backend; use crate::Error; -use core::mem::MaybeUninit; - -pub use crate::util::{inner_u32, inner_u64}; // This linking is vendored from the wasi crate: // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2344-2350 @@ -15,18 +13,22 @@ extern "C" { /// https://github.com/WebAssembly/WASI/blob/38454e9e/legacy/preview1/witx/typenames.witx#L34-L39 const MAX_ERROR_CODE: i32 = u16::MAX as i32; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Based on the wasi code: - // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062 - // Note that size of an allocated object can not be bigger than isize::MAX bytes. - // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe. - #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] - let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) }; - match ret { - 0 => Ok(()), - // WASI functions should return positive error codes which are smaller than `MAX_ERROR_CODE` - code if code <= MAX_ERROR_CODE => Err(Error::from_neg_error_code(-code)), - _ => Err(Error::UNEXPECTED), +pub struct WasiP1Backend; + +unsafe impl Backend for WasiP1Backend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + // Based on the wasi code: + // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062 + // Note that size of an allocated object can not be bigger than isize::MAX bytes. + // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe. + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + let ret = unsafe { random_get(dest as i32, len as i32) }; + match ret { + 0 => Ok(()), + // WASI functions should return positive error codes which are smaller than `MAX_ERROR_CODE` + code if code <= MAX_ERROR_CODE => Err(Error::from_neg_error_code(-code)), + _ => Err(Error::UNEXPECTED), + } } } diff --git a/src/backends/wasi_p2.rs b/src/backends/wasi_p2.rs index 63bd2d7cb..e841b594d 100644 --- a/src/backends/wasi_p2.rs +++ b/src/backends/wasi_p2.rs @@ -1,50 +1,60 @@ //! Implementation for WASI Preview 2. +use crate::Backend; use crate::Error; use core::mem::MaybeUninit; use wasi::random::random::get_random_u64; -#[inline] -pub fn inner_u32() -> Result { - let val = get_random_u64(); - Ok(crate::util::truncate(val)) -} +pub struct WasiP2Backend; -#[inline] -pub fn inner_u64() -> Result { - Ok(get_random_u64()) -} +unsafe impl Backend for WasiP2Backend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - use core::ptr::copy_nonoverlapping; - use wasi::random::random::get_random_u64; + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + use core::ptr::copy_nonoverlapping; + + let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::>() }; + + // We use `get_random_u64` instead of `get_random_bytes` because the latter creates + // an allocation due to the Wit IDL [restrictions][0]. This should be fine since + // the main use case of `getrandom` is seed generation. + // + // [0]: https://github.com/WebAssembly/wasi-random/issues/27 + if !prefix.is_empty() { + let val = get_random_u64(); + let src = (&val as *const u64).cast(); + unsafe { + copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len()); + } + } - let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::>() }; + for dst in chunks { + dst.write(get_random_u64()); + } - // We use `get_random_u64` instead of `get_random_bytes` because the latter creates - // an allocation due to the Wit IDL [restrictions][0]. This should be fine since - // the main use case of `getrandom` is seed generation. - // - // [0]: https://github.com/WebAssembly/wasi-random/issues/27 - if !prefix.is_empty() { - let val = get_random_u64(); - let src = (&val as *const u64).cast(); - unsafe { - copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len()); + if !suffix.is_empty() { + let val = get_random_u64(); + let src = (&val as *const u64).cast(); + unsafe { + copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len()); + } } - } - for dst in chunks { - dst.write(get_random_u64()); + Ok(()) } - if !suffix.is_empty() { + #[inline] + fn u32() -> Result { let val = get_random_u64(); - let src = (&val as *const u64).cast(); - unsafe { - copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len()); - } + Ok(crate::util::truncate(val)) } - Ok(()) + #[inline] + fn u64() -> Result { + Ok(get_random_u64()) + } } diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 1320d9fc4..d4eaae657 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -1,58 +1,73 @@ //! Implementation for WASM based on Web and Node.js +use crate::Backend; use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; - #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] compile_error!("`wasm_js` backend can be enabled only for OS-less WASM targets!"); use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; -// Maximum buffer size allowed in `Crypto.getRandomValuesSize` is 65536 bytes. -// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues -const MAX_BUFFER_SIZE: usize = 65536; +pub struct WasmJsBackend; + +unsafe impl Backend for WasmJsBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } -#[cfg(not(target_feature = "atomics"))] -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - for chunk in dest.chunks_mut(MAX_BUFFER_SIZE) { - if get_random_values(chunk).is_err() { - return Err(Error::WEB_CRYPTO); + #[cfg(not(target_feature = "atomics"))] + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE) { + if get_random_values(chunk).is_err() { + return Err(Error::new_custom(WEB_CRYPTO)); + } } + Ok(()) } - Ok(()) -} -#[cfg(target_feature = "atomics")] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // getRandomValues does not work with all types of WASM memory, - // so we initially write to browser memory to avoid exceptions. - let buf_len = usize::min(dest.len(), MAX_BUFFER_SIZE); - let buf_len_u32 = buf_len - .try_into() - .expect("buffer length is bounded by MAX_BUFFER_SIZE"); - let buf = js_sys::Uint8Array::new_with_length(buf_len_u32); - for chunk in dest.chunks_mut(buf_len) { - let chunk_len = chunk - .len() + #[cfg(target_feature = "atomics")] + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // getRandomValues does not work with all types of WASM memory, + // so we initially write to browser memory to avoid exceptions. + let buf_len = usize::min(dest.len(), MAX_BUFFER_SIZE); + let buf_len_u32 = buf_len .try_into() - .expect("chunk length is bounded by MAX_BUFFER_SIZE"); - // The chunk can be smaller than buf's length, so we call to - // JS to create a smaller view of buf without allocation. - let sub_buf = if chunk_len == buf_len_u32 { - &buf - } else { - &buf.subarray(0, chunk_len) - }; + .expect("buffer length is bounded by MAX_BUFFER_SIZE"); + let buf = js_sys::Uint8Array::new_with_length(buf_len_u32); + for chunk in dest.chunks_mut(buf_len) { + let chunk_len = chunk + .len() + .try_into() + .expect("chunk length is bounded by MAX_BUFFER_SIZE"); + // The chunk can be smaller than buf's length, so we call to + // JS to create a smaller view of buf without allocation. + let sub_buf = if chunk_len == buf_len_u32 { + &buf + } else { + &buf.subarray(0, chunk_len) + }; - if get_random_values(sub_buf).is_err() { - return Err(Error::WEB_CRYPTO); + if get_random_values(sub_buf).is_err() { + return Err(Error::new_custom(WEB_CRYPTO)); + } + + sub_buf.copy_to_uninit(chunk); } + Ok(()) + } - sub_buf.copy_to_uninit(chunk); + #[inline] + fn describe_custom_error(n: u16) -> Option<&'static str> { + if n == WEB_CRYPTO { + Some("Web Crypto API is unavailable") + } else { + None + } } - Ok(()) } #[wasm_bindgen] @@ -66,7 +81,9 @@ extern "C" { fn get_random_values(buf: &js_sys::Uint8Array) -> Result<(), JsValue>; } -impl Error { - /// The environment does not support the Web Crypto API. - pub(crate) const WEB_CRYPTO: Error = Self::new_internal(10); -} +/// The environment does not support the Web Crypto API. +const WEB_CRYPTO: u16 = 10; + +/// Maximum buffer size allowed in `Crypto.getRandomValuesSize` is 65536 bytes. +/// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +const MAX_BUFFER_SIZE: usize = 65536; diff --git a/src/backends/windows.rs b/src/backends/windows.rs index b5cd504fe..804af9a9d 100644 --- a/src/backends/windows.rs +++ b/src/backends/windows.rs @@ -20,10 +20,25 @@ //! - Thin wrapper around ProcessPrng //! //! For more information see the Windows RNG Whitepaper: https://aka.ms/win10rng + +use crate::Backend; use crate::Error; -use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; +pub struct WindowsBackend; + +unsafe impl Backend for WindowsBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let result = unsafe { ProcessPrng(dest, len) }; + // Since Windows 10, calls to the user-mode RNG are guaranteed to never + // fail during runtime (rare windows W); `ProcessPrng` will only ever + // return 1 (which is how windows represents TRUE). + // See the bottom of page 6 of the aforementioned Windows RNG + // whitepaper for more information. + debug_assert!(result == TRUE); + Ok(()) + } +} // Binding to the Windows.Win32.Security.Cryptography.ProcessPrng API. As // bcryptprimitives.dll lacks an import library, we use "raw-dylib". This @@ -47,15 +62,3 @@ extern "system" { #[allow(clippy::upper_case_acronyms)] type BOOL = core::ffi::c_int; // MSRV 1.64, similarly OK for this backend. const TRUE: BOOL = 1; - -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let result = unsafe { ProcessPrng(dest.as_mut_ptr().cast::(), dest.len()) }; - // Since Windows 10, calls to the user-mode RNG are guaranteed to never - // fail during runtime (rare windows W); `ProcessPrng` will only ever - // return 1 (which is how windows represents TRUE). - // See the bottom of page 6 of the aforementioned Windows RNG - // whitepaper for more information. - debug_assert!(result == TRUE); - Ok(()) -} diff --git a/src/backends/windows7.rs b/src/backends/windows7.rs index 8a353a9f8..9cae724d7 100644 --- a/src/backends/windows7.rs +++ b/src/backends/windows7.rs @@ -12,7 +12,41 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; +use crate::Backend; + +pub struct WindowsLegacyBackend; + +unsafe impl Backend for WindowsLegacyBackend { + #[inline] + unsafe fn fill_ptr(dest: *mut u8, len: usize) -> Result<(), Error> { + let slice = core::slice::from_raw_parts_mut(dest.cast(), len); + Self::fill_uninit(slice) + } + + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Prevent overflow of u32 + let chunk_size = + usize::try_from(i32::MAX).expect("Windows does not support 16-bit targets"); + for chunk in dest.chunks_mut(chunk_size) { + let chunk_len = u32::try_from(chunk.len()).expect("chunk size is bounded by i32::MAX"); + let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr().cast::(), chunk_len) }; + if ret != TRUE { + return Err(Error::new_custom(WINDOWS_RTL_GEN_RANDOM)); + } + } + Ok(()) + } + + #[inline] + fn describe_custom_error(n: u16) -> Option<&'static str> { + if n == WINDOWS_RTL_GEN_RANDOM { + Some("RtlGenRandom: Windows system function failure") + } else { + None + } + } +} // Binding to the Windows.Win32.Security.Authentication.Identity.RtlGenRandom // API. Don't use windows-targets as it doesn't support Windows 7 targets. @@ -25,21 +59,5 @@ extern "system" { type BOOLEAN = u8; const TRUE: BOOLEAN = 1u8; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Prevent overflow of u32 - let chunk_size = usize::try_from(i32::MAX).expect("Windows does not support 16-bit targets"); - for chunk in dest.chunks_mut(chunk_size) { - let chunk_len = u32::try_from(chunk.len()).expect("chunk size is bounded by i32::MAX"); - let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr().cast::(), chunk_len) }; - if ret != TRUE { - return Err(Error::WINDOWS_RTL_GEN_RANDOM); - } - } - Ok(()) -} - -impl Error { - /// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed. - pub(crate) const WINDOWS_RTL_GEN_RANDOM: Error = Self::new_internal(10); -} +/// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed. +const WINDOWS_RTL_GEN_RANDOM: u16 = 10; diff --git a/src/error.rs b/src/error.rs index 13f3121f6..fb827a4bd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,8 @@ extern crate std; use core::fmt; +use crate::Backend; + // This private alias mirrors `std::io::RawOsError`: // https://doc.rust-lang.org/std/io/type.RawOsError.html) cfg_if::cfg_if!( @@ -50,8 +52,7 @@ impl Error { /// Creates a new instance of an `Error` from a negative error code. #[cfg(not(target_os = "uefi"))] - #[allow(dead_code)] - pub(super) fn from_neg_error_code(code: RawOsError) -> Self { + pub fn from_neg_error_code(code: RawOsError) -> Self { if code < 0 { let code = NonZeroRawOsError::new(code).expect("`code` is negative"); Self(code) @@ -62,8 +63,7 @@ impl Error { /// Creates a new instance of an `Error` from an UEFI error code. #[cfg(target_os = "uefi")] - #[allow(dead_code)] - pub(super) fn from_uefi_code(code: RawOsError) -> Self { + pub fn from_uefi_code(code: RawOsError) -> Self { if code & UEFI_ERROR_FLAG != 0 { let code = NonZeroRawOsError::new(code).expect("The highest bit of `code` is set to 1"); Self(code) @@ -129,6 +129,19 @@ impl Error { Error(unsafe { NonZeroRawOsError::new_unchecked(code) }) } + /// Creates a new instance of an `Error` from a particular custom error code. + fn as_custom(self) -> Option { + let mut value = self.0.get(); + + if value < Self::CUSTOM_START { + return None; + } + + value -= Self::CUSTOM_START; + + u16::try_from(value).ok() + } + /// Creates a new instance of an `Error` from a particular internal error code. pub(crate) const fn new_internal(n: u16) -> Error { // SAFETY: code > 0 as INTERNAL_START > 0 and adding `n` won't overflow `RawOsError`. @@ -141,39 +154,15 @@ impl Error { Error::UNSUPPORTED => "getrandom: this target is not supported", Error::ERRNO_NOT_POSITIVE => "errno: did not return a positive value", Error::UNEXPECTED => "unexpected situation", - #[cfg(any( - target_os = "ios", - target_os = "visionos", - target_os = "watchos", - target_os = "tvos", - ))] - Error::IOS_RANDOM_GEN => "SecRandomCopyBytes: iOS Security framework failure", - #[cfg(all(windows, target_vendor = "win7"))] - Error::WINDOWS_RTL_GEN_RANDOM => "RtlGenRandom: Windows system function failure", - #[cfg(all(feature = "wasm_js", getrandom_backend = "wasm_js"))] - Error::WEB_CRYPTO => "Web Crypto API is unavailable", - #[cfg(target_os = "vxworks")] - Error::VXWORKS_RAND_SECURE => "randSecure: VxWorks RNG module is not initialized", - - #[cfg(any( - getrandom_backend = "rdrand", - all(target_arch = "x86_64", target_env = "sgx") - ))] - Error::FAILED_RDRAND => "RDRAND: failed multiple times: CPU issue likely", - #[cfg(any( - getrandom_backend = "rdrand", - all(target_arch = "x86_64", target_env = "sgx") - ))] - Error::NO_RDRAND => "RDRAND: instruction not supported", - - #[cfg(getrandom_backend = "rndr")] - Error::RNDR_FAILURE => "RNDR: Could not generate a random number", - #[cfg(getrandom_backend = "rndr")] - Error::RNDR_NOT_AVAILABLE => "RNDR: Register not supported", _ => return None, }; Some(desc) } + + fn custom_desc(&self) -> Option<&'static str> { + let n = self.as_custom()?; + crate::ExternBackend::describe_custom_error(n) + } } impl fmt::Debug for Error { @@ -186,6 +175,9 @@ impl fmt::Debug for Error { } else if let Some(desc) = self.internal_desc() { dbg.field("internal_code", &self.0.get()); dbg.field("description", &desc); + } else if let Some(desc) = self.custom_desc() { + dbg.field("custom_code", &self.0.get()); + dbg.field("description", &desc); } else { dbg.field("unknown_code", &self.0.get()); } @@ -205,6 +197,8 @@ impl fmt::Display for Error { } } else if let Some(desc) = self.internal_desc() { f.write_str(desc) + } else if let Some(desc) = self.custom_desc() { + f.write_str(desc) } else { write!(f, "Unknown Error: {}", self.0.get()) } diff --git a/src/lib.rs b/src/lib.rs index 51c494e17..3e3a8f7f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,45 +32,21 @@ extern crate cfg_if; use core::mem::MaybeUninit; -mod backends; +use crate::backend::ExternBackend; + +mod backend; mod error; mod util; +#[cfg(feature = "default-backends")] +mod backends; + #[cfg(feature = "std")] mod error_std_impls; +pub use crate::backend::Backend; pub use crate::error::Error; -/// 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 -/// make no guarantees regarding the contents of `dest` on error. If `dest` is -/// empty, `getrandom` immediately returns success, making no calls to the -/// underlying operating system. -/// -/// Blocking is possible, at least during early boot; see module documentation. -/// -/// In general, `getrandom` will be fast enough for interactive usage, though -/// significantly slower than a user-space CSPRNG; for the latter consider -/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html). -/// -/// # Examples -/// -/// ``` -/// # fn main() -> Result<(), getrandom::Error> { -/// let mut buf = [0u8; 32]; -/// getrandom::fill(&mut buf)?; -/// # Ok(()) } -/// ``` -#[inline] -pub fn fill(dest: &mut [u8]) -> Result<(), Error> { - // SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, - // and `fill_uninit` guarantees it will never de-initialize - // any part of `dest`. - fill_uninit(unsafe { util::slice_as_uninit_mut(dest) })?; - Ok(()) -} - /// Fill potentially uninitialized buffer `dest` with random bytes from /// the system's preferred random number source and return a mutable /// reference to those bytes. @@ -96,7 +72,7 @@ pub fn fill(dest: &mut [u8]) -> Result<(), Error> { #[inline] pub fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { if !dest.is_empty() { - backends::fill_inner(dest)?; + ExternBackend::fill_uninit(dest)?; } #[cfg(getrandom_msan)] @@ -114,6 +90,32 @@ pub fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { }) } +/// 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 +/// make no guarantees regarding the contents of `dest` on error. If `dest` is +/// empty, `getrandom` immediately returns success, making no calls to the +/// underlying operating system. +/// +/// Blocking is possible, at least during early boot; see module documentation. +/// +/// In general, `getrandom` will be fast enough for interactive usage, though +/// significantly slower than a user-space CSPRNG; for the latter consider +/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html). +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), getrandom::Error> { +/// let mut buf = [0u8; 32]; +/// getrandom::fill(&mut buf)?; +/// # Ok(()) } +/// ``` +#[inline] +pub fn fill(dest: &mut [u8]) -> Result<(), Error> { + ExternBackend::fill(dest) +} + /// Get random `u32` from the system's preferred random number source. /// /// # Examples @@ -125,7 +127,7 @@ pub fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { /// ``` #[inline] pub fn u32() -> Result { - backends::inner_u32() + ExternBackend::u32() } /// Get random `u64` from the system's preferred random number source. @@ -139,5 +141,5 @@ pub fn u32() -> Result { /// ``` #[inline] pub fn u64() -> Result { - backends::inner_u64() + ExternBackend::u64() } diff --git a/src/util.rs b/src/util.rs index d42c26e75..77300992f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,43 +1,42 @@ #![allow(dead_code)] -use crate::Error; -use core::{mem::MaybeUninit, ptr, slice}; +use core::{mem::MaybeUninit, ptr}; /// Polyfill for `maybe_uninit_slice` feature's /// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have /// been initialized. #[inline(always)] -#[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. -pub unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { +#[deny(unsafe_op_in_unsafe_fn)] +pub(crate) unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { let ptr = ptr_from_mut::<[MaybeUninit]>(slice) as *mut [T]; // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. unsafe { &mut *ptr } } +/// View an mutable initialized array as potentially-uninitialized. +/// +/// This is unsafe because it allows assigning uninitialized values into +/// `slice`, which would be undefined behavior. +#[inline(always)] +#[deny(unsafe_op_in_unsafe_fn)] +pub(crate) unsafe fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { + let ptr = ptr_from_mut::<[T]>(slice) as *mut [MaybeUninit]; + // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. + unsafe { &mut *ptr } +} + #[inline] -pub fn uninit_slice_fill_zero(slice: &mut [MaybeUninit]) -> &mut [u8] { +pub(crate) fn uninit_slice_fill_zero(slice: &mut [MaybeUninit]) -> &mut [u8] { unsafe { ptr::write_bytes(slice.as_mut_ptr(), 0, slice.len()) }; unsafe { slice_assume_init_mut(slice) } } #[inline(always)] -pub fn slice_as_uninit(slice: &[T]) -> &[MaybeUninit] { +pub(crate) fn slice_as_uninit(slice: &[T]) -> &[MaybeUninit] { let ptr = ptr_from_ref::<[T]>(slice) as *const [MaybeUninit]; // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. unsafe { &*ptr } } -/// View an mutable initialized array as potentially-uninitialized. -/// -/// This is unsafe because it allows assigning uninitialized values into -/// `slice`, which would be undefined behavior. -#[inline(always)] -#[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. -pub unsafe fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { - let ptr = ptr_from_mut::<[T]>(slice) as *mut [MaybeUninit]; - // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. - unsafe { &mut *ptr } -} - // TODO: MSRV(1.76.0): Replace with `core::ptr::from_mut`. fn ptr_from_mut(r: &mut T) -> *mut T { r @@ -48,36 +47,6 @@ fn ptr_from_ref(r: &T) -> *const T { r } -/// Default implementation of `inner_u32` on top of `fill_uninit` -#[inline] -pub fn inner_u32() -> Result { - let mut res = MaybeUninit::::uninit(); - // SAFETY: the created slice has the same size as `res` - let dst = unsafe { - let p: *mut MaybeUninit = res.as_mut_ptr().cast(); - slice::from_raw_parts_mut(p, core::mem::size_of::()) - }; - crate::fill_uninit(dst)?; - // SAFETY: `dst` has been fully initialized by `imp::fill_inner` - // since it returned `Ok`. - Ok(unsafe { res.assume_init() }) -} - -/// Default implementation of `inner_u64` on top of `fill_uninit` -#[inline] -pub fn inner_u64() -> Result { - let mut res = MaybeUninit::::uninit(); - // SAFETY: the created slice has the same size as `res` - let dst = unsafe { - let p: *mut MaybeUninit = res.as_mut_ptr().cast(); - slice::from_raw_parts_mut(p, core::mem::size_of::()) - }; - crate::fill_uninit(dst)?; - // SAFETY: `dst` has been fully initialized by `imp::fill_inner` - // since it returned `Ok`. - Ok(unsafe { res.assume_init() }) -} - /// Truncates `u64` and returns the lower 32 bits as `u32` pub(crate) fn truncate(val: u64) -> u32 { u32::try_from(val & u64::from(u32::MAX)).expect("The higher 32 bits are masked")