From 0b9c0b86c0e0346ede6a4a1b88d39c223baf4e0c Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 24 Mar 2023 14:38:24 -0700 Subject: [PATCH 1/3] ci: use minimal permissions for Github Actions Fixes #348 we only need to read the contents of the repo to run our tests, no other permissions are needed, as we currently do not publish via our CI jobs. Signed-off-by: Joe Richey --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a8717d7f..0c9f3f15 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,6 +8,9 @@ on: schedule: - cron: "0 12 * * 1" +permissions: + contents: read + env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" From 7f37234dea89424beefb6f8812a59b58afcf1323 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 24 Mar 2023 18:54:44 -0700 Subject: [PATCH 2/3] Revert Public API of #291 Signed-off-by: Joe Richey --- src/lib.rs | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 26490e69..52af12c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,8 +193,7 @@ #[macro_use] extern crate cfg_if; -use crate::util::{slice_as_uninit_mut, slice_assume_init_mut}; -use core::mem::MaybeUninit; +use crate::util::slice_as_uninit_mut; mod error; mod util; @@ -303,40 +302,10 @@ cfg_if! { /// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html). #[inline] pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { - // SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, and - // `getrandom_uninit` guarantees it will never de-initialize any part of - // `dest`. - getrandom_uninit(unsafe { slice_as_uninit_mut(dest) })?; - Ok(()) -} - -/// 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`. -/// -/// No part of `dest` will ever be de-initialized at any point, regardless -/// of what is returned. -/// -/// # 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(&mut buf)?; -/// # Ok(()) } -/// ``` -#[inline] -pub fn getrandom_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { - if !dest.is_empty() { - imp::getrandom_inner(dest)?; + if dest.is_empty() { + return Ok(()); } - // SAFETY: `dest` has been fully initialized by `imp::getrandom_inner` - // since it returned `Ok`. - Ok(unsafe { slice_assume_init_mut(dest) }) + // 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) }) } From 06b6850a28ef6b7487e8b0b75cfd11c0792042fe Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 24 Mar 2023 16:14:10 -0700 Subject: [PATCH 3/3] 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..08cb0e16 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 + /// + /// ``` + /// # use core::mem::MaybeUninit; + /// # fn uninit_example() -> Result<(), getrandom::Error> { + /// use getrandom::Options; + /// + /// let mut buf = [MaybeUninit::::uninit(); 1024]; + /// let buf: &mut [u8] = Options::DEFAULT.fill_uninit(&mut buf)?; + /// # Ok(()) } + /// # uninit_example().unwrap(); + /// ``` + #[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) }) }