From a9a6061b3425e9f24c38463986f4d1576842aa39 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 11 Oct 2022 18:39:26 -0700 Subject: [PATCH] Add `getrandom_uninit_slice(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 | 34 +++++++++++++++++++++++++++ src/3ds.rs | 3 ++- src/bsd_arandom.rs | 12 ++++++---- src/custom.rs | 12 +++++++--- src/dragonfly.rs | 3 ++- src/espidf.rs | 6 ++--- src/fuchsia.rs | 7 +++--- src/ios.rs | 8 +++---- src/js.rs | 17 ++++++++++---- src/lib.rs | 39 +++++++++++++++++++++++++++---- src/linux_android.rs | 3 ++- src/macos.rs | 7 +++--- src/openbsd.rs | 2 +- src/rdrand.rs | 23 ++++++++++--------- src/solaris_illumos.rs | 2 +- src/solid.rs | 8 +++---- src/use_file.rs | 7 ++++-- src/util.rs | 46 ++++++++++++++++++++++++++++++++++++- src/util_libc.rs | 5 ++-- src/vxworks.rs | 11 +++++---- src/wasi.rs | 4 ++-- src/windows.rs | 8 +++---- tests/common/mod.rs | 19 ++++++++++----- tests/normal.rs | 2 +- 27 files changed, 227 insertions(+), 74 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8ca2d01c1..210d67ba7 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 b25704947..d07680506 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_slice` [#291] + +### Breaking Changes +- Update MSRV to 1.36 [#291] + +[#291]: https://github.com/rust-random/getrandom/pull/291 + ## [0.2.7] - 2022-06-14 ### Changed - Update `wasi` dependency to `0.11` [#253] diff --git a/README.md b/README.md index df2307b9c..f43666ade 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 11be47eb7..322e64ae9 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -3,6 +3,7 @@ extern crate test; use std::{ alloc::{alloc_zeroed, dealloc, Layout}, + mem::{self, MaybeUninit}, ptr::NonNull, }; @@ -59,6 +60,20 @@ fn bench_with_init(b: &mut test::Bencher) { b.bytes = N as u64; } +// Used to benchmark the benefit of `getrandom_uninit` compared to +// zero-initializing a buffer and then using `getrandom` (`bench_with_init` +// above). +#[inline(always)] +fn bench_uninit(b: &mut test::Bencher) { + let mut ab = AlignedBuffer::::new(); + let buf = ab.buf(); + // SAFETY: `buf` doesn't escape this scope. + let buf = unsafe { slice_as_uninit_mut(buf) }; + b.iter(|| { + let _ = getrandom::getrandom_uninit_slice(buf); + }) +} + // 32 bytes (256-bit) is the seed sized used for rand::thread_rng const SEED: usize = 32; // Common size of a page, 4 KiB @@ -74,6 +89,10 @@ fn bench_seed(b: &mut test::Bencher) { fn bench_seed_init(b: &mut test::Bencher) { bench_with_init::(b); } +#[bench] +fn bench_seed_uninit(b: &mut test::Bencher) { + bench_uninit::(b); +} #[bench] fn bench_page(b: &mut test::Bencher) { @@ -83,6 +102,10 @@ fn bench_page(b: &mut test::Bencher) { fn bench_page_init(b: &mut test::Bencher) { bench_with_init::(b); } +#[bench] +fn bench_page_uninit(b: &mut test::Bencher) { + bench_uninit::(b); +} #[bench] fn bench_large(b: &mut test::Bencher) { @@ -92,3 +115,14 @@ fn bench_large(b: &mut test::Bencher) { fn bench_large_init(b: &mut test::Bencher) { bench_with_init::(b); } +#[bench] +fn bench_large_uninit(b: &mut test::Bencher) { + bench_uninit::(b); +} + +// TODO: Safety note. +#[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/src/3ds.rs b/src/3ds.rs index 60305127e..87a32a1e8 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 d44121254..96099a399 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,18 +30,20 @@ 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")] { - use crate::util_libc::Weak; + use crate::{util::uninit_slice_as_mut_ptr, 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; 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(uninit_slice_as_mut_ptr(buf), 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 8432dfd22..35771f15c 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 8daaa4048..c3701cc48 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 dce8a2aa0..e857b80ad 100644 --- a/src/espidf.rs +++ b/src/espidf.rs @@ -7,14 +7,14 @@ // except according to those terms. //! Implementation for ESP-IDF -use crate::Error; -use core::ffi::c_void; +use crate::{util::uninit_slice_as_mut_ptr, Error}; +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 572ff5344..8e2412bc4 100644 --- a/src/fuchsia.rs +++ b/src/fuchsia.rs @@ -7,14 +7,15 @@ // except according to those terms. //! Implementation for Fuchsia Zircon -use crate::Error; +use crate::{util::uninit_slice_as_mut_ptr, 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(uninit_slice_as_mut_ptr(dest), dest.len()) } Ok(()) } diff --git a/src/ios.rs b/src/ios.rs index 226de16bd..f4379ed90 100644 --- a/src/ios.rs +++ b/src/ios.rs @@ -7,17 +7,17 @@ // except according to those terms. //! Implementation for iOS -use crate::Error; -use core::{ffi::c_void, ptr::null}; +use crate::{util::uninit_slice_as_mut_ptr, Error}; +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(), uninit_slice_as_mut_ptr(dest)) }; // 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 574c4dc32..44f758093 100644 --- a/src/js.rs +++ b/src/js.rs @@ -5,10 +5,13 @@ // , 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_as_mut_ptr, 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 +31,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 +56,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(uninit_slice_as_mut_ptr(chunk)) }; } } }; diff --git a/src/lib.rs b/src/lib.rs index 91b0b7bb7..2ef97778a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,6 +187,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 @@ -200,7 +203,8 @@ 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>`. cfg_if! { if #[cfg(any(target_os = "emscripten", target_os = "haiku", target_os = "redox"))] { @@ -284,9 +288,34 @@ 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. + getrandom_uninit_slice(unsafe { slice_as_uninit_mut(dest) }).map(|_| ()) +} + +/// 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`. +/// +/// # 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_slice(&mut buf)?; +/// # Ok(()) } +/// ``` +#[inline] +pub fn getrandom_uninit_slice(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 4270b67c6..e81f1e153 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 671a053bf..d1a6bb78d 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -9,20 +9,21 @@ //! Implementation for macOS use crate::{ use_file, + util::uninit_slice_as_mut_ptr, 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(uninit_slice_as_mut_ptr(chunk), chunk.len()) }; if ret != 0 { return Err(last_os_error()); } diff --git a/src/openbsd.rs b/src/openbsd.rs index 41371736f..5d4df50bb 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 1df21e5d9..2a2cbca67 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,19 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { } #[target_feature(enable = "rdrand")] -unsafe fn rdrand_exact(dest: &mut [u8]) -> 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()?); +unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let num_chunks = dest.len() / WORD_SIZE; + let (head, tail) = dest.split_at_mut(num_chunks * WORD_SIZE); + + let head: &mut [MaybeUninit<[u8; WORD_SIZE]>] = + core::slice::from_raw_parts_mut(head.as_mut_ptr() as *mut _, num_chunks); + for chunk in head { + *chunk = MaybeUninit::new(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 cf3067d6d..eaf27094c 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 dc76aacbf..2750833c4 100644 --- a/src/solid.rs +++ b/src/solid.rs @@ -7,15 +7,15 @@ // except according to those terms. //! Implementation for SOLID -use crate::Error; -use core::num::NonZeroU32; +use crate::{util::uninit_slice_as_mut_ptr, Error}; +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(uninit_slice_as_mut_ptr(dest), dest.len()) }; if ret >= 0 { Ok(()) } else { diff --git a/src/use_file.rs b/src/use_file.rs index 16c0216b6..fb45827f4 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 06e23c28c..ac073ce97 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,7 +6,10 @@ // 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}, + 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 +65,44 @@ 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) +} + +/// Polyfill for the unstable `maybe_uninit_slice` feature's +/// `MaybeUninit::slice_as_mut_ptr`. +#[inline(always)] +pub fn uninit_slice_as_mut_ptr(slice: &mut [MaybeUninit]) -> *mut T { + slice.as_mut_ptr() as *mut T +} + +#[inline] +pub fn uninit_slice_fill_zero(slice: &mut [MaybeUninit]) -> &mut [u8] { + slice.iter_mut().for_each(|b| *b = MaybeUninit::zeroed()); + // SAFETY: The above line ensures every element is initialized. + 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 d057071a7..6c80b3fe8 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 6cb5d52fe..caecca288 100644 --- a/src/vxworks.rs +++ b/src/vxworks.rs @@ -7,10 +7,13 @@ // except according to those terms. //! Implementation for VxWorks -use crate::{util_libc::last_os_error, Error}; -use core::sync::atomic::{AtomicBool, Ordering::Relaxed}; +use crate::{util::uninit_slice_as_mut_ptr, util_libc::last_os_error, Error}; +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(uninit_slice_as_mut_ptr(chunk), chunk.len() as i32) }; if ret != 0 { return Err(last_os_error()); } diff --git a/src/wasi.rs b/src/wasi.rs index c5121824a..f7a54f862 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 41dc37a5c..7d31be48f 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -6,8 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::Error; -use core::{ffi::c_void, num::NonZeroU32, ptr}; +use crate::{util::uninit_slice_as_mut_ptr, Error}; +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(), + uninit_slice_as_mut_ptr(chunk), chunk.len() as u32, BCRYPT_USE_SYSTEM_PREFERRED_RNG, ) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 006f230d7..f91f1e716 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 5fff13b38..6cb0d8651 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_slice as getrandom_impl; mod common;