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 b2570494..148943a4 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_raw` and `getrandom_uninit` functions [#279] + +### Changed +- Bump MSRV to 1.36 [#279] + +[#279]: https://github.com/rust-random/getrandom/pull/279 + ## [0.2.7] - 2022-06-14 ### Changed - Update `wasi` dependency to `0.11` [#253] 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..6949b8c2 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -1,59 +1,21 @@ #![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(|| { + 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_raw(b: &mut test::Bencher) { b.iter(|| { - for byte in buf.iter_mut() { - *byte = 0; - } - getrandom::getrandom(&mut buf[..]).unwrap(); + let mut buf = core::mem::MaybeUninit::<[u8; N]>::uninit(); + unsafe { getrandom::getrandom_raw(buf.as_mut_ptr().cast(), N).unwrap() }; test::black_box(&buf); }); b.bytes = N as u64; @@ -71,8 +33,8 @@ fn bench_seed(b: &mut test::Bencher) { bench::(b); } #[bench] -fn bench_seed_init(b: &mut test::Bencher) { - bench_with_init::(b); +fn bench_seed_raw(b: &mut test::Bencher) { + bench_raw::(b); } #[bench] @@ -80,8 +42,8 @@ fn bench_page(b: &mut test::Bencher) { bench::(b); } #[bench] -fn bench_page_init(b: &mut test::Bencher) { - bench_with_init::(b); +fn bench_page_raw(b: &mut test::Bencher) { + bench_raw::(b); } #[bench] @@ -89,6 +51,6 @@ fn bench_large(b: &mut test::Bencher) { bench::(b); } #[bench] -fn bench_large_init(b: &mut test::Bencher) { - bench_with_init::(b); +fn bench_large_raw(b: &mut test::Bencher) { + bench_raw::(b); } diff --git a/src/3ds.rs b/src/3ds.rs index 60305127..26ed9026 100644 --- a/src/3ds.rs +++ b/src/3ds.rs @@ -10,8 +10,9 @@ use crate::util_libc::sys_fill_exact; use crate::Error; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - sys_fill_exact(dest, |buf| unsafe { - libc::getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { + // TODO: use `cast` on MSRV bump to 1.38 + sys_fill_exact(dst, len, |cdst, clen| { + libc::getrandom(cdst as *mut libc::c_void, clen, 0) }) } diff --git a/src/bsd_arandom.rs b/src/bsd_arandom.rs index d4412125..0c680313 100644 --- a/src/bsd_arandom.rs +++ b/src/bsd_arandom.rs @@ -7,22 +7,20 @@ // except according to those terms. //! Implementation for FreeBSD and NetBSD -use crate::{util_libc::sys_fill_exact, Error}; +use crate::{util::raw_chunks, util_libc::sys_fill_exact, Error}; use core::ptr; -fn kern_arnd(buf: &mut [u8]) -> libc::ssize_t { +unsafe fn kern_arnd(dst: *mut u8, mut len: usize) -> libc::ssize_t { static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND]; - let mut len = buf.len(); - let ret = unsafe { - libc::sysctl( - MIB.as_ptr(), - MIB.len() as libc::c_uint, - buf.as_mut_ptr() as *mut _, - &mut len, - ptr::null(), - 0, - ) - }; + // TODO: use `cast` on MSRV bump to 1.38 + let ret = libc::sysctl( + MIB.as_ptr(), + MIB.len() as libc::c_uint, + dst as *mut libc::c_void, + &mut len, + ptr::null(), + 0, + ); if ret == -1 { -1 } else { @@ -30,7 +28,7 @@ fn kern_arnd(buf: &mut [u8]) -> libc::ssize_t { } } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { // getrandom(2) was introduced in FreeBSD 12.0 and NetBSD 10.0 #[cfg(target_os = "freebsd")] { @@ -40,14 +38,13 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { 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) }); + let func: GetRandomFn = core::mem::transmute(fptr); + return sys_fill_exact(dst, len, |cdst, clen| func(cdst, clen, 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) { - sys_fill_exact(chunk, kern_arnd)? - } - Ok(()) + raw_chunks(dst, len, 256, |cdst, clen| { + sys_fill_exact(cdst, clen, |cdst2, clen2| kern_arnd(cdst2, clen2)) + }) } diff --git a/src/custom.rs b/src/custom.rs index 8432dfd2..2916e255 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -78,9 +78,9 @@ 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 { + extern "C" fn __getrandom_custom(dst: *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) }; + let slice = unsafe { ::core::slice::from_raw_parts_mut(dst, len) }; match f(slice) { Ok(()) => 0, Err(e) => e.code().get(), @@ -90,11 +90,11 @@ macro_rules! register_custom_getrandom { } #[allow(dead_code)] -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { extern "C" { - fn __getrandom_custom(dest: *mut u8, len: usize) -> u32; + fn __getrandom_custom(dst: *mut u8, len: usize) -> u32; } - let ret = unsafe { __getrandom_custom(dest.as_mut_ptr(), dest.len()) }; + let ret = __getrandom_custom(dst, len); match NonZeroU32::new(ret) { None => Ok(()), Some(code) => Err(Error::from(code)), diff --git a/src/dragonfly.rs b/src/dragonfly.rs index 8daaa404..829d8c27 100644 --- a/src/dragonfly.rs +++ b/src/dragonfly.rs @@ -13,15 +13,15 @@ use crate::{ Error, }; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> 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) }); + let func: GetRandomFn = core::mem::transmute(fptr); + sys_fill_exact(dst, len, |cdst, clen| func(cdst, clen, 0)) } else { - use_file::getrandom_inner(dest) + use_file::getrandom_inner(dst, len) } } diff --git a/src/espidf.rs b/src/espidf.rs index dce8a2aa..ba85169b 100644 --- a/src/espidf.rs +++ b/src/espidf.rs @@ -14,13 +14,13 @@ extern "C" { fn esp_fill_random(buf: *mut c_void, len: usize) -> u32; } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { // Not that NOT enabling WiFi, BT, or the voltage noise entropy source (via `bootloader_random_enable`) // will cause ESP-IDF to return pseudo-random numbers based on the voltage noise entropy, after the initial boot process: // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html // // However tracking if some of these entropy sources is enabled is way too difficult to implement here - unsafe { esp_fill_random(dest.as_mut_ptr().cast(), dest.len()) }; - + // TODO: use `cast` on MSRV bump to 1.38 + esp_fill_random(dst as *mut c_void, len); Ok(()) } diff --git a/src/fuchsia.rs b/src/fuchsia.rs index 572ff534..649541ad 100644 --- a/src/fuchsia.rs +++ b/src/fuchsia.rs @@ -14,7 +14,7 @@ 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 unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { + zx_cprng_draw(dst, len); Ok(()) } diff --git a/src/ios.rs b/src/ios.rs index 226de16b..113f8462 100644 --- a/src/ios.rs +++ b/src/ios.rs @@ -15,13 +15,12 @@ extern "C" { fn SecRandomCopyBytes(rnd: *const c_void, count: usize, bytes: *mut u8) -> i32; } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> 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 = SecRandomCopyBytes(null(), len, dst); // errSecSuccess (from SecBase.h) is always zero. - if ret != 0 { - Err(Error::IOS_SEC_RANDOM) - } else { - Ok(()) + match ret { + 0 => Ok(()), + _ => Err(Error::IOS_SEC_RANDOM), } } diff --git a/src/js.rs b/src/js.rs index 574c4dc3..1ead4b77 100644 --- a/src/js.rs +++ b/src/js.rs @@ -5,7 +5,7 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::Error; +use crate::{util::raw_chunks, Error}; extern crate std; use std::thread_local; @@ -28,32 +28,34 @@ thread_local!( static RNG_SOURCE: Result = getrandom_init(); ); -pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub(crate) unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> 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); - } + // We have to create a slice to pass it to the Node function. + // Since `dst` may be uninitialized, we have to initialize it first. + core::ptr::write_bytes(dst, 0, len); + let dst = core::slice::from_raw_parts_mut(dst, len); + n.random_fill_sync(dst) + .map_err(|_| Error::NODE_RANDOM_FILL_SYNC) } RngSource::Web(crypto, buf) => { // getRandomValues does not work with all types of WASM memory, // so we initially write to browser memory to avoid exceptions. - for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) { + raw_chunks(dst, len, WEB_CRYPTO_BUFFER_SIZE, |cdst, clen| { // The chunk can be smaller than buf's length, so we call to // JS to create a smaller view of buf without allocation. - let sub_buf = buf.subarray(0, chunk.len() as u32); - - if crypto.get_random_values(&sub_buf).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); - } - sub_buf.copy_to(chunk); - } + let sub_buf = buf.subarray(0, clen as u32); + crypto + .get_random_values(&sub_buf) + .map_err(|_| Error::WEB_GET_RANDOM_VALUES)?; + sub_buf.raw_copy_to_ptr(cdst); + Ok(()) + }) } - }; - Ok(()) + } }) } diff --git a/src/lib.rs b/src/lib.rs index 91b0b7bb..a9f34d30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,6 +187,8 @@ #[macro_use] extern crate cfg_if; +use core::mem::MaybeUninit; + mod error; mod util; // To prevent a breaking change when targets are added, we always export the @@ -271,7 +273,7 @@ cfg_if! { } } -/// Fill `dest` with random bytes from the system's preferred random number +/// Fill `dst` with random bytes from the system's preferred random number /// source. /// /// This function returns an error on any failure, including partial reads. We @@ -284,9 +286,66 @@ 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). -pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { - if dest.is_empty() { - return Ok(()); +#[inline] +pub fn getrandom(dst: &mut [u8]) -> Result<(), Error> { + unsafe { getrandom_raw(dst.as_mut_ptr(), dst.len()) } +} + +/// Raw version of the `getrandom` function. +/// +/// If this function returns `Ok(())`, then it's safe to assume that the +/// `len` random bytes were written to the memory pointed by `dst`. +/// +/// # Safety +/// +/// `dst` MUST be [valid] for writes of `len` bytes. +/// +/// [valid]: core::ptr#safety +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), getrandom::Error> { +/// const BUF_SIZE: usize = 1024; +/// +/// let buf: [u8; BUF_SIZE] = unsafe { +/// let mut buf = core::mem::MaybeUninit::uninit(); +/// getrandom::getrandom_raw(buf.as_mut_ptr() as *mut u8, BUF_SIZE)?; +/// buf.assume_init() +/// }; +/// # Ok(()) } +/// ``` +#[inline] +pub unsafe fn getrandom_raw(dst: *mut u8, len: usize) -> Result<(), Error> { + match len { + 0 => Ok(()), + _ => imp::getrandom_inner(dst, len), + } +} + +/// Version of the `getrandom` function which fills uninitialized buffer +/// and returns initialized slice of equal length. +/// +/// On successful completion this function is guaranteed to return slice +/// which points to the same memory as `dst` and has the same length. +/// In other words, it's safe to assume that `dst` is initialized after +/// this function has returned `Ok`. +/// +/// # Examples +/// +/// ```ignore +/// # // We ignore this test since `uninit_array` requires Nigthly +/// # 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(dst: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { + let dst_ptr = dst.as_mut_ptr() as *mut u8; + let dst_len = dst.len(); + unsafe { + getrandom_raw(dst_ptr, dst_len)?; + Ok(core::slice::from_raw_parts_mut(dst_ptr, dst_len)) } - imp::getrandom_inner(dest) } diff --git a/src/linux_android.rs b/src/linux_android.rs index 4270b67c..a96dd993 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -13,15 +13,16 @@ use crate::{ {use_file, Error}, }; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { // getrandom(2) was introduced in Linux 3.17 static HAS_GETRANDOM: LazyBool = LazyBool::new(); if HAS_GETRANDOM.unsync_init(is_getrandom_available) { - sys_fill_exact(dest, |buf| unsafe { - getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) + // TODO: use `cast` on MSRV bump to 1.38 + sys_fill_exact(dst, len, |cdst, clen| { + getrandom(cdst as *mut libc::c_void, clen, 0) }) } else { - use_file::getrandom_inner(dest) + use_file::getrandom_inner(dst, len) } } diff --git a/src/macos.rs b/src/macos.rs index 671a053b..0b527eb9 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -9,6 +9,7 @@ //! Implementation for macOS use crate::{ use_file, + util::raw_chunks, util_libc::{last_os_error, Weak}, Error, }; @@ -16,21 +17,21 @@ use core::mem; type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> 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()); + let func: GetEntropyFn = mem::transmute(fptr); + raw_chunks(dst, len, 256, |cdst, clen| { + let ret = func(cdst, clen); + match ret { + 0 => Ok(()), + _ => 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) + use_file::getrandom_inner(dst, len) } } diff --git a/src/openbsd.rs b/src/openbsd.rs index 41371736..7b9e761a 100644 --- a/src/openbsd.rs +++ b/src/openbsd.rs @@ -7,15 +7,16 @@ // except according to those terms. //! Implementation for OpenBSD -use crate::{util_libc::last_os_error, Error}; +use crate::{util::raw_chunks, util_libc::last_os_error, Error}; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> 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()) }; - if ret == -1 { - return Err(last_os_error()); + raw_chunks(dst, len, 256, |cdst, clen| { + // TODO: use `cast` on MSRV bump to 1.38 + let ret = libc::getentropy(cdst as *mut libc::c_void, clen); + match ret { + -1 => Err(last_os_error()), + _ => Ok(()), } - } - Ok(()) + }) } diff --git a/src/rdrand.rs b/src/rdrand.rs index 1df21e5d..63a0e5a6 100644 --- a/src/rdrand.rs +++ b/src/rdrand.rs @@ -8,7 +8,7 @@ //! Implementation for SGX using RDRAND instruction use crate::Error; -use core::mem; +use core::{mem, ptr}; cfg_if! { if #[cfg(target_arch = "x86_64")] { @@ -69,29 +69,31 @@ 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 unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { if !is_rdrand_supported() { 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) } + rdrand_exact(dst, len) } #[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(mut dst: *mut u8, mut len: usize) -> Result<(), Error> { + while len >= WORD_SIZE { + // TODO: use `cast` on MSRV bump to 1.38 + ptr::write(dst as *mut [u8; WORD_SIZE], rdrand()?); + dst = dst.add(WORD_SIZE); + len -= WORD_SIZE; } - let tail = chunks.into_remainder(); - let n = tail.len(); - if n > 0 { - tail.copy_from_slice(&rdrand()?[..n]); + if len != 0 { + let src = rdrand()?; + // TODO: use `cast` on MSRV bump to 1.38 + let src_ptr = &src as *const [u8; WORD_SIZE] as *const u8; + ptr::copy_nonoverlapping(src_ptr, dst, len) } + Ok(()) } diff --git a/src/solaris_illumos.rs b/src/solaris_illumos.rs index cf3067d6..cfdd8bc0 100644 --- a/src/solaris_illumos.rs +++ b/src/solaris_illumos.rs @@ -19,6 +19,7 @@ //! libc::dlsym. use crate::{ use_file, + util::raw_chunks, util_libc::{sys_fill_exact, Weak}, Error, }; @@ -29,20 +30,19 @@ 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 unsafe fn getrandom_inner(dst: &mut [u8], len: usize) -> 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() { - let func: GetRandomFn = unsafe { mem::transmute(fptr) }; + let func: GetRandomFn = 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 - })? - } - Ok(()) + raw_chunks(dst, len, 256, |cdst, clen| { + sys_fill_exact(cdst, clen, |cdst2, clen2| { + func(cdst2, clen2, 0) as libc::ssize_t + }) + }) } else { - use_file::getrandom_inner(dest) + use_file::getrandom_inner(dst, len) } } diff --git a/src/solid.rs b/src/solid.rs index dc76aacb..d3123ec0 100644 --- a/src/solid.rs +++ b/src/solid.rs @@ -14,8 +14,8 @@ 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 unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { + let ret = SOLID_RNG_SampleRandomBytes(dst, len); if ret >= 0 { Ok(()) } else { diff --git a/src/use_file.rs b/src/use_file.rs index 16c0216b..d83d0b65 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -29,17 +29,22 @@ 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 unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> 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 = |dst: *mut u8, len: usize| libc::read(fd, dst as *mut _, 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)?; + // `Crypto.getRandomValues` documents `len` should be at most 65536 bytes. + let mut dst = dst; + let mut len = len; + while len != 0 { + let chunk_len = core::cmp::min(len, 65536); + sys_fill_exact(dst, chunk_len, read)?; + dst = dst.add(chunk_len); + len -= chunk_len; } } else { - sys_fill_exact(dest, read)?; + sys_fill_exact(dst, len, read)?; } Ok(()) } diff --git a/src/util.rs b/src/util.rs index 06e23c28..d5515c5e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -62,3 +62,20 @@ impl LazyBool { self.0.unsync_init(|| init() as usize) != 0 } } + +pub unsafe fn raw_chunks( + mut dst: *mut u8, + mut len: usize, + chunk_len: usize, + f: impl Fn(*mut u8, usize) -> Result<(), crate::Error>, +) -> Result<(), crate::Error> { + while len >= chunk_len { + f(dst, chunk_len)?; + dst = dst.add(chunk_len); + len -= chunk_len; + } + if len != 0 { + f(dst, len)?; + } + Ok(()) +} diff --git a/src/util_libc.rs b/src/util_libc.rs index d057071a..e0c106ed 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -58,12 +58,13 @@ pub fn last_os_error() -> Error { // Fill a buffer by repeatedly invoking a system call. The `sys_fill` function: // - 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, +pub unsafe fn sys_fill_exact( + mut dst: *mut u8, + mut len: usize, + sys_fill: impl Fn(*mut u8, usize) -> libc::ssize_t, ) -> Result<(), Error> { - while !buf.is_empty() { - let res = sys_fill(buf); + while len != 0 { + let res = sys_fill(dst, len); if res < 0 { let err = last_os_error(); // We should try again if the call was interrupted. @@ -73,7 +74,9 @@ pub fn sys_fill_exact( } else { // We don't check for EOF (ret = 0) as the data we are reading // should be an infinite stream of random bytes. - buf = &mut buf[(res as usize)..]; + let res = res as usize; + dst = dst.add(res); + len -= res; } } Ok(()) diff --git a/src/vxworks.rs b/src/vxworks.rs index 6cb5d52f..3b36a140 100644 --- a/src/vxworks.rs +++ b/src/vxworks.rs @@ -7,28 +7,28 @@ // except according to those terms. //! Implementation for VxWorks -use crate::{util_libc::last_os_error, Error}; +use crate::{util::raw_chunks, util_libc::last_os_error, Error}; use core::sync::atomic::{AtomicBool, Ordering::Relaxed}; -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { static RNG_INIT: AtomicBool = AtomicBool::new(false); while !RNG_INIT.load(Relaxed) { - let ret = unsafe { libc::randSecure() }; + let ret = libc::randSecure(); if ret < 0 { return Err(Error::VXWORKS_RAND_SECURE); } else if ret > 0 { RNG_INIT.store(true, Relaxed); break; } - unsafe { libc::usleep(10) }; + libc::usleep(10); } // 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) }; - if ret != 0 { - return Err(last_os_error()); + raw_chunks(dst, len, i32::max_value() as usize, |cdst, clen| { + let ret = libc::randABytes(cdst, clen as i32); + match ret { + 0 => Ok(()), + _ => Err(last_os_error()), } - } - Ok(()) + }) } diff --git a/src/wasi.rs b/src/wasi.rs index c5121824..b006996d 100644 --- a/src/wasi.rs +++ b/src/wasi.rs @@ -11,9 +11,11 @@ use crate::Error; use core::num::NonZeroU32; use wasi::wasi_snapshot_preview1::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) } { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { + // NOTE: WASI is a 32-bit target, it can not have objects bigger + // than `i32::MAX - 1`, so we do not need chunking here + match random_get(dst as i32, len as i32) { 0 => Ok(()), - err => Err(unsafe { NonZeroU32::new_unchecked(err as u32) }.into()), + err => Err(NonZeroU32::new_unchecked(err as u32).into()), } } diff --git a/src/windows.rs b/src/windows.rs index 41dc37a5..ccfc3c27 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -6,7 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::Error; +use crate::{util::raw_chunks, Error}; use core::{ffi::c_void, num::NonZeroU32, ptr}; const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; @@ -21,18 +21,16 @@ extern "system" { ) -> u32; } -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { +pub unsafe fn getrandom_inner(dst: *mut u8, len: usize) -> Result<(), Error> { // Prevent overflow of u32 - for chunk in dest.chunks_mut(u32::max_value() as usize) { + raw_chunks(dst, len, u32::max_value() as usize, |cdst, clen| { // BCryptGenRandom was introduced in Windows Vista - let ret = unsafe { - BCryptGenRandom( - ptr::null_mut(), - chunk.as_mut_ptr(), - chunk.len() as u32, - BCRYPT_USE_SYSTEM_PREFERRED_RNG, - ) - }; + let ret = BCryptGenRandom( + ptr::null_mut(), + cdst, + clen as u32, + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ); // NTSTATUS codes use the two highest bits for severity status. if ret >> 30 == 0b11 { // We zeroize the highest bit, so the error code will reside @@ -41,9 +39,10 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { // SAFETY: the second highest bit is always equal to one, // so it's impossible to get zero. Unfortunately the type // system does not have a way to express this yet. - let code = unsafe { NonZeroU32::new_unchecked(code) }; - return Err(Error::from(code)); + let code = NonZeroU32::new_unchecked(code); + Err(Error::from(code)) + } else { + Ok(()) } - } - Ok(()) + }) } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 006f230d..b2cadcbe 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,4 +1,4 @@ -use super::getrandom_impl; +use super::getrandom; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; @@ -9,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(&mut [0u8; 0]).unwrap(); + getrandom(&mut [0u8; 0]).unwrap(); } #[test] fn test_diff() { let mut v1 = [0u8; 1000]; - getrandom_impl(&mut v1).unwrap(); + getrandom(&mut v1).unwrap(); let mut v2 = [0u8; 1000]; - getrandom_impl(&mut v2).unwrap(); + getrandom(&mut v2).unwrap(); let mut n_diff_bits = 0; for i in 0..v1.len() { @@ -32,7 +32,7 @@ fn test_diff() { #[test] fn test_huge() { let mut huge = [0u8; 100_000]; - getrandom_impl(&mut huge).unwrap(); + getrandom(&mut huge).unwrap(); } // On WASM, the thread API always fails/panics @@ -53,7 +53,7 @@ fn test_multithreading() { let mut v = [0u8; 1000]; for _ in 0..100 { - getrandom_impl(&mut v).unwrap(); + getrandom(&mut v).unwrap(); thread::yield_now(); } }); diff --git a/tests/normal.rs b/tests/normal.rs index 5fff13b3..2392efd7 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; mod common; diff --git a/tests/rdrand.rs b/tests/rdrand.rs index 4ff85c47..57bc4fc8 100644 --- a/tests/rdrand.rs +++ b/tests/rdrand.rs @@ -11,5 +11,11 @@ mod rdrand; #[path = "../src/util.rs"] mod util; -use rdrand::getrandom_inner as getrandom_impl; +pub fn getrandom(dst: &mut [u8]) -> Result<(), Error> { + if dst.is_empty() { + return Ok(()); + } + unsafe { rdrand::getrandom_inner(dst.as_mut_ptr(), dst.len()) } +} + mod common;