From 6d7cb5bd3a4ec1f68c4549ce730b634154e57438 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 24 Mar 2023 16:14:10 -0700 Subject: [PATCH] Draft: Options API Right now we only have a single option `Option::DEFAULT`. However, in the future, we could add options to: - guarantee the function doesn't block - use a insecure but quicker system source (for seeding hashmaps). - not use a fallback mechanism - never use the custom RNG source - always prefere the custom RNG source if it exists Not all of these are things we should _necessarily_ do, but this gives us the flexibility to add such options in the future. Signed-off-by: Joe Richey --- benches/buffer.rs | 6 ++- build.rs | 25 +++++++++++++ src/lib.rs | 95 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 build.rs diff --git a/benches/buffer.rs b/benches/buffer.rs index b32be433..1e3e78e6 100644 --- a/benches/buffer.rs +++ b/benches/buffer.rs @@ -3,11 +3,13 @@ extern crate test; use std::mem::MaybeUninit; +use getrandom::Options; + // Call getrandom on a zero-initialized stack buffer #[inline(always)] fn bench_getrandom() { let mut buf = [0u8; N]; - getrandom::getrandom(&mut buf).unwrap(); + Options::DEFAULT.fill(&mut buf).unwrap(); test::black_box(&buf as &[u8]); } @@ -15,7 +17,7 @@ fn bench_getrandom() { #[inline(always)] fn bench_getrandom_uninit() { let mut uninit = [MaybeUninit::uninit(); N]; - let buf: &[u8] = getrandom::getrandom_uninit(&mut uninit).unwrap(); + let buf: &[u8] = Options::DEFAULT.fill_uninit(&mut uninit).unwrap(); test::black_box(buf); } diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..cd43fcae --- /dev/null +++ b/build.rs @@ -0,0 +1,25 @@ +use std::env; +use std::process::Command; +use std::str; + +fn main() { + let minor_ver = rustc_minor().expect("failed to get rustc version"); + + if minor_ver >= 40 { + println!("cargo:rustc-cfg=getrandom_non_exhaustive"); + } +} + +// Based on libc's implementation: +// https://github.com/rust-lang/libc/blob/74e81a50c2528b01507e9d03f594949c0f91c817/build.rs#L168-L205 +fn rustc_minor() -> Option { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?.stdout; + let version = str::from_utf8(&output).ok()?; + let mut pieces = version.split('.'); + if pieces.next()? != "rustc 1" { + return None; + } + let minor = pieces.next()?; + minor.parse().ok() +} diff --git a/src/lib.rs b/src/lib.rs index 52af12c4..edecf959 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,7 +193,8 @@ #[macro_use] extern crate cfg_if; -use crate::util::slice_as_uninit_mut; +use crate::util::{slice_as_uninit_mut, slice_assume_init_mut}; +use core::mem::MaybeUninit; mod error; mod util; @@ -290,22 +291,84 @@ cfg_if! { /// Fill `dest` with random bytes from the system's preferred random number /// source. /// -/// This function returns an error on any failure, including partial reads. We -/// make no guarantees regarding the contents of `dest` on error. If `dest` is -/// empty, `getrandom` immediately returns success, making no calls to the -/// underlying operating system. -/// -/// Blocking is possible, at least during early boot; see module documentation. -/// -/// 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). +/// Convinence alias for `Options::DEFAULT.fill(dest)`. For more info, see +/// [`Options::DEFAULT`] and [`Options::fill`]. #[inline] pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { - if dest.is_empty() { - return Ok(()); + Options::DEFAULT.fill(dest) +} + +/// Options for specifying how random bytes should be generated. +/// +/// Currently, [`Options::DEFAULT`] is the only allowed option, but we may add +/// additional options in the future (hense why this enum is `non_exhaustive`). +#[derive(Clone, Copy, Debug)] +#[cfg_attr(getrandom_non_exhaustive, non_exhaustive)] +pub enum Options { + /// Use the system's preferred random number source. + /// + /// This implementation is garunteed to produce cryptographically random + /// bytes on success. However, it may block in order to do so, + /// [especially during early boot](https://docs.rs/getrandom#early-boot). + /// + /// In general, this sources will be fast enough for + /// interactive usage, though significantly slower than a user-space CSPRNG. + /// For a user-space CSPRNG seeded from this source, consider + /// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html). + DEFAULT, +} + +impl Options { + /// Fill `dest` with random bytes. + /// + /// This function returns an error on any failure, including partial reads. + /// We make no guarantees regarding the contents of `dest` on error. If + /// `dest` is empty, immediately return success, making no calls to the + /// underlying system RNG source. + #[inline] + pub fn fill(self, dest: &mut [u8]) -> Result<(), Error> { + if dest.is_empty() { + return Ok(()); + } + // SAFETY: The &mut [MaybeUninit] reference doesn't escape, and + // `getrandom_inner` will never de-initialize any part of `dest`. + imp::getrandom_inner(unsafe { slice_as_uninit_mut(dest) }) + } + + /// Initialize `dest` with random bytes. + /// + /// On success, this function is guaranteed to return a slice which points + /// to the same memory as `dest` and has the same length. In this case, it + /// is safe to assume that `dest` is initialized. + /// + /// On either success or failure, no part of `dest` will ever be + /// de-initialized at any point. + /// + /// # Examples + /// + /// ``` + /// # #![feature(maybe_uninit_uninit_array)] + /// # use core::mem::MaybeUninit; + /// # fn foo() -> Result<(), getrandom::Error> { + /// use getrandom::Options; + /// + /// let mut buf = MaybeUninit::uninit_array::<1024>(); + /// let buf: &mut [u8] = Options::DEFAULT.fill_uninit(&mut buf)?; + /// # Ok(()) } + /// ``` + #[inline] + pub fn fill_uninit(self, dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { + if !dest.is_empty() { + imp::getrandom_inner(dest)?; + } + // SAFETY: `dest` has been fully initialized by `imp::getrandom_inner` + Ok(unsafe { slice_assume_init_mut(dest) }) + } +} + +// TODO(MSRV 1.62): Use #[derive(Default)] +impl Default for Options { + fn default() -> Self { + Self::DEFAULT } - // SAFETY: The &mut [MaybeUninit] reference doesn't escape, and - // `getrandom_inner` will never de-initialize any part of `dest`. - imp::getrandom_inner(unsafe { slice_as_uninit_mut(dest) }) }