Skip to content

Commit

Permalink
Add getrandom_uninit_slice(dest: &mut [MaybeUninit<u8>]) -> ....
Browse files Browse the repository at this point in the history
Add a public API for filling an `&mut [MaybeUninit<u8>]`. 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.
  • Loading branch information
briansmith committed Oct 13, 2022
1 parent cfdad53 commit a9a6061
Show file tree
Hide file tree
Showing 27 changed files with 227 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -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

Expand Down
34 changes: 34 additions & 0 deletions benches/mod.rs
Expand Up @@ -3,6 +3,7 @@ extern crate test;

use std::{
alloc::{alloc_zeroed, dealloc, Layout},
mem::{self, MaybeUninit},
ptr::NonNull,
};

Expand Down Expand Up @@ -59,6 +60,20 @@ fn bench_with_init<const N: usize>(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<const N: usize>(b: &mut test::Bencher) {
let mut ab = AlignedBuffer::<N>::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
Expand All @@ -74,6 +89,10 @@ fn bench_seed(b: &mut test::Bencher) {
fn bench_seed_init(b: &mut test::Bencher) {
bench_with_init::<SEED>(b);
}
#[bench]
fn bench_seed_uninit(b: &mut test::Bencher) {
bench_uninit::<SEED>(b);
}

#[bench]
fn bench_page(b: &mut test::Bencher) {
Expand All @@ -83,6 +102,10 @@ fn bench_page(b: &mut test::Bencher) {
fn bench_page_init(b: &mut test::Bencher) {
bench_with_init::<PAGE>(b);
}
#[bench]
fn bench_page_uninit(b: &mut test::Bencher) {
bench_uninit::<PAGE>(b);
}

#[bench]
fn bench_large(b: &mut test::Bencher) {
Expand All @@ -92,3 +115,14 @@ fn bench_large(b: &mut test::Bencher) {
fn bench_large_init(b: &mut test::Bencher) {
bench_with_init::<LARGE>(b);
}
#[bench]
fn bench_large_uninit(b: &mut test::Bencher) {
bench_uninit::<LARGE>(b);
}

// TODO: Safety note.
#[inline(always)]
unsafe fn slice_as_uninit_mut<T>(slice: &mut [T]) -> &mut [MaybeUninit<T>] {
// SAFETY: `MaybeUninit<T>` is guaranteed to be layout-compatible with `T`.
mem::transmute(slice)
}
3 changes: 2 additions & 1 deletion src/3ds.rs
Expand Up @@ -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<u8>]) -> Result<(), Error> {
sys_fill_exact(dest, |buf| unsafe {
libc::getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0)
})
Expand Down
12 changes: 7 additions & 5 deletions src/bsd_arandom.rs
Expand Up @@ -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<u8>]) -> libc::ssize_t {
static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND];
let mut len = buf.len();
let ret = unsafe {
Expand All @@ -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<u8>]) -> 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
Expand Down
12 changes: 9 additions & 3 deletions src/custom.rs
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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<u8>]) -> 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(()),
Expand Down
3 changes: 2 additions & 1 deletion src/dragonfly.rs
Expand Up @@ -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<u8>]) -> 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;

Expand Down
6 changes: 3 additions & 3 deletions src/espidf.rs
Expand Up @@ -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<u8>]) -> 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
Expand Down
7 changes: 4 additions & 3 deletions src/fuchsia.rs
Expand Up @@ -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<u8>]) -> Result<(), Error> {
unsafe { zx_cprng_draw(uninit_slice_as_mut_ptr(dest), dest.len()) }
Ok(())
}
8 changes: 4 additions & 4 deletions src/ios.rs
Expand Up @@ -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<u8>]) -> 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)
Expand Down
17 changes: 13 additions & 4 deletions src/js.rs
Expand Up @@ -5,10 +5,13 @@
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, 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};
Expand All @@ -28,12 +31,16 @@ thread_local!(
static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
);

pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> 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);
}
Expand All @@ -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)) };
}
}
};
Expand Down
39 changes: 34 additions & 5 deletions src/lib.rs
Expand Up @@ -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
Expand All @@ -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<u8>]) -> Result<(), Error>`.
cfg_if! {
if #[cfg(any(target_os = "emscripten", target_os = "haiku",
target_os = "redox"))] {
Expand Down Expand Up @@ -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<u8>]) -> 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) })
}
3 changes: 2 additions & 1 deletion src/linux_android.rs
Expand Up @@ -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<u8>]) -> Result<(), Error> {
// getrandom(2) was introduced in Linux 3.17
static HAS_GETRANDOM: LazyBool = LazyBool::new();
if HAS_GETRANDOM.unsync_init(is_getrandom_available) {
Expand Down
7 changes: 4 additions & 3 deletions src/macos.rs
Expand Up @@ -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<u8>]) -> Result<(), Error> {
// getentropy(2) was added in 10.12, Rust supports 10.7+
static GETENTROPY: Weak = unsafe { Weak::new("getentropy\0") };
if let Some(fptr) = GETENTROPY.ptr() {
let func: GetEntropyFn = unsafe { mem::transmute(fptr) };
for chunk in dest.chunks_mut(256) {
let ret = unsafe { func(chunk.as_mut_ptr(), chunk.len()) };
let ret = unsafe { func(uninit_slice_as_mut_ptr(chunk), chunk.len()) };
if ret != 0 {
return Err(last_os_error());
}
Expand Down
2 changes: 1 addition & 1 deletion src/openbsd.rs
Expand Up @@ -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<u8>]) -> 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()) };
Expand Down

0 comments on commit a9a6061

Please sign in to comment.