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

Implement raw API #279

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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_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]
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
58 changes: 10 additions & 48 deletions 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<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()) }
}
}

// 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();
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_raw<const N: usize>(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;
Expand All @@ -71,24 +33,24 @@ fn bench_seed(b: &mut test::Bencher) {
bench::<SEED>(b);
}
#[bench]
fn bench_seed_init(b: &mut test::Bencher) {
bench_with_init::<SEED>(b);
fn bench_seed_raw(b: &mut test::Bencher) {
bench_raw::<SEED>(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);
fn bench_page_raw(b: &mut test::Bencher) {
bench_raw::<PAGE>(b);
}

#[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);
fn bench_large_raw(b: &mut test::Bencher) {
bench_raw::<LARGE>(b);
}
7 changes: 4 additions & 3 deletions src/3ds.rs
Expand Up @@ -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)
})
}
37 changes: 17 additions & 20 deletions src/bsd_arandom.rs
Expand Up @@ -7,30 +7,28 @@
// 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 {
len as 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")]
{
Expand All @@ -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))
})
}
10 changes: 5 additions & 5 deletions src/custom.rs
Expand Up @@ -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) };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is incorrect and fixing it would break backward compatibility.

getrandom_raw can be called with a pointer to uninitialized memory. So, we cannot legally construct a &mut [u8] slice from that pointer since we're only allowed to construct such a slice if the pointer points to initialized memory.

The function $path has a parameter of type &mut [u8] but to support the _raw API and/or the _uninit API it must take &mut [MaybeUninit<u8>] or a raw pointer and length instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One solution would be to have register_custom_getrandom! pre-initialize the output buffer before constructing slice, similar to what the JS code does. We could add a register_custom_getrandom_uninit! that does the same thing without the redundant zero pre-initialization that uses MaybeUninit<u8> instead.

match f(slice) {
Ok(()) => 0,
Err(e) => e.code().get(),
Expand All @@ -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)),
Expand Down
8 changes: 4 additions & 4 deletions src/dragonfly.rs
Expand Up @@ -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)
}
}
6 changes: 3 additions & 3 deletions src/espidf.rs
Expand Up @@ -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(())
}
4 changes: 2 additions & 2 deletions src/fuchsia.rs
Expand Up @@ -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(())
}
11 changes: 5 additions & 6 deletions src/ios.rs
Expand Up @@ -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),
}
}
32 changes: 17 additions & 15 deletions src/js.rs
Expand Up @@ -5,7 +5,7 @@
// <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::raw_chunks, Error};

extern crate std;
use std::thread_local;
Expand All @@ -28,32 +28,34 @@ thread_local!(
static RNG_SOURCE: Result<RngSource, Error> = 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);
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
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(())
}
})
}

Expand Down