Skip to content

Commit

Permalink
Implement raw API
Browse files Browse the repository at this point in the history
  • Loading branch information
newpavlov committed Sep 29, 2022
1 parent 9a64857 commit c12acd9
Show file tree
Hide file tree
Showing 25 changed files with 234 additions and 204 deletions.
58 changes: 10 additions & 48 deletions benches/mod.rs
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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) };
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 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 = unsafe { __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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -27,32 +27,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);
n.random_fill_sync(dst)
.map_err(|_| Error::NODE_RANDOM_FILL_SYNC)
}
RngSource::Browser(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(BROWSER_CRYPTO_BUFFER_SIZE) {
raw_chunks(dst, len, BROWSER_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
42 changes: 37 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,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
Expand All @@ -264,9 +264,41 @@ 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
///
/// ```ignore
/// # // We ignore this doctest because `MaybeUninit` was stabilized
/// # // in Rust 1.36, while this crate has MSRV equal to 1.34.
/// # 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),
}
imp::getrandom_inner(dest)
}
9 changes: 5 additions & 4 deletions src/linux_android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
Loading

0 comments on commit c12acd9

Please sign in to comment.