From 1872b3e1228910bdcbfd12744c5c6039dbcc9765 Mon Sep 17 00:00:00 2001 From: Jeff Vander Stoep Date: Thu, 1 Feb 2024 19:43:14 +0100 Subject: Upgrade getrandom to 0.2.12 This project was upgraded with external_updater. Usage: tools/external_updater/updater.sh update external/rust/crates/getrandom For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md Test: TreeHugger Change-Id: I43a887168c674135090cee3b29d16158c2e03cf7 --- .cargo_vcs_info.json | 2 +- Android.bp | 8 +-- CHANGELOG.md | 96 +++++++++++++++++++++++++++++++++- Cargo.toml | 21 ++++++-- Cargo.toml.orig | 23 ++++++--- LICENSE-MIT | 2 +- METADATA | 25 ++++----- README.md | 33 +++++++++--- SECURITY.md | 13 +++++ benches/buffer.rs | 71 +++++++++++++++++++++++++ benches/mod.rs | 94 --------------------------------- src/3ds.rs | 11 +--- src/apple-other.rs | 24 +++++++++ src/bsd_arandom.rs | 39 ++++++-------- src/custom.rs | 46 +++++++++-------- src/dragonfly.rs | 15 ++---- src/emscripten.rs | 13 +++++ src/error.rs | 14 +++-- src/error_impls.rs | 7 --- src/espidf.rs | 12 +---- src/fuchsia.rs | 13 ++--- src/hermit.rs | 29 +++++++++++ src/hurd.rs | 10 ++++ src/ios.rs | 27 ---------- src/js.rs | 37 ++++++++----- src/lazy.rs | 56 ++++++++++++++++++++ src/lib.rs | 137 +++++++++++++++++++++++++++++++++++-------------- src/linux_android.rs | 13 ++--- src/macos.rs | 42 +++++---------- src/openbsd.rs | 11 +--- src/rdrand.rs | 126 +++++++++++++++++++++++++++------------------ src/solaris_illumos.rs | 33 +++++------- src/solid.rs | 14 ++--- src/use_file.rs | 52 ++++++++----------- src/util.rs | 85 ++++++++++-------------------- src/util_libc.rs | 49 ++++++++---------- src/vita.rs | 13 +++++ src/vxworks.rs | 17 +++--- src/wasi.rs | 28 +++++----- src/windows.rs | 32 ++++++++---- tests/common/mod.rs | 46 ++++++++++++++--- tests/custom.rs | 26 ++++++---- tests/rdrand.rs | 9 +++- 43 files changed, 861 insertions(+), 613 deletions(-) create mode 100644 SECURITY.md create mode 100644 benches/buffer.rs delete mode 100644 benches/mod.rs create mode 100644 src/apple-other.rs create mode 100644 src/emscripten.rs create mode 100644 src/hermit.rs create mode 100644 src/hurd.rs delete mode 100644 src/ios.rs create mode 100644 src/lazy.rs create mode 100644 src/vita.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index fc6c877..bf9c98e 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "5c1bb00b74a2c72ba182171b93d1c4b9d30c10c4" + "sha1": "f68a940b4dac78b49f00599aebd56c72284a92d9" }, "path_in_vcs": "" } \ No newline at end of file diff --git a/Android.bp b/Android.bp index bbd1507..e5c7ab1 100644 --- a/Android.bp +++ b/Android.bp @@ -42,7 +42,7 @@ rust_test { host_supported: true, crate_name: "getrandom", cargo_env_compat: true, - cargo_pkg_version: "0.2.8", + cargo_pkg_version: "0.2.12", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -62,7 +62,7 @@ rust_test { host_supported: true, crate_name: "normal", cargo_env_compat: true, - cargo_pkg_version: "0.2.8", + cargo_pkg_version: "0.2.12", srcs: ["tests/normal.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -83,7 +83,7 @@ rust_test { host_supported: true, crate_name: "rdrand", cargo_env_compat: true, - cargo_pkg_version: "0.2.8", + cargo_pkg_version: "0.2.12", srcs: ["tests/rdrand.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -104,7 +104,7 @@ rust_library { host_supported: true, crate_name: "getrandom", cargo_env_compat: true, - cargo_pkg_version: "0.2.8", + cargo_pkg_version: "0.2.12", srcs: ["src/lib.rs"], edition: "2018", features: ["std"], diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cf9a58..a283d38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,94 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.12] - 2024-01-09 +### Fixed +- Custom backend for targets without atomics [#385] + +### Changed +- Improve robustness of the Hermit backend and `sys_fill_exact` [#386] +- Raise minimum supported Apple OS versions to macOS 10.12 and iOS 10 [#388] + +### Added +- Document platform support policy [#387] + +[#385]: https://github.com/rust-random/getrandom/pull/385 +[#386]: https://github.com/rust-random/getrandom/pull/386 +[#387]: https://github.com/rust-random/getrandom/pull/387 +[#388]: https://github.com/rust-random/getrandom/pull/388 + +## [0.2.11] - 2023-11-08 +### Added +- GNU/Hurd support [#370] + +### Changed +- Renamed `__getrandom_internal` to `__GETRANDOM_INTERNAL` [#369] +- Updated link to Hermit docs [#374] + +[#369]: https://github.com/rust-random/getrandom/pull/369 +[#370]: https://github.com/rust-random/getrandom/pull/370 +[#374]: https://github.com/rust-random/getrandom/pull/374 + +## [0.2.10] - 2023-06-06 +### Added +- Support for PS Vita (`armv7-sony-vita-newlibeabihf`) [#359] + +### Changed +- Use getentropy from libc on Emscripten targets [#362] + +[#359]: https://github.com/rust-random/getrandom/pull/359 +[#362]: https://github.com/rust-random/getrandom/pull/362 + +## [0.2.9] - 2023-04-06 +### Added +- AIX support [#282] +- `getrandom_uninit` function [#291] +- `wasm64-unknown-unknown` support [#303] +- tvOS and watchOS support [#317] +- QNX/nto support [#325] +- Support for `getrandom` syscall on NetBSD ≥ 10.0 [#331] +- `RtlGenRandom` fallback for non-UWP Windows [#337] + +### Breaking Changes +- Update MSRV to 1.36 [#291] + +### Fixed +- Solaris/OpenBSD/Dragonfly build [#301] + +### Changed +- Update MSRV to 1.36 [#291] +- Use getentropy on Emscripten [#307] +- Solaris: consistantly use `/dev/random` source [#310] +- Move 3ds selection above rdrand/js/custom fallback [#312] +- Remove buffer zeroing from Node.js implementation [#315] +- Use `open` instead of `open64` [#326] +- Remove #cfg from bsd_arandom.rs [#332] +- Hermit: use `sys_read_entropy` syscall [#333] +- Eliminate potential panic in sys_fill_exact [#334] +- rdrand: Remove checking for 0 and !0 and instead check CPU family and do a self-test [#335] +- Move `__getrandom_custom` definition into a const block [#344] +- Switch the custom backend to Rust ABI [#347] + +[#282]: https://github.com/rust-random/getrandom/pull/282 +[#291]: https://github.com/rust-random/getrandom/pull/291 +[#301]: https://github.com/rust-random/getrandom/pull/301 +[#303]: https://github.com/rust-random/getrandom/pull/303 +[#307]: https://github.com/rust-random/getrandom/pull/307 +[#310]: https://github.com/rust-random/getrandom/pull/310 +[#312]: https://github.com/rust-random/getrandom/pull/312 +[#315]: https://github.com/rust-random/getrandom/pull/315 +[#317]: https://github.com/rust-random/getrandom/pull/317 +[#325]: https://github.com/rust-random/getrandom/pull/325 +[#326]: https://github.com/rust-random/getrandom/pull/326 +[#331]: https://github.com/rust-random/getrandom/pull/331 +[#332]: https://github.com/rust-random/getrandom/pull/332 +[#333]: https://github.com/rust-random/getrandom/pull/333 +[#334]: https://github.com/rust-random/getrandom/pull/334 +[#335]: https://github.com/rust-random/getrandom/pull/335 +[#337]: https://github.com/rust-random/getrandom/pull/337 +[#344]: https://github.com/rust-random/getrandom/pull/344 +[#347]: https://github.com/rust-random/getrandom/pull/347 + ## [0.2.8] - 2022-10-20 ### Changed - The [Web Cryptography API] will now be preferred on `wasm32-unknown-unknown` @@ -95,7 +183,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.2] - 2021-01-19 ### Changed - Forward `rustc-dep-of-std` to dependencies. [#198] -- Highlight feature-dependend functionality in documentation using the `doc_cfg` feature. [#200] +- Highlight feature-dependent functionality in documentation using the `doc_cfg` feature. [#200] [#198]: https://github.com/rust-random/getrandom/pull/198 [#200]: https://github.com/rust-random/getrandom/pull/200 @@ -225,7 +313,7 @@ disabled `dummy` feature. [#90] ## [0.1.9] - 2019-08-14 [YANKED] ### Changed - Remove `std` dependency for opening and reading files. [#58] -- Use `wasi` isntead of `libc` on WASI target. [#64] +- Use `wasi` instead of `libc` on WASI target. [#64] - By default emit a compile-time error when built for an unsupported target. This behaviour can be disabled by using the `dummy` feature. [#71] @@ -331,6 +419,10 @@ Publish initial implementation. ## [0.0.0] - 2019-01-19 Publish an empty template library. +[0.2.12]: https://github.com/rust-random/getrandom/compare/v0.2.11...v0.2.12 +[0.2.11]: https://github.com/rust-random/getrandom/compare/v0.2.10...v0.2.11 +[0.2.10]: https://github.com/rust-random/getrandom/compare/v0.2.9...v0.2.10 +[0.2.9]: https://github.com/rust-random/getrandom/compare/v0.2.8...v0.2.9 [0.2.8]: https://github.com/rust-random/getrandom/compare/v0.2.7...v0.2.8 [0.2.7]: https://github.com/rust-random/getrandom/compare/v0.2.6...v0.2.7 [0.2.6]: https://github.com/rust-random/getrandom/compare/v0.2.5...v0.2.6 diff --git a/Cargo.toml b/Cargo.toml index f0252c9..5d94d81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ [package] edition = "2018" name = "getrandom" -version = "0.2.8" +version = "0.2.12" authors = ["The Rand Project Developers"] exclude = [".*"] description = "A small cross-platform library for retrieving random data from system source" @@ -25,6 +25,16 @@ categories = [ license = "MIT OR Apache-2.0" repository = "https://github.com/rust-random/getrandom" +[package.metadata.cross.target.x86_64-unknown-netbsd] +pre-build = [ + "mkdir -p /tmp/netbsd", + "curl https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O", + "tar -C /tmp/netbsd -xJf base.tar.xz", + "cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib", + "rm base.tar.xz", + "rm -rf /tmp/netbsd", +] + [package.metadata.docs.rs] features = [ "std", @@ -63,21 +73,22 @@ rustc-dep-of-std = [ std = [] test-in-browser = [] -[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.js-sys] +[target."cfg(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), target_os = \"unknown\"))".dependencies.js-sys] version = "0.3" optional = true -[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.wasm-bindgen] +[target."cfg(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), target_os = \"unknown\"))".dependencies.wasm-bindgen] version = "0.2.62" optional = true default-features = false -[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dev-dependencies.wasm-bindgen-test] +[target."cfg(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), target_os = \"unknown\"))".dev-dependencies.wasm-bindgen-test] version = "0.3.18" [target."cfg(target_os = \"wasi\")".dependencies.wasi] version = "0.11" +default-features = false [target."cfg(unix)".dependencies.libc] -version = "0.2.120" +version = "0.2.149" default-features = false diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 2024c8f..ea57331 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "getrandom" -version = "0.2.8" # Also update html_root_url in lib.rs when bumping this +version = "0.2.12" # Also update html_root_url in lib.rs when bumping this edition = "2018" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" @@ -18,15 +18,15 @@ compiler_builtins = { version = "0.1", optional = true } core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" } [target.'cfg(unix)'.dependencies] -libc = { version = "0.2.120", default-features = false } +libc = { version = "0.2.149", default-features = false } [target.'cfg(target_os = "wasi")'.dependencies] -wasi = "0.11" +wasi = { version = "0.11", default-features = false } -[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } js-sys = { version = "0.3", optional = true } -[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies] wasm-bindgen-test = "0.3.18" [features] @@ -34,7 +34,7 @@ wasm-bindgen-test = "0.3.18" std = [] # Feature to enable fallback RDRAND-based implementation on x86/x86_64 rdrand = [] -# Feature to enable JavaScript bindings on wasm32-unknown-unknown +# Feature to enable JavaScript bindings on wasm*-unknown-unknown js = ["wasm-bindgen", "js-sys"] # Feature to enable custom RNG implementations custom = [] @@ -51,3 +51,14 @@ test-in-browser = [] [package.metadata.docs.rs] features = ["std", "custom"] rustdoc-args = ["--cfg", "docsrs"] + +# workaround for https://github.com/cross-rs/cross/issues/1345 +[package.metadata.cross.target.x86_64-unknown-netbsd] +pre-build = [ + "mkdir -p /tmp/netbsd", + "curl https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O", + "tar -C /tmp/netbsd -xJf base.tar.xz", + "cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib", + "rm base.tar.xz", + "rm -rf /tmp/netbsd", +] diff --git a/LICENSE-MIT b/LICENSE-MIT index d93b5ba..8ca28a1 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright 2018 Developers of the Rand project +Copyright (c) 2018-2024 The rust-random Project Developers Copyright (c) 2014 The Rust Project Developers Permission is hereby granted, free of charge, to any diff --git a/METADATA b/METADATA index 8c76529..1ecb3fe 100644 --- a/METADATA +++ b/METADATA @@ -1,23 +1,20 @@ # This project was upgraded with external_updater. -# Usage: tools/external_updater/updater.sh update rust/crates/getrandom -# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md +# Usage: tools/external_updater/updater.sh update external/rust/crates/getrandom +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md name: "getrandom" description: "A small cross-platform library for retrieving random data from system source" third_party { - url { - type: HOMEPAGE - value: "https://crates.io/crates/getrandom" - } - url { - type: ARCHIVE - value: "https://static.crates.io/crates/getrandom/getrandom-0.2.8.crate" - } - version: "0.2.8" license_type: NOTICE last_upgrade_date { - year: 2022 - month: 12 - day: 12 + year: 2024 + month: 2 + day: 1 + } + homepage: "https://crates.io/crates/getrandom" + identifier { + type: "Archive" + value: "https://static.crates.io/crates/getrandom/getrandom-0.2.12.crate" + version: "0.2.12" } } diff --git a/README.md b/README.md index df2307b..b4b5a2b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status]][GitHub Actions] [![Crate]][crates.io] [![Documentation]][docs.rs] [![Dependency Status]][deps.rs] [![Downloads]][crates.io] [![License]][LICENSE-MIT] [GitHub Actions]: https://github.com/rust-random/getrandom/actions?query=workflow:Tests+branch:master -[Build Status]: https://github.com/rust-random/getrandom/workflows/Tests/badge.svg?branch=master +[Build Status]: https://github.com/rust-random/getrandom/actions/workflows/tests.yml/badge.svg?branch=master [crates.io]: https://crates.io/crates/getrandom [Crate]: https://img.shields.io/crates/v/getrandom [docs.rs]: https://docs.rs/getrandom @@ -15,10 +15,10 @@ [License]: https://img.shields.io/crates/l/getrandom -A Rust library for retrieving random data from (operating) system source. It is -assumed that system always provides high-quality cryptographically secure random +A Rust library for retrieving random data from (operating) system sources. It is +assumed that the system always provides high-quality cryptographically secure random data, ideally backed by hardware entropy sources. This crate derives its name -from Linux's `getrandom` function, but is cross platform, roughly supporting +from Linux's `getrandom` function, but is cross-platform, roughly supporting the same set of platforms as Rust's `std` lib. This is a low-level API. Most users should prefer using high-level random-number @@ -52,13 +52,30 @@ crate features, WASM support and Custom RNGs see the ## Minimum Supported Rust Version -This crate requires Rust 1.34.0 or later. +This crate requires Rust 1.36.0 or later. -# License +## 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 pluggable custom implementations. + +This means that as Rust drops support for old versions of operating systems (such as old Linux kernel versions, Android API levels, etc) +in stable releases, `getrandom` may create new patch releases (`0.N.x`) that remove support for outdated platform versions. + +## License The `getrandom` library is distributed under either of - * [Apache License, Version 2.0](LICENSE-APACHE) - * [MIT license](LICENSE-MIT) + * [Apache License, Version 2.0][LICENSE-APACHE] + * [MIT license][LICENSE-MIT] at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-APACHE]: https://github.com/rust-random/getrandom/blob/master/LICENSE-APACHE +[LICENSE-MIT]: https://github.com/rust-random/getrandom/blob/master/LICENSE-MIT diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..19bfb9a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/rust-random/getrandom/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. diff --git a/benches/buffer.rs b/benches/buffer.rs new file mode 100644 index 0000000..b32be43 --- /dev/null +++ b/benches/buffer.rs @@ -0,0 +1,71 @@ +#![feature(test, maybe_uninit_uninit_array_transpose)] +extern crate test; + +use std::mem::MaybeUninit; + +// Call getrandom on a zero-initialized stack buffer +#[inline(always)] +fn bench_getrandom() { + let mut buf = [0u8; N]; + getrandom::getrandom(&mut buf).unwrap(); + test::black_box(&buf as &[u8]); +} + +// Call getrandom_uninit on an uninitialized stack buffer +#[inline(always)] +fn bench_getrandom_uninit() { + let mut uninit = [MaybeUninit::uninit(); N]; + let buf: &[u8] = getrandom::getrandom_uninit(&mut uninit).unwrap(); + test::black_box(buf); +} + +// We benchmark using #[inline(never)] "inner" functions for two reasons: +// - Avoiding inlining reduces a source of variance when running benchmarks. +// - It is _much_ easier to get the assembly or IR for the inner loop. +// +// For example, using cargo-show-asm (https://github.com/pacak/cargo-show-asm), +// we can get the assembly for a particular benchmark's inner loop by running: +// cargo asm --bench buffer --release buffer::p384::bench_getrandom::inner +macro_rules! bench { + ( $name:ident, $size:expr ) => { + pub mod $name { + #[bench] + pub fn bench_getrandom(b: &mut test::Bencher) { + #[inline(never)] + fn inner() { + super::bench_getrandom::<{ $size }>() + } + + b.bytes = $size as u64; + b.iter(inner); + } + #[bench] + pub fn bench_getrandom_uninit(b: &mut test::Bencher) { + #[inline(never)] + fn inner() { + super::bench_getrandom_uninit::<{ $size }>() + } + + b.bytes = $size as u64; + b.iter(inner); + } + } + }; +} + +// 16 bytes (128 bits) is the size of an 128-bit AES key/nonce. +bench!(aes128, 128 / 8); + +// 32 bytes (256 bits) is the seed sized used for rand::thread_rng +// and the `random` value in a ClientHello/ServerHello for TLS. +// This is also the size of a 256-bit AES/HMAC/P-256/Curve25519 key +// and/or nonce. +bench!(p256, 256 / 8); + +// A P-384/HMAC-384 key and/or nonce. +bench!(p384, 384 / 8); + +// Initializing larger buffers is not the primary use case of this library, as +// this should normally be done by a userspace CSPRNG. However, we have a test +// here to see the effects of a lower (amortized) syscall overhead. +bench!(page, 4096); diff --git a/benches/mod.rs b/benches/mod.rs deleted file mode 100644 index 11be47e..0000000 --- a/benches/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -#![feature(test)] -extern crate test; - -use std::{ - alloc::{alloc_zeroed, dealloc, Layout}, - ptr::NonNull, -}; - -// AlignedBuffer is like a Box<[u8; N]> except that it is always N-byte aligned -struct AlignedBuffer(NonNull<[u8; N]>); - -impl AlignedBuffer { - fn layout() -> Layout { - Layout::from_size_align(N, N).unwrap() - } - - fn new() -> Self { - let p = unsafe { alloc_zeroed(Self::layout()) } as *mut [u8; N]; - Self(NonNull::new(p).unwrap()) - } - - fn buf(&mut self) -> &mut [u8; N] { - unsafe { self.0.as_mut() } - } -} - -impl Drop for AlignedBuffer { - fn drop(&mut self) { - unsafe { dealloc(self.0.as_ptr() as *mut u8, Self::layout()) } - } -} - -// Used to benchmark the throughput of getrandom in an optimal scenario. -// The buffer is hot, and does not require initialization. -#[inline(always)] -fn bench(b: &mut test::Bencher) { - let mut ab = AlignedBuffer::::new(); - let buf = ab.buf(); - b.iter(|| { - getrandom::getrandom(&mut buf[..]).unwrap(); - test::black_box(&buf); - }); - b.bytes = N as u64; -} - -// Used to benchmark the throughput of getrandom is a slightly less optimal -// scenario. The buffer is still hot, but requires initialization. -#[inline(always)] -fn bench_with_init(b: &mut test::Bencher) { - let mut ab = AlignedBuffer::::new(); - let buf = ab.buf(); - b.iter(|| { - for byte in buf.iter_mut() { - *byte = 0; - } - getrandom::getrandom(&mut buf[..]).unwrap(); - test::black_box(&buf); - }); - b.bytes = N as u64; -} - -// 32 bytes (256-bit) is the seed sized used for rand::thread_rng -const SEED: usize = 32; -// Common size of a page, 4 KiB -const PAGE: usize = 4096; -// Large buffer to get asymptotic performance, 2 MiB -const LARGE: usize = 1 << 21; - -#[bench] -fn bench_seed(b: &mut test::Bencher) { - bench::(b); -} -#[bench] -fn bench_seed_init(b: &mut test::Bencher) { - bench_with_init::(b); -} - -#[bench] -fn bench_page(b: &mut test::Bencher) { - bench::(b); -} -#[bench] -fn bench_page_init(b: &mut test::Bencher) { - bench_with_init::(b); -} - -#[bench] -fn bench_large(b: &mut test::Bencher) { - bench::(b); -} -#[bench] -fn bench_large_init(b: &mut test::Bencher) { - bench_with_init::(b); -} diff --git a/src/3ds.rs b/src/3ds.rs index 6030512..a5aae77 100644 --- a/src/3ds.rs +++ b/src/3ds.rs @@ -1,16 +1,9 @@ -// Copyright 2021 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for Nintendo 3DS use crate::util_libc::sys_fill_exact; use crate::Error; +use core::mem::MaybeUninit; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { sys_fill_exact(dest, |buf| unsafe { libc::getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) }) diff --git a/src/apple-other.rs b/src/apple-other.rs new file mode 100644 index 0000000..167d8cf --- /dev/null +++ b/src/apple-other.rs @@ -0,0 +1,24 @@ +//! Implementation for iOS, tvOS, and watchOS where `getentropy` is unavailable. +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit}; + +// libsystem contains the libc of Darwin, and every binary ends up linked against it either way. This +// makes it a more lightweight choice compared to `Security.framework`. +extern "C" { + // This RNG uses a thread-local CSPRNG to provide data, which is seeded by the operating system's root CSPRNG. + // Its the best option after `getentropy` on modern Darwin-based platforms that also avoids the + // high startup costs and linking of Security.framework. + // + // While its just an implementation detail, `Security.framework` just calls into this anyway. + fn CCRandomGenerateBytes(bytes: *mut c_void, size: usize) -> i32; +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let ret = unsafe { CCRandomGenerateBytes(dest.as_mut_ptr() as *mut c_void, dest.len()) }; + // kCCSuccess (from CommonCryptoError.h) is always zero. + if ret != 0 { + Err(Error::IOS_SEC_RANDOM) + } else { + Ok(()) + } +} diff --git a/src/bsd_arandom.rs b/src/bsd_arandom.rs index d441212..6e133d8 100644 --- a/src/bsd_arandom.rs +++ b/src/bsd_arandom.rs @@ -1,16 +1,11 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for FreeBSD and NetBSD -use crate::{util_libc::sys_fill_exact, Error}; -use core::ptr; +use crate::{ + util_libc::{sys_fill_exact, Weak}, + Error, +}; +use core::{mem::MaybeUninit, ptr}; -fn kern_arnd(buf: &mut [u8]) -> libc::ssize_t { +fn kern_arnd(buf: &mut [MaybeUninit]) -> libc::ssize_t { static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND]; let mut len = buf.len(); let ret = unsafe { @@ -30,20 +25,18 @@ fn kern_arnd(buf: &mut [u8]) -> libc::ssize_t { } } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - // getrandom(2) was introduced in FreeBSD 12.0 and NetBSD 10.0 - #[cfg(target_os = "freebsd")] - { - use crate::util_libc::Weak; - static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; - type GetRandomFn = - unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; +type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; - if let Some(fptr) = GETRANDOM.ptr() { - let func: GetRandomFn = unsafe { core::mem::transmute(fptr) }; - return sys_fill_exact(dest, |buf| unsafe { func(buf.as_mut_ptr(), buf.len(), 0) }); - } +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // getrandom(2) was introduced in FreeBSD 12.0 and NetBSD 10.0 + static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; + if let Some(fptr) = GETRANDOM.ptr() { + let func: GetRandomFn = unsafe { core::mem::transmute(fptr) }; + return sys_fill_exact(dest, |buf| unsafe { + func(buf.as_mut_ptr() as *mut u8, buf.len(), 0) + }); } + // Both FreeBSD and NetBSD will only return up to 256 bytes at a time, and // older NetBSD kernels will fail on longer buffers. for chunk in dest.chunks_mut(256) { diff --git a/src/custom.rs b/src/custom.rs index 8432dfd..8dc9cb7 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -1,14 +1,6 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! An implementation which calls out to an externally defined function. -use crate::Error; -use core::num::NonZeroU32; +use crate::{util::uninit_slice_fill_zero, Error}; +use core::{mem::MaybeUninit, num::NonZeroU32}; /// Register a function to be invoked by `getrandom` on unsupported targets. /// @@ -76,24 +68,36 @@ use core::num::NonZeroU32; #[cfg_attr(docsrs, doc(cfg(feature = "custom")))] macro_rules! register_custom_getrandom { ($path:path) => { - // We use an extern "C" function to get the guarantees of a stable ABI. - #[no_mangle] - extern "C" fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { - let f: fn(&mut [u8]) -> Result<(), $crate::Error> = $path; - let slice = unsafe { ::core::slice::from_raw_parts_mut(dest, len) }; - match f(slice) { - Ok(()) => 0, - Err(e) => e.code().get(), + // 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 [u8]) -> Result<(), Error> { - extern "C" { +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()) }; match NonZeroU32::new(ret) { None => Ok(()), diff --git a/src/dragonfly.rs b/src/dragonfly.rs index 8daaa40..ac4794c 100644 --- a/src/dragonfly.rs +++ b/src/dragonfly.rs @@ -1,26 +1,21 @@ -// Copyright 2021 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for DragonFly BSD use crate::{ use_file, util_libc::{sys_fill_exact, Weak}, Error, }; +use core::mem::MaybeUninit; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; // getrandom(2) was introduced in DragonflyBSD 5.7 if let Some(fptr) = GETRANDOM.ptr() { let func: GetRandomFn = unsafe { core::mem::transmute(fptr) }; - return sys_fill_exact(dest, |buf| unsafe { func(buf.as_mut_ptr(), buf.len(), 0) }); + return sys_fill_exact(dest, |buf| unsafe { + func(buf.as_mut_ptr() as *mut u8, buf.len(), 0) + }); } else { use_file::getrandom_inner(dest) } diff --git a/src/emscripten.rs b/src/emscripten.rs new file mode 100644 index 0000000..30221c6 --- /dev/null +++ b/src/emscripten.rs @@ -0,0 +1,13 @@ +//! Implementation for Emscripten +use crate::{util_libc::last_os_error, Error}; +use core::mem::MaybeUninit; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Emscripten 2.0.5 added getentropy, so we can use it unconditionally. + // Unlike other getentropy implementations, there is no max buffer length. + let ret = unsafe { libc::getentropy(dest.as_mut_ptr() as *mut libc::c_void, dest.len()) }; + if ret < 0 { + return Err(last_os_error()); + } + Ok(()) +} diff --git a/src/error.rs b/src/error.rs index ab39a3c..13c81c7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,3 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. use core::{fmt, num::NonZeroU32}; /// A small and `no_std` compatible error type @@ -35,7 +28,11 @@ impl Error { pub const UNSUPPORTED: Error = internal_error(0); /// The platform-specific `errno` returned a non-positive value. pub const ERRNO_NOT_POSITIVE: Error = internal_error(1); - /// Call to iOS [`SecRandomCopyBytes`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) failed. + /// Encountered an unexpected situation which should not happen in practice. + pub const UNEXPECTED: Error = internal_error(2); + /// Call to [`CCRandomGenerateBytes`](https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html) failed + /// on iOS, tvOS, or waatchOS. + // TODO: Update this constant name in the next breaking release. pub const IOS_SEC_RANDOM: Error = internal_error(3); /// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed. pub const WINDOWS_RTL_GEN_RANDOM: Error = internal_error(4); @@ -164,6 +161,7 @@ fn internal_desc(error: Error) -> Option<&'static str> { match error { Error::UNSUPPORTED => Some("getrandom: this target is not supported"), Error::ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"), + Error::UNEXPECTED => Some("unexpected situation"), Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Security framework failure"), Error::WINDOWS_RTL_GEN_RANDOM => Some("RtlGenRandom: Windows system function failure"), Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"), diff --git a/src/error_impls.rs b/src/error_impls.rs index 61f46d2..a7bffb8 100644 --- a/src/error_impls.rs +++ b/src/error_impls.rs @@ -1,10 +1,3 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. #![cfg_attr(docsrs, doc(cfg(feature = "std")))] extern crate std; diff --git a/src/espidf.rs b/src/espidf.rs index dce8a2a..7da5ca8 100644 --- a/src/espidf.rs +++ b/src/espidf.rs @@ -1,20 +1,12 @@ -// Copyright 2021 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for ESP-IDF use crate::Error; -use core::ffi::c_void; +use core::{ffi::c_void, mem::MaybeUninit}; extern "C" { fn esp_fill_random(buf: *mut c_void, len: usize) -> u32; } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_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 diff --git a/src/fuchsia.rs b/src/fuchsia.rs index 572ff53..1197068 100644 --- a/src/fuchsia.rs +++ b/src/fuchsia.rs @@ -1,20 +1,13 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for Fuchsia Zircon use crate::Error; +use core::mem::MaybeUninit; #[link(name = "zircon")] extern "C" { fn zx_cprng_draw(buffer: *mut u8, length: usize); } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - unsafe { zx_cprng_draw(dest.as_mut_ptr(), dest.len()) } +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + unsafe { zx_cprng_draw(dest.as_mut_ptr() as *mut u8, dest.len()) } Ok(()) } diff --git a/src/hermit.rs b/src/hermit.rs new file mode 100644 index 0000000..c4f6194 --- /dev/null +++ b/src/hermit.rs @@ -0,0 +1,29 @@ +//! Implementation for Hermit +use crate::Error; +use core::{mem::MaybeUninit, num::NonZeroU32}; + +/// Minimum return value which we should get from syscalls in practice, +/// because Hermit uses positive `i32`s for error codes: +/// https://github.com/hermitcore/libhermit-rs/blob/main/src/errno.rs +const MIN_RET_CODE: isize = -(i32::MAX as isize); + +extern "C" { + fn sys_read_entropy(buffer: *mut u8, length: usize, flags: u32) -> isize; +} + +pub fn getrandom_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { + while !dest.is_empty() { + let res = unsafe { sys_read_entropy(dest.as_mut_ptr() as *mut u8, dest.len(), 0) }; + // Positive `isize`s can be safely casted to `usize` + if res > 0 && (res as usize) <= dest.len() { + dest = &mut dest[res as usize..]; + } else { + let err = match res { + MIN_RET_CODE..=-1 => NonZeroU32::new(-res as u32).unwrap().into(), + _ => Error::UNEXPECTED, + }; + return Err(err); + } + } + Ok(()) +} diff --git a/src/hurd.rs b/src/hurd.rs new file mode 100644 index 0000000..472a7d8 --- /dev/null +++ b/src/hurd.rs @@ -0,0 +1,10 @@ +//! Implementation for GNU/Hurd +use crate::util_libc::sys_fill_exact; +use crate::Error; +use core::mem::MaybeUninit; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + sys_fill_exact(dest, |buf| unsafe { + libc::getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) + }) +} diff --git a/src/ios.rs b/src/ios.rs deleted file mode 100644 index 226de16..0000000 --- a/src/ios.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Implementation for iOS -use crate::Error; -use core::{ffi::c_void, ptr::null}; - -#[link(name = "Security", kind = "framework")] -extern "C" { - fn SecRandomCopyBytes(rnd: *const c_void, count: usize, bytes: *mut u8) -> i32; -} - -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - // Apple's documentation guarantees kSecRandomDefault is a synonym for NULL. - let ret = unsafe { SecRandomCopyBytes(null(), dest.len(), dest.as_mut_ptr()) }; - // errSecSuccess (from SecBase.h) is always zero. - if ret != 0 { - Err(Error::IOS_SEC_RANDOM) - } else { - Ok(()) - } -} diff --git a/src/js.rs b/src/js.rs index 574c4dc..e5428f5 100644 --- a/src/js.rs +++ b/src/js.rs @@ -1,14 +1,8 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. +//! Implementation for WASM based on Web and Node.js use crate::Error; extern crate std; -use std::thread_local; +use std::{mem::MaybeUninit, thread_local}; use js_sys::{global, Function, Uint8Array}; use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; @@ -16,6 +10,8 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; // Size of our temporary Uint8Array buffer used with WebCrypto methods // Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues const WEB_CRYPTO_BUFFER_SIZE: usize = 256; +// Node.js's crypto.randomFillSync requires the size to be less than 2**31. +const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1; enum RngSource { Node(NodeCrypto), @@ -28,14 +24,27 @@ thread_local!( static RNG_SOURCE: Result = getrandom_init(); ); -pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { RNG_SOURCE.with(|result| { let source = result.as_ref().map_err(|&e| e)?; match source { RngSource::Node(n) => { - if n.random_fill_sync(dest).is_err() { - return Err(Error::NODE_RANDOM_FILL_SYNC); + for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) { + // SAFETY: chunk is never used directly, the memory is only + // modified via the Uint8Array view, which is passed + // directly to JavaScript. Also, crypto.randomFillSync does + // not resize the buffer. We know the length is less than + // u32::MAX because of the chunking above. + // Note that this uses the fact that JavaScript doesn't + // have a notion of "uninitialized memory", this is purely + // a Rust/C/C++ concept. + let res = n.random_fill_sync(unsafe { + Uint8Array::view_mut_raw(chunk.as_mut_ptr() as *mut u8, chunk.len()) + }); + if res.is_err() { + return Err(Error::NODE_RANDOM_FILL_SYNC); + } } } RngSource::Web(crypto, buf) => { @@ -49,7 +58,9 @@ pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { if crypto.get_random_values(&sub_buf).is_err() { return Err(Error::WEB_GET_RANDOM_VALUES); } - sub_buf.copy_to(chunk); + + // SAFETY: `sub_buf`'s length is the same length as `chunk` + unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) }; } } }; @@ -120,7 +131,7 @@ extern "C" { type NodeCrypto; // crypto.randomFillSync() #[wasm_bindgen(method, js_name = randomFillSync, catch)] - fn random_fill_sync(this: &NodeCrypto, buf: &mut [u8]) -> Result<(), JsValue>; + fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>; // Ideally, we would just use `fn require(s: &str)` here. However, doing // this causes a Webpack warning. So we instead return the function itself diff --git a/src/lazy.rs b/src/lazy.rs new file mode 100644 index 0000000..b9c2f88 --- /dev/null +++ b/src/lazy.rs @@ -0,0 +1,56 @@ +use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + +// This structure represents a lazily initialized static usize value. Useful +// when it is preferable to just rerun initialization instead of locking. +// Both unsync_init and sync_init will invoke an init() function until it +// succeeds, then return the cached value for future calls. +// +// Both methods support init() "failing". If the init() method returns UNINIT, +// that value will be returned as normal, but will not be cached. +// +// Users should only depend on the _value_ returned by init() functions. +// Specifically, for the following init() function: +// fn init() -> usize { +// a(); +// let v = b(); +// c(); +// v +// } +// the effects of c() or writes to shared memory will not necessarily be +// observed and additional synchronization methods with be needed. +pub(crate) struct LazyUsize(AtomicUsize); + +impl LazyUsize { + pub const fn new() -> Self { + Self(AtomicUsize::new(Self::UNINIT)) + } + + // The initialization is not completed. + pub const UNINIT: usize = usize::max_value(); + + // Runs the init() function at least once, returning the value of some run + // of init(). Multiple callers can run their init() functions in parallel. + // init() should always return the same value, if it succeeds. + pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { + // Relaxed ordering is fine, as we only have a single atomic variable. + let mut val = self.0.load(Relaxed); + if val == Self::UNINIT { + val = init(); + self.0.store(val, Relaxed); + } + val + } +} + +// Identical to LazyUsize except with bool instead of usize. +pub(crate) struct LazyBool(LazyUsize); + +impl LazyBool { + pub const fn new() -> Self { + Self(LazyUsize::new()) + } + + pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool { + self.0.unsync_init(|| init() as usize) != 0 + } +} diff --git a/src/lib.rs b/src/lib.rs index 67325a3..e80ec6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,3 @@ -// Copyright 2019 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Interface to the operating system's random number generator. //! //! # Supported targets @@ -14,25 +6,29 @@ //! | ----------------- | ------------------ | -------------- //! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` //! | Windows | `*‑windows‑*` | [`BCryptGenRandom`] -//! | macOS | `*‑apple‑darwin` | [`getentropy`][3] if available, otherwise [`/dev/random`][4] (identical to `/dev/urandom`) -//! | iOS | `*‑apple‑ios` | [`SecRandomCopyBytes`] +//! | macOS | `*‑apple‑darwin` | [`getentropy`][3] +//! | iOS, tvOS, watchOS | `*‑apple‑ios`, `*-apple-tvos`, `*-apple-watchos` | [`CCRandomGenerateBytes`] //! | FreeBSD | `*‑freebsd` | [`getrandom`][5] if available, otherwise [`kern.arandom`][6] //! | OpenBSD | `*‑openbsd` | [`getentropy`][7] -//! | NetBSD | `*‑netbsd` | [`kern.arandom`][8] -//! | Dragonfly BSD | `*‑dragonfly` | [`getrandom`][9] if available, otherwise [`/dev/random`][10] +//! | NetBSD | `*‑netbsd` | [`getrandom`][16] if available, otherwise [`kern.arandom`][8] +//! | Dragonfly BSD | `*‑dragonfly` | [`getrandom`][9] if available, otherwise [`/dev/urandom`][10] (identical to `/dev/random`) //! | Solaris, illumos | `*‑solaris`, `*‑illumos` | [`getrandom`][11] if available, otherwise [`/dev/random`][12] //! | Fuchsia OS | `*‑fuchsia` | [`cprng_draw`] //! | Redox | `*‑redox` | `/dev/urandom` -//! | Haiku | `*‑haiku` | `/dev/random` (identical to `/dev/urandom`) -//! | Hermit | `x86_64-*-hermit` | [`RDRAND`] +//! | 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` | `/dev/random` (identical to `/dev/urandom`) +//! | Emscripten | `*‑emscripten` | [`getentropy`][13] //! | WASI | `wasm32‑wasi` | [`random_get`] -//! | Web Browser and Node.js | `wasm32‑*‑unknown` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js, see [WebAssembly support] +//! | 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 | `armv6k-nintendo-3ds` | [`getrandom`][1] +//! | PS Vita | `armv7-sony-vita-newlibeabihf` | [`getentropy`][13] +//! | QNX Neutrino | `*‑nto-qnx*` | [`/dev/urandom`][14] (identical to `/dev/random`) +//! | AIX | `*-ibm-aix` | [`/dev/urandom`][15] //! //! There is no blanket implementation on `unix` targets that reads from //! `/dev/urandom`. This ensures all supported targets are using the recommended @@ -102,6 +98,13 @@ //! ``` //! This crate will then use the provided `webcrypto` implementation. //! +//! ### 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 pluggable custom implementations. +//! +//! This means that as Rust drops support for old versions of operating systems (such as old Linux kernel versions, Android API levels, etc) +//! in stable releases, `getrandom` may create new patch releases (`0.N.x`) that remove support for outdated platform versions. +//! //! ### Custom implementations //! //! The [`register_custom_getrandom!`] macro allows a user to mark their own @@ -150,7 +153,7 @@ //! [1]: http://man7.org/linux/man-pages/man2/getrandom.2.html //! [2]: http://man7.org/linux/man-pages/man4/urandom.4.html //! [3]: https://www.unix.com/man-page/mojave/2/getentropy/ -//! [4]: https://www.unix.com/man-page/mojave/4/random/ +//! [4]: https://www.unix.com/man-page/mojave/4/urandom/ //! [5]: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable //! [6]: https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4 //! [7]: https://man.openbsd.org/getentropy.2 @@ -159,11 +162,16 @@ //! [10]: https://leaf.dragonflybsd.org/cgi/web-man?command=random§ion=4 //! [11]: https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html //! [12]: https://docs.oracle.com/cd/E86824_01/html/E54777/random-7d.html +//! [13]: https://github.com/emscripten-core/emscripten/pull/12240 +//! [14]: https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.utilities/topic/r/random.html +//! [15]: https://www.ibm.com/docs/en/aix/7.3?topic=files-random-urandom-devices +//! [16]: https://man.netbsd.org/getrandom.2 +//! [17]: https://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getrandom //! //! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom //! [`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues //! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide -//! [`SecRandomCopyBytes`]: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc +//! [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html //! [`cprng_draw`]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw //! [`crypto.randomFillSync`]: https://nodejs.org/api/crypto.html#cryptorandomfillsyncbuffer-offset-size //! [`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html#_CPPv415esp_fill_randomPv6size_t @@ -173,11 +181,12 @@ //! [`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 #![doc( html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", html_favicon_url = "https://www.rust-lang.org/favicon.ico", - html_root_url = "https://docs.rs/getrandom/0.2.8" + html_root_url = "https://docs.rs/getrandom/0.2.12" )] #![no_std] #![warn(rust_2018_idioms, unused_lifetimes, missing_docs)] @@ -186,6 +195,9 @@ #[macro_use] extern crate cfg_if; +use crate::util::{slice_as_uninit_mut, slice_assume_init_mut}; +use core::mem::MaybeUninit; + mod error; mod util; // To prevent a breaking change when targets are added, we always export the @@ -199,15 +211,19 @@ pub use crate::error::Error; // System-specific implementations. // -// These should all provide getrandom_inner with the same signature as getrandom. +// These should all provide getrandom_inner with the signature +// `fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error>`. +// The function MUST fully initialize `dest` when `Ok(())` is returned. +// The function MUST NOT ever write uninitialized bytes into `dest`, +// regardless of what value it returns. cfg_if! { - if #[cfg(any(target_os = "emscripten", target_os = "haiku", - target_os = "redox"))] { + 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(target_os = "android", target_os = "linux"))] { mod util_libc; mod use_file; + mod lazy; #[path = "linux_android.rs"] mod imp; } else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] { mod util_libc; @@ -222,19 +238,18 @@ cfg_if! { #[path = "dragonfly.rs"] mod imp; } else if #[cfg(target_os = "fuchsia")] { #[path = "fuchsia.rs"] mod imp; - } else if #[cfg(target_os = "ios")] { - #[path = "ios.rs"] mod imp; + } else if #[cfg(any(target_os = "ios", target_os = "watchos", target_os = "tvos"))] { + #[path = "apple-other.rs"] mod imp; } else if #[cfg(target_os = "macos")] { mod util_libc; - mod use_file; #[path = "macos.rs"] mod imp; } else if #[cfg(target_os = "openbsd")] { mod util_libc; #[path = "openbsd.rs"] mod imp; - } else if #[cfg(target_os = "wasi")] { + } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { #[path = "wasi.rs"] mod imp; - } else if #[cfg(all(target_arch = "x86_64", target_os = "hermit"))] { - #[path = "rdrand.rs"] mod imp; + } else if #[cfg(target_os = "hermit")] { + #[path = "hermit.rs"] mod imp; } else if #[cfg(target_os = "vxworks")] { mod util_libc; #[path = "vxworks.rs"] mod imp; @@ -244,23 +259,36 @@ cfg_if! { #[path = "espidf.rs"] mod imp; } else if #[cfg(windows)] { #[path = "windows.rs"] mod imp; + } else if #[cfg(all(target_os = "horizon", target_arch = "arm"))] { + // We check for target_arch = "arm" because the Nintendo Switch also + // uses Horizon OS (it is aarch64). + mod util_libc; + #[path = "3ds.rs"] mod imp; + } else if #[cfg(target_os = "vita")] { + mod util_libc; + #[path = "vita.rs"] mod imp; + } else if #[cfg(target_os = "emscripten")] { + mod util_libc; + #[path = "emscripten.rs"] mod imp; } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { + mod lazy; #[path = "rdrand.rs"] mod imp; } else if #[cfg(all(feature = "rdrand", any(target_arch = "x86_64", target_arch = "x86")))] { + mod lazy; #[path = "rdrand.rs"] mod imp; } else if #[cfg(all(feature = "js", - target_arch = "wasm32", target_os = "unknown"))] { + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown"))] { #[path = "js.rs"] mod imp; - } else if #[cfg(all(target_os = "horizon", target_arch = "arm"))] { - // We check for target_arch = "arm" because the Nintendo Switch also - // uses Horizon OS (it is aarch64). + } else if #[cfg(target_os = "hurd")] { mod util_libc; - #[path = "3ds.rs"] mod imp; + #[path = "hurd.rs"] mod imp; } else if #[cfg(feature = "custom")] { use custom as imp; - } else if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] { - compile_error!("the wasm32-unknown-unknown target is not supported by \ + } 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: \ https://docs.rs/getrandom/#webassembly-support"); @@ -283,9 +311,42 @@ cfg_if! { /// 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). +#[inline] pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { - if dest.is_empty() { - return Ok(()); + // SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, and + // `getrandom_uninit` guarantees it will never de-initialize any part of + // `dest`. + getrandom_uninit(unsafe { slice_as_uninit_mut(dest) })?; + Ok(()) +} + +/// Version of the `getrandom` function which fills `dest` with random bytes +/// returns a mutable reference to those bytes. +/// +/// On successful completion this function is guaranteed to return a slice +/// which points to the same memory as `dest` and has the same length. +/// In other words, it's safe to assume that `dest` is initialized after +/// this function has returned `Ok`. +/// +/// No part of `dest` will ever be de-initialized at any point, regardless +/// of what is returned. +/// +/// # Examples +/// +/// ```ignore +/// # // We ignore this test since `uninit_array` is unstable. +/// #![feature(maybe_uninit_uninit_array)] +/// # fn main() -> Result<(), getrandom::Error> { +/// let mut buf = core::mem::MaybeUninit::uninit_array::<1024>(); +/// let buf: &mut [u8] = getrandom::getrandom_uninit(&mut buf)?; +/// # Ok(()) } +/// ``` +#[inline] +pub fn getrandom_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { + if !dest.is_empty() { + imp::getrandom_inner(dest)?; } - imp::getrandom_inner(dest) + // SAFETY: `dest` has been fully initialized by `imp::getrandom_inner` + // since it returned `Ok`. + Ok(unsafe { slice_assume_init_mut(dest) }) } diff --git a/src/linux_android.rs b/src/linux_android.rs index 4270b67..2517159 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -1,19 +1,12 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for Linux / Android use crate::{ - util::LazyBool, + lazy::LazyBool, util_libc::{last_os_error, sys_fill_exact}, {use_file, Error}, }; +use core::mem::MaybeUninit; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getrandom(2) was introduced in Linux 3.17 static HAS_GETRANDOM: LazyBool = LazyBool::new(); if HAS_GETRANDOM.unsync_init(is_getrandom_available) { diff --git a/src/macos.rs b/src/macos.rs index 671a053..44af76b 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -1,36 +1,18 @@ -// Copyright 2019 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for macOS -use crate::{ - use_file, - util_libc::{last_os_error, Weak}, - Error, -}; -use core::mem; +use crate::{util_libc::last_os_error, Error}; +use core::mem::MaybeUninit; -type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int; +extern "C" { + // Supported as of macOS 10.12+. + fn getentropy(buf: *mut u8, size: libc::size_t) -> libc::c_int; +} -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - // getentropy(2) was added in 10.12, Rust supports 10.7+ - static GETENTROPY: Weak = unsafe { Weak::new("getentropy\0") }; - if let Some(fptr) = GETENTROPY.ptr() { - let func: GetEntropyFn = unsafe { mem::transmute(fptr) }; - for chunk in dest.chunks_mut(256) { - let ret = unsafe { func(chunk.as_mut_ptr(), chunk.len()) }; - if ret != 0 { - return Err(last_os_error()); - } +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(256) { + let ret = unsafe { getentropy(chunk.as_mut_ptr() as *mut u8, chunk.len()) }; + if ret != 0 { + return Err(last_os_error()); } - Ok(()) - } else { - // We fallback to reading from /dev/random instead of SecRandomCopyBytes - // to avoid high startup costs and linking the Security framework. - use_file::getrandom_inner(dest) } + Ok(()) } diff --git a/src/openbsd.rs b/src/openbsd.rs index 4137173..f4d64da 100644 --- a/src/openbsd.rs +++ b/src/openbsd.rs @@ -1,15 +1,8 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for OpenBSD use crate::{util_libc::last_os_error, Error}; +use core::mem::MaybeUninit; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getentropy(2) was added in OpenBSD 5.6, so we can use it unconditionally. for chunk in dest.chunks_mut(256) { let ret = unsafe { libc::getentropy(chunk.as_mut_ptr() as *mut libc::c_void, chunk.len()) }; diff --git a/src/rdrand.rs b/src/rdrand.rs index 1df21e5..f527c8c 100644 --- a/src/rdrand.rs +++ b/src/rdrand.rs @@ -1,14 +1,6 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Implementation for SGX using RDRAND instruction -use crate::Error; -use core::mem; +//! RDRAND backend for x86(-64) targets +use crate::{lazy::LazyBool, util::slice_as_uninit, Error}; +use core::mem::{size_of, MaybeUninit}; cfg_if! { if #[cfg(target_arch = "x86_64")] { @@ -24,74 +16,106 @@ cfg_if! { // Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures // Software Developer’s Manual" - Volume 1 - Section 7.3.17.1. const RETRY_LIMIT: usize = 10; -const WORD_SIZE: usize = mem::size_of::(); #[target_feature(enable = "rdrand")] -unsafe fn rdrand() -> Result<[u8; WORD_SIZE], Error> { +unsafe fn rdrand() -> Option { for _ in 0..RETRY_LIMIT { - let mut el = mem::zeroed(); - if rdrand_step(&mut el) == 1 { - // AMD CPUs from families 14h to 16h (pre Ryzen) sometimes fail to - // set CF on bogus random data, so we check these values explicitly. - // See https://github.com/systemd/systemd/issues/11810#issuecomment-489727505 - // We perform this check regardless of target to guard against - // any implementation that incorrectly fails to set CF. - if el != 0 && el != !0 { - return Ok(el.to_ne_bytes()); - } - // Keep looping in case this was a false positive. + let mut val = 0; + if rdrand_step(&mut val) == 1 { + return Some(val as usize); } } - Err(Error::FAILED_RDRAND) + None } -// "rdrand" target feature requires "+rdrnd" flag, see https://github.com/rust-lang/rust/issues/49653. +// "rdrand" target feature requires "+rdrand" flag, see https://github.com/rust-lang/rust/issues/49653. #[cfg(all(target_env = "sgx", not(target_feature = "rdrand")))] compile_error!( - "SGX targets require 'rdrand' target feature. Enable by using -C target-feature=+rdrnd." + "SGX targets require 'rdrand' target feature. Enable by using -C target-feature=+rdrand." ); -#[cfg(target_feature = "rdrand")] -fn is_rdrand_supported() -> bool { - true +// Run a small self-test to make sure we aren't repeating values +// Adapted from Linux's test in arch/x86/kernel/cpu/rdrand.c +// Fails with probability < 2^(-90) on 32-bit systems +#[target_feature(enable = "rdrand")] +unsafe fn self_test() -> bool { + // On AMD, RDRAND returns 0xFF...FF on failure, count it as a collision. + let mut prev = !0; // TODO(MSRV 1.43): Move to usize::MAX + let mut fails = 0; + for _ in 0..8 { + match rdrand() { + Some(val) if val == prev => fails += 1, + Some(val) => prev = val, + None => return false, + }; + } + fails <= 2 } -// TODO use is_x86_feature_detected!("rdrand") when that works in core. See: -// https://github.com/rust-lang-nursery/stdsimd/issues/464 -#[cfg(not(target_feature = "rdrand"))] -fn is_rdrand_supported() -> bool { - use crate::util::LazyBool; +fn is_rdrand_good() -> bool { + #[cfg(not(target_feature = "rdrand"))] + { + // SAFETY: All Rust x86 targets are new enough to have CPUID, and we + // check that leaf 1 is supported before using it. + let cpuid0 = unsafe { arch::__cpuid(0) }; + if cpuid0.eax < 1 { + return false; + } + let cpuid1 = unsafe { arch::__cpuid(1) }; - // SAFETY: All Rust x86 targets are new enough to have CPUID, and if CPUID - // is supported, CPUID leaf 1 is always supported. - const FLAG: u32 = 1 << 30; - static HAS_RDRAND: LazyBool = LazyBool::new(); - HAS_RDRAND.unsync_init(|| unsafe { (arch::__cpuid(1).ecx & FLAG) != 0 }) + let vendor_id = [ + cpuid0.ebx.to_le_bytes(), + cpuid0.edx.to_le_bytes(), + cpuid0.ecx.to_le_bytes(), + ]; + if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] { + let mut family = (cpuid1.eax >> 8) & 0xF; + if family == 0xF { + family += (cpuid1.eax >> 20) & 0xFF; + } + // AMD CPUs families before 17h (Zen) sometimes fail to set CF when + // RDRAND fails after suspend. Don't use RDRAND on those families. + // See https://bugzilla.redhat.com/show_bug.cgi?id=1150286 + if family < 0x17 { + return false; + } + } + + const RDRAND_FLAG: u32 = 1 << 30; + if cpuid1.ecx & RDRAND_FLAG == 0 { + return false; + } + } + + // SAFETY: We have already checked that rdrand is available. + unsafe { self_test() } } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - if !is_rdrand_supported() { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + static RDRAND_GOOD: LazyBool = LazyBool::new(); + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { return Err(Error::NO_RDRAND); } - - // SAFETY: After this point, rdrand is supported, so calling the rdrand - // functions is not undefined behavior. - unsafe { rdrand_exact(dest) } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) } +// TODO: make this function safe when we have feature(target_feature_11) #[target_feature(enable = "rdrand")] -unsafe fn rdrand_exact(dest: &mut [u8]) -> Result<(), Error> { +unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Option<()> { // We use chunks_exact_mut instead of chunks_mut as it allows almost all // calls to memcpy to be elided by the compiler. - let mut chunks = dest.chunks_exact_mut(WORD_SIZE); + let mut chunks = dest.chunks_exact_mut(size_of::()); for chunk in chunks.by_ref() { - chunk.copy_from_slice(&rdrand()?); + let src = rdrand()?.to_ne_bytes(); + chunk.copy_from_slice(slice_as_uninit(&src)); } let tail = chunks.into_remainder(); let n = tail.len(); if n > 0 { - tail.copy_from_slice(&rdrand()?[..n]); + let src = rdrand()?.to_ne_bytes(); + tail.copy_from_slice(slice_as_uninit(&src[..n])); } - Ok(()) + Some(()) } diff --git a/src/solaris_illumos.rs b/src/solaris_illumos.rs index cf3067d..fbc2394 100644 --- a/src/solaris_illumos.rs +++ b/src/solaris_illumos.rs @@ -1,19 +1,10 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for the Solaris family //! -//! Read from `/dev/random`, with chunks of limited size (256 bytes). //! `/dev/random` uses the Hash_DRBG with SHA512 algorithm from NIST SP 800-90A. //! `/dev/urandom` uses the FIPS 186-2 algorithm, which is considered less -//! secure. We choose to read from `/dev/random`. +//! secure. We choose to read from `/dev/random` (and use GRND_RANDOM). //! -//! Since Solaris 11.3 and mid-2015 illumos, the `getrandom` syscall is available. +//! Solaris 11.3 and late-2018 illumos added the getrandom(2) libc function. //! To make sure we can compile on both Solaris and its derivatives, as well as //! function, we check for the existence of getrandom(2) in libc by calling //! libc::dlsym. @@ -22,23 +13,25 @@ use crate::{ util_libc::{sys_fill_exact, Weak}, Error, }; -use core::mem; +use core::mem::{self, MaybeUninit}; -#[cfg(target_os = "illumos")] -type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; -#[cfg(target_os = "solaris")] -type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::c_int; +static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; +type GetRandomFn = + unsafe extern "C" fn(*mut libc::c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - // getrandom(2) was introduced in Solaris 11.3 for Illumos in 2015. - static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { if let Some(fptr) = GETRANDOM.ptr() { let func: GetRandomFn = unsafe { mem::transmute(fptr) }; // 256 bytes is the lowest common denominator across all the Solaris // derived platforms for atomically obtaining random data. for chunk in dest.chunks_mut(256) { sys_fill_exact(chunk, |buf| unsafe { - func(buf.as_mut_ptr(), buf.len(), 0) as libc::ssize_t + // A cast is needed for the flags as libc uses the wrong type. + func( + buf.as_mut_ptr() as *mut libc::c_void, + buf.len(), + libc::GRND_RANDOM as libc::c_uint, + ) })? } Ok(()) diff --git a/src/solid.rs b/src/solid.rs index dc76aac..cae8caf 100644 --- a/src/solid.rs +++ b/src/solid.rs @@ -1,21 +1,13 @@ -// Copyright 2021 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for SOLID use crate::Error; -use core::num::NonZeroU32; +use core::{mem::MaybeUninit, num::NonZeroU32}; extern "C" { pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32; } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr(), dest.len()) }; +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr() as *mut u8, dest.len()) }; if ret >= 0 { Ok(()) } else { diff --git a/src/use_file.rs b/src/use_file.rs index 16c0216..333325b 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -1,57 +1,47 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementations that just need to read from a file use crate::{ - util::LazyUsize, util_libc::{open_readonly, sys_fill_exact}, Error, }; use core::{ cell::UnsafeCell, + mem::MaybeUninit, sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; +// We prefer using /dev/urandom and only use /dev/random if the OS +// documentation indicates that /dev/urandom is insecure. +// On Solaris/Illumos, see src/solaris_illumos.rs +// On Dragonfly, Haiku, and QNX Neutrino the devices are identical. +#[cfg(any(target_os = "solaris", target_os = "illumos"))] +const FILE_PATH: &str = "/dev/random\0"; #[cfg(any( + target_os = "aix", + target_os = "android", + target_os = "linux", + target_os = "redox", target_os = "dragonfly", - target_os = "emscripten", target_os = "haiku", - target_os = "macos", - target_os = "solaris", - target_os = "illumos" + target_os = "nto", ))] -const FILE_PATH: &str = "/dev/random\0"; -#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] const FILE_PATH: &str = "/dev/urandom\0"; +const FD_UNINIT: usize = usize::max_value(); -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let fd = get_rng_fd()?; - let read = |buf: &mut [u8]| unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) }; - - if cfg!(target_os = "emscripten") { - // `Crypto.getRandomValues` documents `dest` should be at most 65536 bytes. - for chunk in dest.chunks_mut(65536) { - sys_fill_exact(chunk, read)?; - } - } else { - sys_fill_exact(dest, read)?; - } - Ok(()) + sys_fill_exact(dest, |buf| unsafe { + libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) + }) } // Returns the file descriptor for the device file used to retrieve random // bytes. The file will be opened exactly once. All subsequent calls will // return the same file descriptor. This file descriptor is never closed. fn get_rng_fd() -> Result { - static FD: AtomicUsize = AtomicUsize::new(LazyUsize::UNINIT); + static FD: AtomicUsize = AtomicUsize::new(FD_UNINIT); fn get_fd() -> Option { match FD.load(Relaxed) { - LazyUsize::UNINIT => None, + FD_UNINIT => None, val => Some(val as libc::c_int), } } @@ -76,8 +66,8 @@ fn get_rng_fd() -> Result { wait_until_rng_ready()?; let fd = unsafe { open_readonly(FILE_PATH)? }; - // The fd always fits in a usize without conflicting with UNINIT. - debug_assert!(fd >= 0 && (fd as usize) < LazyUsize::UNINIT); + // The fd always fits in a usize without conflicting with FD_UNINIT. + debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT); FD.store(fd as usize, Relaxed); Ok(fd) diff --git a/src/util.rs b/src/util.rs index 06e23c2..1c4e70b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,64 +1,35 @@ -// Copyright 2019 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. #![allow(dead_code)] -use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; +use core::{mem::MaybeUninit, ptr}; -// This structure represents a lazily initialized static usize value. Useful -// when it is preferable to just rerun initialization instead of locking. -// Both unsync_init and sync_init will invoke an init() function until it -// succeeds, then return the cached value for future calls. -// -// Both methods support init() "failing". If the init() method returns UNINIT, -// that value will be returned as normal, but will not be cached. -// -// Users should only depend on the _value_ returned by init() functions. -// Specifically, for the following init() function: -// fn init() -> usize { -// a(); -// let v = b(); -// c(); -// v -// } -// the effects of c() or writes to shared memory will not necessarily be -// observed and additional synchronization methods with be needed. -pub struct LazyUsize(AtomicUsize); - -impl LazyUsize { - pub const fn new() -> Self { - Self(AtomicUsize::new(Self::UNINIT)) - } - - // The initialization is not completed. - pub const UNINIT: usize = usize::max_value(); - - // Runs the init() function at least once, returning the value of some run - // of init(). Multiple callers can run their init() functions in parallel. - // init() should always return the same value, if it succeeds. - pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { - // Relaxed ordering is fine, as we only have a single atomic variable. - let mut val = self.0.load(Relaxed); - if val == Self::UNINIT { - val = init(); - self.0.store(val, Relaxed); - } - val - } +/// Polyfill for `maybe_uninit_slice` feature's +/// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have +/// been initialized. +#[inline(always)] +pub unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { + // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. + &mut *(slice as *mut [MaybeUninit] as *mut [T]) } -// Identical to LazyUsize except with bool instead of usize. -pub struct LazyBool(LazyUsize); +#[inline] +pub 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) } +} -impl LazyBool { - pub const fn new() -> Self { - Self(LazyUsize::new()) - } +#[inline(always)] +pub fn slice_as_uninit(slice: &[T]) -> &[MaybeUninit] { + // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. + // There is no risk of writing a `MaybeUninit` into the result since + // the result isn't mutable. + unsafe { &*(slice as *const [T] as *const [MaybeUninit]) } +} - pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool { - self.0.unsync_init(|| init() as usize) != 0 - } +/// 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)] +pub unsafe fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { + // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. + &mut *(slice as *mut [T] as *mut [MaybeUninit]) } diff --git a/src/util_libc.rs b/src/util_libc.rs index d057071..0b792c3 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -1,13 +1,7 @@ -// Copyright 2019 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. #![allow(dead_code)] use crate::Error; use core::{ + mem::MaybeUninit, num::NonZeroU32, ptr::NonNull, sync::atomic::{fence, AtomicPtr, Ordering}, @@ -17,7 +11,7 @@ use libc::c_void; cfg_if! { if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android"))] { use libc::__errno as errno_location; - } else if #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "redox"))] { + } else if #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "hurd", target_os = "redox"))] { use libc::__errno_location as errno_location; } else if #[cfg(any(target_os = "solaris", target_os = "illumos"))] { use libc::___errno as errno_location; @@ -25,12 +19,16 @@ cfg_if! { use libc::__error as errno_location; } else if #[cfg(target_os = "haiku")] { use libc::_errnop as errno_location; - } else if #[cfg(all(target_os = "horizon", target_arch = "arm"))] { + } else if #[cfg(target_os = "nto")] { + use libc::__get_errno_ptr as errno_location; + } else if #[cfg(any(all(target_os = "horizon", target_arch = "arm"), target_os = "vita"))] { extern "C" { // Not provided by libc: https://github.com/rust-lang/libc/issues/1995 fn __errno() -> *mut libc::c_int; } use __errno as errno_location; + } else if #[cfg(target_os = "aix")] { + use libc::_Errno as errno_location; } } @@ -59,21 +57,24 @@ pub fn last_os_error() -> Error { // - should return -1 and set errno on failure // - should return the number of bytes written on success pub fn sys_fill_exact( - mut buf: &mut [u8], - sys_fill: impl Fn(&mut [u8]) -> libc::ssize_t, + mut buf: &mut [MaybeUninit], + sys_fill: impl Fn(&mut [MaybeUninit]) -> libc::ssize_t, ) -> Result<(), Error> { while !buf.is_empty() { let res = sys_fill(buf); - if res < 0 { - let err = last_os_error(); - // We should try again if the call was interrupted. - if err.raw_os_error() != Some(libc::EINTR) { - return Err(err); + match res { + res if res > 0 => buf = buf.get_mut(res as usize..).ok_or(Error::UNEXPECTED)?, + -1 => { + let err = last_os_error(); + // We should try again if the call was interrupted. + if err.raw_os_error() != Some(libc::EINTR) { + return Err(err); + } } - } else { - // We don't check for EOF (ret = 0) as the data we are reading + // Negative return codes not equal to -1 should be impossible. + // EOF (ret = 0) should be impossible, as the data we are reading // should be an infinite stream of random bytes. - buf = &mut buf[(res as usize)..]; + _ => return Err(Error::UNEXPECTED), } } Ok(()) @@ -135,19 +136,11 @@ impl Weak { } } -cfg_if! { - if #[cfg(any(target_os = "linux", target_os = "emscripten"))] { - use libc::open64 as open; - } else { - use libc::open; - } -} - // SAFETY: path must be null terminated, FD must be manually closed. pub unsafe fn open_readonly(path: &str) -> Result { debug_assert_eq!(path.as_bytes().last(), Some(&0)); loop { - let fd = open(path.as_ptr() as *const _, libc::O_RDONLY | libc::O_CLOEXEC); + let fd = libc::open(path.as_ptr() as *const _, libc::O_RDONLY | libc::O_CLOEXEC); if fd >= 0 { return Ok(fd); } diff --git a/src/vita.rs b/src/vita.rs new file mode 100644 index 0000000..20a9878 --- /dev/null +++ b/src/vita.rs @@ -0,0 +1,13 @@ +//! Implementation for PS Vita +use crate::{util_libc::last_os_error, Error}; +use core::mem::MaybeUninit; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(256) { + let ret = unsafe { libc::getentropy(chunk.as_mut_ptr() as *mut libc::c_void, chunk.len()) }; + if ret == -1 { + return Err(last_os_error()); + } + } + Ok(()) +} diff --git a/src/vxworks.rs b/src/vxworks.rs index 6cb5d52..7ca9d6b 100644 --- a/src/vxworks.rs +++ b/src/vxworks.rs @@ -1,16 +1,11 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for VxWorks use crate::{util_libc::last_os_error, Error}; -use core::sync::atomic::{AtomicBool, Ordering::Relaxed}; +use core::{ + mem::MaybeUninit, + sync::atomic::{AtomicBool, Ordering::Relaxed}, +}; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { static RNG_INIT: AtomicBool = AtomicBool::new(false); while !RNG_INIT.load(Relaxed) { let ret = unsafe { libc::randSecure() }; @@ -25,7 +20,7 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { // Prevent overflow of i32 for chunk in dest.chunks_mut(i32::max_value() as usize) { - let ret = unsafe { libc::randABytes(chunk.as_mut_ptr(), chunk.len() as i32) }; + let ret = unsafe { libc::randABytes(chunk.as_mut_ptr() as *mut u8, chunk.len() as i32) }; if ret != 0 { return Err(last_os_error()); } diff --git a/src/wasi.rs b/src/wasi.rs index c512182..d6c8a91 100644 --- a/src/wasi.rs +++ b/src/wasi.rs @@ -1,19 +1,17 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - //! Implementation for WASI use crate::Error; -use core::num::NonZeroU32; -use wasi::wasi_snapshot_preview1::random_get; +use core::{ + mem::MaybeUninit, + num::{NonZeroU16, NonZeroU32}, +}; +use wasi::random_get; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - match unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) } { - 0 => Ok(()), - err => Err(unsafe { NonZeroU32::new_unchecked(err as u32) }.into()), - } +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + unsafe { random_get(dest.as_mut_ptr() as *mut u8, dest.len()) }.map_err(|e| { + // The WASI errno will always be non-zero, but we check just in case. + match NonZeroU16::new(e.raw()) { + Some(r) => Error::from(NonZeroU32::from(r)), + None => Error::ERRNO_NOT_POSITIVE, + } + }) } diff --git a/src/windows.rs b/src/windows.rs index 41dc37a..2d1c483 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,13 +1,6 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - +//! Implementation for Windows use crate::Error; -use core::{ffi::c_void, num::NonZeroU32, ptr}; +use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr}; const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; @@ -21,20 +14,37 @@ extern "system" { ) -> u32; } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +// Forbidden when targetting UWP +#[cfg(not(target_vendor = "uwp"))] +#[link(name = "advapi32")] +extern "system" { + #[link_name = "SystemFunction036"] + fn RtlGenRandom(RandomBuffer: *mut c_void, RandomBufferLength: u32) -> u8; +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // Prevent overflow of u32 for chunk in dest.chunks_mut(u32::max_value() as usize) { // BCryptGenRandom was introduced in Windows Vista let ret = unsafe { BCryptGenRandom( ptr::null_mut(), - chunk.as_mut_ptr(), + chunk.as_mut_ptr() as *mut u8, chunk.len() as u32, BCRYPT_USE_SYSTEM_PREFERRED_RNG, ) }; // NTSTATUS codes use the two highest bits for severity status. if ret >> 30 == 0b11 { + // Failed. Try RtlGenRandom as a fallback. + #[cfg(not(target_vendor = "uwp"))] + { + let ret = + unsafe { RtlGenRandom(chunk.as_mut_ptr() as *mut c_void, chunk.len() as u32) }; + if ret != 0 { + continue; + } + } // We zeroize the highest bit, so the error code will reside // inside the range designated for OS codes. let code = ret ^ (1 << 31); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 006f230..666f7f5 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -12,7 +12,19 @@ fn test_zero() { 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(); @@ -20,13 +32,35 @@ fn test_diff() { let mut v2 = [0u8; 1000]; getrandom_impl(&mut v2).unwrap(); - let mut n_diff_bits = 0; - for i in 0..v1.len() { - n_diff_bits += (v1[i] ^ v2[i]).count_ones(); - } + // 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); +} - // Check at least 1 bit per byte differs. p(failure) < 1e-1000 with random input. - assert!(n_diff_bits >= v1.len() as u32); +// 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] diff --git a/tests/custom.rs b/tests/custom.rs index 62eae1d..b085094 100644 --- a/tests/custom.rs +++ b/tests/custom.rs @@ -7,13 +7,8 @@ ))] use wasm_bindgen_test::wasm_bindgen_test as test; -#[cfg(feature = "test-in-browser")] -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); -use core::{ - num::NonZeroU32, - sync::atomic::{AtomicU8, Ordering}, -}; +use core::num::NonZeroU32; use getrandom::{getrandom, register_custom_getrandom, Error}; fn len7_err() -> Error { @@ -21,27 +16,36 @@ fn len7_err() -> Error { } fn super_insecure_rng(buf: &mut [u8]) -> Result<(), Error> { + // `getrandom` guarantees it will not call any implementation if the output + // buffer is empty. + assert!(!buf.is_empty()); // Length 7 buffers return a custom error if buf.len() == 7 { return Err(len7_err()); } - // Otherwise, increment an atomic counter - static COUNTER: AtomicU8 = AtomicU8::new(0); + // Otherwise, fill bytes based on input length + let mut start = buf.len() as u8; for b in buf { - *b = COUNTER.fetch_add(1, Ordering::Relaxed); + *b = start; + start = start.wrapping_mul(3); } Ok(()) } register_custom_getrandom!(super_insecure_rng); +use getrandom::getrandom as getrandom_impl; +mod common; + #[test] fn custom_rng_output() { let mut buf = [0u8; 4]; assert_eq!(getrandom(&mut buf), Ok(())); - assert_eq!(buf, [0, 1, 2, 3]); + assert_eq!(buf, [4, 12, 36, 108]); + + let mut buf = [0u8; 3]; assert_eq!(getrandom(&mut buf), Ok(())); - assert_eq!(buf, [4, 5, 6, 7]); + assert_eq!(buf, [3, 9, 27]); } #[test] diff --git a/tests/rdrand.rs b/tests/rdrand.rs index 4ff85c4..a355c31 100644 --- a/tests/rdrand.rs +++ b/tests/rdrand.rs @@ -6,10 +6,17 @@ 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; -use rdrand::getrandom_inner as getrandom_impl; +// 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; -- cgit v1.2.3