Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add getrandom_uninit(dest: &mut [MaybeUninit<u8>]) -> .... #291

Merged
merged 7 commits into from Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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` [#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`
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
112 changes: 43 additions & 69 deletions benches/mod.rs
@@ -1,94 +1,68 @@
#![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<const N: usize>(NonNull<[u8; N]>);

impl<const N: usize> AlignedBuffer<N> {
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<const N: usize> Drop for AlignedBuffer<N> {
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<const N: usize>(b: &mut test::Bencher) {
let mut ab = AlignedBuffer::<N>::new();
let buf = ab.buf();
fn bench_getrandom<const N: usize>(b: &mut test::Bencher) {
b.bytes = N as u64;
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<const N: usize>(b: &mut test::Bencher) {
let mut ab = AlignedBuffer::<N>::new();
let buf = ab.buf();
fn bench_getrandom_uninit<const N: usize>(b: &mut test::Bencher) {
b.bytes = N as u64;
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.
josephlr marked this conversation as resolved.
Show resolved Hide resolved
let mut buf = [MaybeUninit::new(0u8); N];
josephlr marked this conversation as resolved.
Show resolved Hide resolved
josephlr marked this conversation as resolved.
Show resolved Hide resolved
let buf = getrandom::getrandom_uninit(&mut buf[..]).unwrap();
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
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::<SEED>(b);
}
#[bench]
fn bench_seed_init(b: &mut test::Bencher) {
bench_with_init::<SEED>(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::<PAGE>(b);
}
#[bench]
fn bench_page_init(b: &mut test::Bencher) {
bench_with_init::<PAGE>(b);
}
// 16 bytes (128 bits) is the size of an 128-bit AES key/nonce.
bench!(aes128, 128 / 8);

#[bench]
fn bench_large(b: &mut test::Bencher) {
bench::<LARGE>(b);
}
#[bench]
fn bench_large_init(b: &mut test::Bencher) {
bench_with_init::<LARGE>(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);

// 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);
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
10 changes: 6 additions & 4 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,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<u8>]) -> Result<(), Error> {
// getrandom(2) was introduced in FreeBSD 12.0 and NetBSD 10.0
#[cfg(target_os = "freebsd")]
{
Expand All @@ -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
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);
josephlr marked this conversation as resolved.
Show resolved Hide resolved
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
4 changes: 2 additions & 2 deletions src/espidf.rs
Expand Up @@ -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<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
5 changes: 3 additions & 2 deletions src/fuchsia.rs
Expand Up @@ -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<u8>]) -> Result<(), Error> {
unsafe { zx_cprng_draw(dest.as_mut_ptr() as *mut u8, dest.len()) }
Ok(())
}
6 changes: 3 additions & 3 deletions src/ios.rs
Expand Up @@ -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<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(), dest.as_mut_ptr() as *mut u8) };
// errSecSuccess (from SecBase.h) is always zero.
if ret != 0 {
Err(Error::IOS_SEC_RANDOM)
Expand Down
14 changes: 10 additions & 4 deletions src/js.rs
Expand Up @@ -5,10 +5,10 @@
// <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_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 +28,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 +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) };
}
}
};
Expand Down