Skip to content

Commit

Permalink
Add "insecure" functions
Browse files Browse the repository at this point in the history
  • Loading branch information
newpavlov committed Nov 29, 2024
1 parent e1943e4 commit eef3564
Show file tree
Hide file tree
Showing 27 changed files with 294 additions and 154 deletions.
63 changes: 37 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ library like [`rand`].

[`rand`]: https://crates.io/crates/rand

## Usage
## Examples

Add the `getrandom` dependency to your `Cargo.toml` file:

Expand All @@ -38,6 +38,16 @@ fn get_random_u128() -> Result<u128, getrandom::Error> {
}
```

The crate also support direct generation of random `u32` and `u64` values:

```rust
fn get_rand_u32_u64() -> Result<(u32, u64), getrandom::Error> {
let a = getrandom::u32()?;
let b = getrandom::u64()?;
Ok((a, b))
}
```

## Supported targets

| Target | Target Triple | Implementation
Expand Down Expand Up @@ -106,6 +116,25 @@ WILL NOT have any effect on its downstream users.

[`.cargo/config.toml`]: https://doc.rust-lang.org/cargo/reference/config.html

### "Insecure" functions

Sometimes, early in the boot process, the OS has not collected enough
entropy to securely seed its RNG. This is especially common on virtual
machines, where standard "random" events are hard to come by.

Some operating system interfaces always block until the RNG is securely
seeded, which can take anywhere from a few seconds to more than a minute.
Some platforms offer a choice between blocking and getting potentially less
secure randomness, i.e. generated data may be less difficult for an attacker
to predict.

We expose this functionality through two sets of functions. The "secure"
functions ([`fill`], [`fill_uninit`], [`u32`], and [`u64`]) may block but
always return "secure" randomness suitable for cryptographic needs.
Meanwhile, their "insecure" counterparts ([`insecure_fill`],
[`insecure_fill_uninit`], [`insecure_u32`], and [`insecure_u64`]) may return
randomness of lesser quality but are less likely to block in entropy-starved scenarios.

### WebAssembly support

This crate fully supports the [WASI] and [Emscripten] targets. However,
Expand Down Expand Up @@ -210,31 +239,6 @@ The fallback can be disabled by enabling the `linux_getrandom` opt-in backend.
Note that doing so will bump minimum supported Linux kernel version to 3.17
and Android API level to 23 (Marshmallow).

### Early boot

Sometimes, early in the boot process, the OS has not collected enough
entropy to securely seed its RNG. This is especially common on virtual
machines, where standard "random" events are hard to come by.

Some operating system interfaces always block until the RNG is securely
seeded. This can take anywhere from a few seconds to more than a minute.
A few (Linux, NetBSD and Solaris) offer a choice between blocking and
getting an error; in these cases, we always choose to block.

On Linux (when the `getrandom` system call is not available), reading from
`/dev/urandom` never blocks, even when the OS hasn't collected enough
entropy yet. To avoid returning low-entropy bytes, we first poll
`/dev/random` and only switch to `/dev/urandom` once this has succeeded.

On OpenBSD, this kind of entropy accounting isn't available, and on
NetBSD, blocking on it is discouraged. On these platforms, nonblocking
interfaces are used, even when reliable entropy may not be available.
On the platforms where it is used, the reliability of entropy accounting
itself isn't free from controversy. This library provides randomness
sourced according to the platform's best practices, but each platform has
its own limits on the grade of randomness it can promise in environments
with few sources of entropy.

## Error handling

We always prioritize failure over returning known insecure "random" bytes.
Expand Down Expand Up @@ -346,4 +350,11 @@ dual licensed as above, without any additional terms or conditions.
[LICENSE-MIT]: https://github.com/rust-random/getrandom/blob/master/LICENSE-MIT

[`Error::UNEXPECTED`]: https://docs.rs/getrandom/latest/getrandom/struct.Error.html#associatedconstant.UNEXPECTED
[`fill`]: https://docs.rs/getrandom/latest/getrandom/fn.fill.html
[`fill_uninit`]: https://docs.rs/getrandom/latest/getrandom/fn.fill_uninit.html
[`u32`]: https://docs.rs/getrandom/latest/getrandom/fn.u32.html
[`u64`]: https://docs.rs/getrandom/latest/getrandom/fn.u64.html
[`insecure_fill`]: https://docs.rs/getrandom/latest/getrandom/fn.insecure_fill.html
[`insecure_fill_uninit`]: https://docs.rs/getrandom/latest/getrandom/fn.insecure_fill_uninit.html
[`insecure_u32`]: https://docs.rs/getrandom/latest/getrandom/fn.insecure_u32.html
[`insecure_u64`]: https://docs.rs/getrandom/latest/getrandom/fn.insecure_u64.html
16 changes: 11 additions & 5 deletions src/backends.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
//! System-specific implementations.
//!
//! This module should provide `fill_inner` with the signature
//! `fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error>`.
//! The function MUST fully initialize `dest` when `Ok(())` is returned.
//! The function MUST NOT ever write uninitialized bytes into `dest`,
//! regardless of what value it returns.
//! This module should provide the following functions:
//! - `fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error>`
//! - `fn insecure_fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error>`
//! - `fn u32() -> Result<u32, Error>`
//! - `fn insecure_u32() -> Result<u32, Error>`
//! - `fn u64() -> Result<u64, Error>`
//! - `fn insecure_u64() -> Result<u64, Error>`
//!
//! `fill_uninit` and `insecure_fill_uninit` MUST fully initialize `dest`
//! when `Ok(())` is returned. The functions MUST NOT ever write uninitialized
//! bytes into `dest`, regardless of what value it returns.

cfg_if! {
if #[cfg(getrandom_backend = "custom")] {
Expand Down
4 changes: 2 additions & 2 deletions src/backends/apple_other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
use crate::Error;
use core::{ffi::c_void, mem::MaybeUninit};

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
let dst_ptr = dest.as_mut_ptr().cast::<c_void>();
let ret = unsafe { libc::CCRandomGenerateBytes(dst_ptr, dest.len()) };
if ret == libc::kCCSuccess {
Expand Down
4 changes: 2 additions & 2 deletions src/backends/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
use crate::Error;
use core::mem::MaybeUninit;

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
extern "Rust" {
fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error>;
}
Expand Down
33 changes: 23 additions & 10 deletions src/backends/esp_idf.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
//! Implementation for ESP-IDF
//! Implementation for ESP-IDF.
//!
//! Note 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.
use crate::Error;
use core::{ffi::c_void, mem::MaybeUninit};

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64};

#[cfg(not(target_os = "espidf"))]
compile_error!("`esp_idf` backend can be enabled only for ESP-IDF targets!");

extern "C" {
fn esp_fill_random(buf: *mut c_void, len: usize) -> u32;
fn esp_random() -> u32;
fn esp_fill_random(buf: *mut c_void, len: usize);
}

pub fn fill_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
//
// 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()) };
pub fn u32() -> Result<u32, Error> {
Ok(unsafe { esp_random() })
}

pub fn u64() -> Result<u64, Error> {
let (a, b) = unsafe { (esp_random(), esp_random()) };
let res = (u64::from(a) << 32) | u64::from(b);
Ok(res)
}

pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
unsafe { esp_fill_random(dest.as_mut_ptr().cast(), dest.len()) };
Ok(())
}
4 changes: 2 additions & 2 deletions src/backends/fuchsia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
use crate::Error;
use core::mem::MaybeUninit;

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

#[link(name = "zircon")]
extern "C" {
fn zx_cprng_draw(buffer: *mut u8, length: usize);
}

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
unsafe { zx_cprng_draw(dest.as_mut_ptr().cast::<u8>(), dest.len()) }
Ok(())
}
4 changes: 2 additions & 2 deletions src/backends/getentropy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
use crate::Error;
use core::{ffi::c_void, mem::MaybeUninit};

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

#[path = "../util_libc.rs"]
mod util_libc;

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
for chunk in dest.chunks_mut(256) {
let ret = unsafe { libc::getentropy(chunk.as_mut_ptr().cast::<c_void>(), chunk.len()) };
if ret != 0 {
Expand Down
4 changes: 2 additions & 2 deletions src/backends/getrandom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
use crate::Error;
use core::{ffi::c_void, mem::MaybeUninit};

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

#[path = "../util_libc.rs"]
mod util_libc;

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
util_libc::sys_fill_exact(dest, |buf| unsafe {
libc::getrandom(buf.as_mut_ptr().cast::<c_void>(), buf.len(), 0)
})
Expand Down
8 changes: 5 additions & 3 deletions src/backends/hermit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
use crate::Error;
use core::mem::MaybeUninit;

pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64};

extern "C" {
fn sys_read_entropy(buffer: *mut u8, length: usize, flags: u32) -> isize;
// Note that `sys_secure_rand32/64` are implemented using `sys_read_entropy`:
Expand All @@ -12,7 +14,7 @@ extern "C" {
fn sys_secure_rand64(value: *mut u64) -> i32;
}

pub fn inner_u32() -> Result<u32, Error> {
pub fn u32() -> Result<u32, Error> {
let mut res = MaybeUninit::uninit();
let ret = unsafe { sys_secure_rand32(res.as_mut_ptr()) };
match ret {
Expand All @@ -22,7 +24,7 @@ pub fn inner_u32() -> Result<u32, Error> {
}
}

pub fn inner_u64() -> Result<u64, Error> {
pub fn u64() -> Result<u64, Error> {
let mut res = MaybeUninit::uninit();
let ret = unsafe { sys_secure_rand64(res.as_mut_ptr()) };
match ret {
Expand All @@ -32,7 +34,7 @@ pub fn inner_u64() -> Result<u64, Error> {
}
}

pub fn fill_inner(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
while !dest.is_empty() {
let res = unsafe { sys_read_entropy(dest.as_mut_ptr().cast::<u8>(), dest.len(), 0) };
match res {
Expand Down
4 changes: 2 additions & 2 deletions src/backends/linux_android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
use crate::Error;
use core::mem::MaybeUninit;

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

#[path = "../util_libc.rs"]
mod util_libc;

#[cfg(not(any(target_os = "android", target_os = "linux")))]
compile_error!("`linux_getrandom` backend can be enabled only for Linux/Android targets!");

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
util_libc::sys_fill_exact(dest, |buf| unsafe {
libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0)
})
Expand Down
6 changes: 3 additions & 3 deletions src/backends/linux_android_with_fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use core::{
};
use use_file::util_libc;

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;

Expand Down Expand Up @@ -56,10 +56,10 @@ fn init() -> NonNull<c_void> {
// prevent inlining of the fallback implementation
#[inline(never)]
fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
use_file::fill_inner(dest)
use_file::fill_uninit(dest)
}

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// Despite being only a single atomic variable, we still cannot always use
// Ordering::Relaxed, as we need to make sure a successful call to `init`
// is "ordered before" any data read through the returned pointer (which
Expand Down
4 changes: 2 additions & 2 deletions src/backends/linux_rustix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
use crate::{Error, MaybeUninit};
use rustix::rand::{getrandom_uninit, GetRandomFlags};

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

#[cfg(not(any(target_os = "android", target_os = "linux")))]
compile_error!("`linux_rustix` backend can be enabled only for Linux/Android targets!");

pub fn fill_inner(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
loop {
let res = getrandom_uninit(dest, GetRandomFlags::empty()).map(|(res, _)| res.len());
match res {
Expand Down
4 changes: 2 additions & 2 deletions src/backends/netbsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use core::{
sync::atomic::{AtomicPtr, Ordering},
};

pub use crate::util::{inner_u32, inner_u64};
pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64, u32, u64};

#[path = "../util_libc.rs"]
mod util_libc;
Expand Down Expand Up @@ -62,7 +62,7 @@ fn init() -> *mut c_void {
ptr
}

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// Despite being only a single atomic variable, we still cannot always use
// Ordering::Relaxed, as we need to make sure a successful call to `init`
// is "ordered before" any data read through the returned pointer (which
Expand Down
8 changes: 5 additions & 3 deletions src/backends/rdrand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
use crate::{util::slice_as_uninit, Error};
use core::mem::{size_of, MaybeUninit};

pub use crate::default_impls::{insecure_fill_uninit, insecure_u32, insecure_u64};

#[path = "../lazy.rs"]
mod lazy;

Expand Down Expand Up @@ -147,23 +149,23 @@ unsafe fn rdrand_u64() -> Option<u64> {
Some((u64::from(a) << 32) || u64::from(b))
}

pub fn inner_u32() -> Result<u32, Error> {
pub fn u32() -> Result<u32, Error> {
if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
return Err(Error::NO_RDRAND);
}
// SAFETY: After this point, we know rdrand is supported.
unsafe { rdrand_u32() }.ok_or(Error::FAILED_RDRAND)
}

pub fn inner_u64() -> Result<u64, Error> {
pub fn u64() -> Result<u64, Error> {
if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
return Err(Error::NO_RDRAND);
}
// SAFETY: After this point, we know rdrand is supported.
unsafe { rdrand_u64() }.ok_or(Error::FAILED_RDRAND)
}

pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
return Err(Error::NO_RDRAND);
}
Expand Down
Loading

0 comments on commit eef3564

Please sign in to comment.