From 2bb9a7d4b632c3f3d8ca26ddeb0a86156a32b157 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 11 Oct 2022 18:39:26 -0700 Subject: [PATCH 1/7] Add `getrandom_uninit(dest: &mut [MaybeUninit]) -> ...`. Add a public API for filling an `&mut [MaybeUninit]`. This will primarily serve as the building block for more typeful APIs for constructing random arrays. Increase the MSRV to 1.36, as `MaybeUninit` was added in that release. Fixes #226. --- .github/workflows/tests.yml | 2 +- CHANGELOG.md | 9 ++++ README.md | 2 +- benches/mod.rs | 102 ++++++++++++------------------------ src/3ds.rs | 3 +- src/bsd_arandom.rs | 10 ++-- src/custom.rs | 12 +++-- src/dragonfly.rs | 3 +- src/espidf.rs | 4 +- src/fuchsia.rs | 5 +- src/ios.rs | 6 +-- src/js.rs | 14 +++-- src/lib.rs | 48 +++++++++++++++-- src/linux_android.rs | 3 +- src/macos.rs | 6 +-- src/openbsd.rs | 2 +- src/rdrand.rs | 12 ++--- src/solaris_illumos.rs | 2 +- src/solid.rs | 6 +-- src/use_file.rs | 7 ++- src/util.rs | 39 +++++++++++++- src/util_libc.rs | 5 +- src/vxworks.rs | 9 ++-- src/wasi.rs | 4 +- src/windows.rs | 6 +-- tests/common/mod.rs | 19 ++++--- tests/normal.rs | 2 +- 27 files changed, 211 insertions(+), 131 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8ca2d01c..210d67ba 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - toolchain: [nightly, beta, stable, 1.34] + toolchain: [nightly, beta, stable, 1.36] # Only Test macOS on stable to reduce macOS CI jobs include: - os: macos-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cf9a584..182236c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ 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). +## Unreleased +### Added +- `getrandom_uninit` [#291] + +### Breaking Changes +- Update MSRV to 1.36 [#291] + +[#291]: https://github.com/rust-random/getrandom/pull/291 + ## [0.2.8] - 2022-10-20 ### Changed - The [Web Cryptography API] will now be preferred on `wasm32-unknown-unknown` diff --git a/README.md b/README.md index df2307b9..f43666ad 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ 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 diff --git a/benches/mod.rs b/benches/mod.rs index 11be47eb..07474b66 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -1,94 +1,58 @@ #![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()) } - } -} +use std::mem::MaybeUninit; // 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(); +fn bench_getrandom(b: &mut test::Bencher) { b.iter(|| { + let mut buf = [0u8; N]; 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(); +fn bench_getrandom_uninit(b: &mut test::Bencher) { b.iter(|| { - for byte in buf.iter_mut() { - *byte = 0; - } - getrandom::getrandom(&mut buf[..]).unwrap(); + // TODO: When the feature `maybe_uninit_as_bytes` is available, use: + // ``` + // let mut buf: MaybeUninit<[u8; N]> = MaybeUninit::uninit(); + // getrandom::getrandom_uninit(buf.as_bytes_mut()).unwrap(); + // test::black_box(unsafe { buf.assume_init() }) + // ``` + // since that is the shape we expect most callers to have. + let mut buf = [MaybeUninit::new(0u8); N]; + let buf = getrandom::getrandom_uninit(&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; +macro_rules! bench { + ( $name:ident, $size:expr ) => { + pub mod $name { + #[bench] + pub fn bench_getrandom(b: &mut test::Bencher) { + super::bench_getrandom::<{ $size }>(b); + } -#[bench] -fn bench_seed(b: &mut test::Bencher) { - bench::(b); -} -#[bench] -fn bench_seed_init(b: &mut test::Bencher) { - bench_with_init::(b); + #[bench] + pub fn bench_getrandom_uninit(b: &mut test::Bencher) { + super::bench_getrandom_uninit::<{ $size }>(b); + } + } + }; } -#[bench] -fn bench_page(b: &mut test::Bencher) { - bench::(b); -} -#[bench] -fn bench_page_init(b: &mut test::Bencher) { - bench_with_init::(b); -} +// 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); -#[bench] -fn bench_large(b: &mut test::Bencher) { - bench::(b); -} -#[bench] -fn bench_large_init(b: &mut test::Bencher) { - bench_with_init::(b); -} +// A P-384/HMAC-384 key and/or nonce. +bench!(p384, 384 / 8); diff --git a/src/3ds.rs b/src/3ds.rs index 60305127..87a32a1e 100644 --- a/src/3ds.rs +++ b/src/3ds.rs @@ -9,8 +9,9 @@ //! 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/bsd_arandom.rs b/src/bsd_arandom.rs index d4412125..3539dd5d 100644 --- a/src/bsd_arandom.rs +++ b/src/bsd_arandom.rs @@ -8,9 +8,9 @@ //! Implementation for FreeBSD and NetBSD use crate::{util_libc::sys_fill_exact, Error}; -use core::ptr; +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,7 +30,7 @@ fn kern_arnd(buf: &mut [u8]) -> libc::ssize_t { } } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getrandom(2) was introduced in FreeBSD 12.0 and NetBSD 10.0 #[cfg(target_os = "freebsd")] { @@ -41,7 +41,9 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { 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) + }); } } // Both FreeBSD and NetBSD will only return up to 256 bytes at a time, and diff --git a/src/custom.rs b/src/custom.rs index 8432dfd2..35771f15 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -7,8 +7,8 @@ // 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. /// @@ -90,10 +90,16 @@ macro_rules! register_custom_getrandom { } #[allow(dead_code)] -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { extern "C" { 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 8daaa404..c3701cc4 100644 --- a/src/dragonfly.rs +++ b/src/dragonfly.rs @@ -12,8 +12,9 @@ use crate::{ util_libc::{sys_fill_exact, Weak}, Error, }; +use std::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; diff --git a/src/espidf.rs b/src/espidf.rs index dce8a2aa..d074dc4c 100644 --- a/src/espidf.rs +++ b/src/espidf.rs @@ -8,13 +8,13 @@ //! 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 572ff534..5a135f34 100644 --- a/src/fuchsia.rs +++ b/src/fuchsia.rs @@ -8,13 +8,14 @@ //! 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/ios.rs b/src/ios.rs index 226de16b..8f904859 100644 --- a/src/ios.rs +++ b/src/ios.rs @@ -8,16 +8,16 @@ //! Implementation for iOS use crate::Error; -use core::{ffi::c_void, ptr::null}; +use core::{ffi::c_void, mem::MaybeUninit, 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> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // Apple's documentation guarantees kSecRandomDefault is a synonym for NULL. - let ret = unsafe { SecRandomCopyBytes(null(), dest.len(), dest.as_mut_ptr()) }; + let ret = unsafe { SecRandomCopyBytes(null(), dest.len(), dest.as_mut_ptr() as *mut u8) }; // errSecSuccess (from SecBase.h) is always zero. if ret != 0 { Err(Error::IOS_SEC_RANDOM) diff --git a/src/js.rs b/src/js.rs index 574c4dc3..7b9ae313 100644 --- a/src/js.rs +++ b/src/js.rs @@ -5,10 +5,10 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::Error; +use crate::{util::uninit_slice_fill_zero, 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}; @@ -28,12 +28,16 @@ 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) => { + // XXX(perf): `random_fill_sync` requires a `&mut [u8]` so we + // have to ensure the memory in `dest` is initialized. + let dest = uninit_slice_fill_zero(dest); + if n.random_fill_sync(dest).is_err() { return Err(Error::NODE_RANDOM_FILL_SYNC); } @@ -49,7 +53,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) }; } } }; diff --git a/src/lib.rs b/src/lib.rs index 67325a31..c4809d6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,6 +186,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,7 +202,11 @@ 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"))] { @@ -283,9 +290,40 @@ 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(()); - } - imp::getrandom_inner(dest) + // 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> { + 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 4270b67c..e81f1e15 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -12,8 +12,9 @@ use crate::{ 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 671a053b..312f9b27 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -12,17 +12,17 @@ use crate::{ util_libc::{last_os_error, Weak}, Error, }; -use core::mem; +use core::mem::{self, MaybeUninit}; type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> 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()) }; + let ret = unsafe { func(chunk.as_mut_ptr() as *mut u8, chunk.len()) }; if ret != 0 { return Err(last_os_error()); } diff --git a/src/openbsd.rs b/src/openbsd.rs index 41371736..5d4df50b 100644 --- a/src/openbsd.rs +++ b/src/openbsd.rs @@ -9,7 +9,7 @@ //! Implementation for OpenBSD use crate::{util_libc::last_os_error, Error}; -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 1df21e5d..910b5350 100644 --- a/src/rdrand.rs +++ b/src/rdrand.rs @@ -7,8 +7,8 @@ // except according to those terms. //! Implementation for SGX using RDRAND instruction -use crate::Error; -use core::mem; +use crate::{util::slice_as_uninit, Error}; +use core::mem::{self, MaybeUninit}; cfg_if! { if #[cfg(target_arch = "x86_64")] { @@ -69,7 +69,7 @@ fn is_rdrand_supported() -> bool { HAS_RDRAND.unsync_init(|| unsafe { (arch::__cpuid(1).ecx & FLAG) != 0 }) } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { if !is_rdrand_supported() { return Err(Error::NO_RDRAND); } @@ -80,18 +80,18 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { } #[target_feature(enable = "rdrand")] -unsafe fn rdrand_exact(dest: &mut [u8]) -> Result<(), Error> { +unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Result<(), Error> { // 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); for chunk in chunks.by_ref() { - chunk.copy_from_slice(&rdrand()?); + chunk.copy_from_slice(slice_as_uninit(&rdrand()?)); } let tail = chunks.into_remainder(); let n = tail.len(); if n > 0 { - tail.copy_from_slice(&rdrand()?[..n]); + tail.copy_from_slice(slice_as_uninit(&rdrand()?[..n])); } Ok(()) } diff --git a/src/solaris_illumos.rs b/src/solaris_illumos.rs index cf3067d6..eaf27094 100644 --- a/src/solaris_illumos.rs +++ b/src/solaris_illumos.rs @@ -29,7 +29,7 @@ type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> #[cfg(target_os = "solaris")] type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::c_int; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getrandom(2) was introduced in Solaris 11.3 for Illumos in 2015. static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; if let Some(fptr) = GETRANDOM.ptr() { diff --git a/src/solid.rs b/src/solid.rs index dc76aacb..aeccc4e2 100644 --- a/src/solid.rs +++ b/src/solid.rs @@ -8,14 +8,14 @@ //! 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 16c0216b..fb45827f 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -14,6 +14,7 @@ use crate::{ }; use core::{ cell::UnsafeCell, + mem::MaybeUninit, sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; @@ -29,9 +30,11 @@ 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"; -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()) }; + let read = |buf: &mut [MaybeUninit]| 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. diff --git a/src/util.rs b/src/util.rs index 06e23c28..c7438363 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,7 +6,11 @@ // 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::{self, MaybeUninit}, + ptr, + 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. @@ -62,3 +66,36 @@ impl LazyBool { self.0.unsync_init(|| init() as usize) != 0 } } + +/// 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`. + mem::transmute(slice) +} + +#[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) } +} + +#[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 { mem::transmute(slice) } +} + +/// 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`. + mem::transmute(slice) +} diff --git a/src/util_libc.rs b/src/util_libc.rs index d057071a..6c80b3fe 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -8,6 +8,7 @@ #![allow(dead_code)] use crate::Error; use core::{ + mem::MaybeUninit, num::NonZeroU32, ptr::NonNull, sync::atomic::{fence, AtomicPtr, Ordering}, @@ -59,8 +60,8 @@ 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); diff --git a/src/vxworks.rs b/src/vxworks.rs index 6cb5d52f..9b2090fb 100644 --- a/src/vxworks.rs +++ b/src/vxworks.rs @@ -8,9 +8,12 @@ //! 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 +28,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 c5121824..f7a54f86 100644 --- a/src/wasi.rs +++ b/src/wasi.rs @@ -8,10 +8,10 @@ //! Implementation for WASI use crate::Error; -use core::num::NonZeroU32; +use core::{mem::MaybeUninit, num::NonZeroU32}; use wasi::wasi_snapshot_preview1::random_get; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> 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()), diff --git a/src/windows.rs b/src/windows.rs index 41dc37a5..e5a626c0 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -7,7 +7,7 @@ // except according to those terms. 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,14 +21,14 @@ extern "system" { ) -> u32; } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +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, ) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 006f230d..f91f1e71 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,4 +1,5 @@ use super::getrandom_impl; +use core::mem::{self, MaybeUninit}; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; @@ -9,16 +10,16 @@ wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); #[test] fn test_zero() { // Test that APIs are happy with zero-length requests - getrandom_impl(&mut [0u8; 0]).unwrap(); + getrandom_impl(unsafe { slice_as_uninit_mut(&mut [0u8; 0]) }).unwrap(); } #[test] fn test_diff() { let mut v1 = [0u8; 1000]; - getrandom_impl(&mut v1).unwrap(); + getrandom_impl(unsafe { slice_as_uninit_mut(&mut v1) }).unwrap(); let mut v2 = [0u8; 1000]; - getrandom_impl(&mut v2).unwrap(); + getrandom_impl(unsafe { slice_as_uninit_mut(&mut v2) }).unwrap(); let mut n_diff_bits = 0; for i in 0..v1.len() { @@ -32,7 +33,7 @@ fn test_diff() { #[test] fn test_huge() { let mut huge = [0u8; 100_000]; - getrandom_impl(&mut huge).unwrap(); + getrandom_impl(unsafe { slice_as_uninit_mut(&mut huge) }).unwrap(); } // On WASM, the thread API always fails/panics @@ -51,9 +52,9 @@ fn test_multithreading() { // wait until all the tasks are ready to go. rx.recv().unwrap(); let mut v = [0u8; 1000]; - + let y = unsafe { slice_as_uninit_mut(&mut v) }; for _ in 0..100 { - getrandom_impl(&mut v).unwrap(); + getrandom_impl(y).unwrap(); thread::yield_now(); } }); @@ -64,3 +65,9 @@ fn test_multithreading() { tx.send(()).unwrap(); } } + +#[inline(always)] +unsafe fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { + // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. + mem::transmute(slice) +} diff --git a/tests/normal.rs b/tests/normal.rs index 5fff13b3..62d357ea 100644 --- a/tests/normal.rs +++ b/tests/normal.rs @@ -7,5 +7,5 @@ )))] // Use the normal getrandom implementation on this architecture. -use getrandom::getrandom as getrandom_impl; +use getrandom::getrandom_uninit as getrandom_impl; mod common; From dfc0d1cd7f74e21807bcd0779ddc4123eeb08263 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 18 Oct 2022 18:34:16 -0700 Subject: [PATCH 2/7] Revert testing changes Signed-off-by: Joe Richey --- tests/common/mod.rs | 19 ++++++------------- tests/normal.rs | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index f91f1e71..006f230d 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,5 +1,4 @@ use super::getrandom_impl; -use core::mem::{self, MaybeUninit}; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; @@ -10,16 +9,16 @@ wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); #[test] fn test_zero() { // Test that APIs are happy with zero-length requests - getrandom_impl(unsafe { slice_as_uninit_mut(&mut [0u8; 0]) }).unwrap(); + getrandom_impl(&mut [0u8; 0]).unwrap(); } #[test] fn test_diff() { let mut v1 = [0u8; 1000]; - getrandom_impl(unsafe { slice_as_uninit_mut(&mut v1) }).unwrap(); + getrandom_impl(&mut v1).unwrap(); let mut v2 = [0u8; 1000]; - getrandom_impl(unsafe { slice_as_uninit_mut(&mut v2) }).unwrap(); + getrandom_impl(&mut v2).unwrap(); let mut n_diff_bits = 0; for i in 0..v1.len() { @@ -33,7 +32,7 @@ fn test_diff() { #[test] fn test_huge() { let mut huge = [0u8; 100_000]; - getrandom_impl(unsafe { slice_as_uninit_mut(&mut huge) }).unwrap(); + getrandom_impl(&mut huge).unwrap(); } // On WASM, the thread API always fails/panics @@ -52,9 +51,9 @@ fn test_multithreading() { // wait until all the tasks are ready to go. rx.recv().unwrap(); let mut v = [0u8; 1000]; - let y = unsafe { slice_as_uninit_mut(&mut v) }; + for _ in 0..100 { - getrandom_impl(y).unwrap(); + getrandom_impl(&mut v).unwrap(); thread::yield_now(); } }); @@ -65,9 +64,3 @@ fn test_multithreading() { tx.send(()).unwrap(); } } - -#[inline(always)] -unsafe fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { - // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. - mem::transmute(slice) -} diff --git a/tests/normal.rs b/tests/normal.rs index 62d357ea..5fff13b3 100644 --- a/tests/normal.rs +++ b/tests/normal.rs @@ -7,5 +7,5 @@ )))] // Use the normal getrandom implementation on this architecture. -use getrandom::getrandom_uninit as getrandom_impl; +use getrandom::getrandom as getrandom_impl; mod common; From a1ddafba422a61ff986bad72ae121418d977a575 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 18 Oct 2022 18:34:30 -0700 Subject: [PATCH 3/7] Allow rdrand tests to work with new implementation Signed-off-by: Joe Richey --- tests/rdrand.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/rdrand.rs b/tests/rdrand.rs index 4ff85c47..25678683 100644 --- a/tests/rdrand.rs +++ b/tests/rdrand.rs @@ -11,5 +11,10 @@ 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; From ebc54def9cc06e3b59ed9f60115b8fee538136fd Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 18 Oct 2022 18:59:41 -0700 Subject: [PATCH 4/7] Add Additional benchmarks and buffer size Signed-off-by: Joe Richey --- benches/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/benches/mod.rs b/benches/mod.rs index 07474b66..9a8f080e 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -7,6 +7,7 @@ use std::mem::MaybeUninit; // The buffer is hot, and does not require initialization. #[inline(always)] fn bench_getrandom(b: &mut test::Bencher) { + b.bytes = N as u64; b.iter(|| { let mut buf = [0u8; N]; getrandom::getrandom(&mut buf[..]).unwrap(); @@ -18,6 +19,7 @@ fn bench_getrandom(b: &mut test::Bencher) { // scenario. The buffer is still hot, but requires initialization. #[inline(always)] fn bench_getrandom_uninit(b: &mut test::Bencher) { + b.bytes = N as u64; b.iter(|| { // TODO: When the feature `maybe_uninit_as_bytes` is available, use: // ``` @@ -48,6 +50,9 @@ macro_rules! bench { }; } +// 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 @@ -56,3 +61,8 @@ 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); From 47d5dcc0406d71f5442d9f31bde6f9b8b7912d46 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 20 Oct 2022 14:01:10 -0700 Subject: [PATCH 5/7] Use pointer casts instead of transmute Signed-off-by: Joe Richey --- src/util.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util.rs b/src/util.rs index c7438363..3162afad 100644 --- a/src/util.rs +++ b/src/util.rs @@ -7,7 +7,7 @@ // except according to those terms. #![allow(dead_code)] use core::{ - mem::{self, MaybeUninit}, + mem::MaybeUninit, ptr, sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; @@ -73,7 +73,7 @@ impl LazyBool { #[inline(always)] pub unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. - mem::transmute(slice) + &mut *(slice as *mut [MaybeUninit] as *mut [T]) } #[inline] @@ -87,7 +87,7 @@ 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 { mem::transmute(slice) } + unsafe { &*(slice as *const [T] as *const [MaybeUninit]) } } /// View an mutable initialized array as potentially-uninitialized. @@ -97,5 +97,5 @@ pub fn slice_as_uninit(slice: &[T]) -> &[MaybeUninit] { #[inline(always)] pub unsafe fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. - mem::transmute(slice) + &mut *(slice as *mut [T] as *mut [MaybeUninit]) } From 123fefac602fd36504134c01328c42be0b827626 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 20 Oct 2022 16:37:41 -0700 Subject: [PATCH 6/7] Avoid initializing the buffer in `getrandom_uninit` benchmarks. --- benches/mod.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/benches/mod.rs b/benches/mod.rs index 9a8f080e..ec822b14 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -1,4 +1,6 @@ #![feature(test)] +#![feature(maybe_uninit_as_bytes)] + extern crate test; use std::mem::MaybeUninit; @@ -21,16 +23,9 @@ fn bench_getrandom(b: &mut test::Bencher) { fn bench_getrandom_uninit(b: &mut test::Bencher) { b.bytes = N as u64; b.iter(|| { - // TODO: When the feature `maybe_uninit_as_bytes` is available, use: - // ``` - // let mut buf: MaybeUninit<[u8; N]> = MaybeUninit::uninit(); - // getrandom::getrandom_uninit(buf.as_bytes_mut()).unwrap(); - // test::black_box(unsafe { buf.assume_init() }) - // ``` - // since that is the shape we expect most callers to have. - let mut buf = [MaybeUninit::new(0u8); N]; - let buf = getrandom::getrandom_uninit(&mut buf[..]).unwrap(); - test::black_box(&buf); + let mut buf: MaybeUninit<[u8; N]> = MaybeUninit::uninit(); + let buf = getrandom::getrandom_uninit(buf.as_bytes_mut()).unwrap(); + test::black_box(buf); }); } From 48158b22005f92a7c06fddf74084b0564fe518d4 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 20 Oct 2022 16:51:53 -0700 Subject: [PATCH 7/7] Benchmarks: Consume the result in `black_box`. --- benches/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/benches/mod.rs b/benches/mod.rs index ec822b14..23b8c74c 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -13,7 +13,7 @@ fn bench_getrandom(b: &mut test::Bencher) { b.iter(|| { let mut buf = [0u8; N]; getrandom::getrandom(&mut buf[..]).unwrap(); - test::black_box(&buf); + test::black_box(buf); }); } @@ -24,8 +24,9 @@ fn bench_getrandom_uninit(b: &mut test::Bencher) { b.bytes = N as u64; b.iter(|| { let mut buf: MaybeUninit<[u8; N]> = MaybeUninit::uninit(); - let buf = getrandom::getrandom_uninit(buf.as_bytes_mut()).unwrap(); - test::black_box(buf); + let _ = getrandom::getrandom_uninit(buf.as_bytes_mut()).unwrap(); + let buf: [u8; N] = unsafe { buf.assume_init() }; + test::black_box(buf) }); }