Skip to content

Commit

Permalink
Make the RNG fall back to RtlGenRandom if BCryptGenRandom fails
Browse files Browse the repository at this point in the history
  • Loading branch information
badboy committed Sep 2, 2022
1 parent d3aa089 commit 432ded5
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ js-sys = { version = "0.3", optional = true }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies]
wasm-bindgen-test = "0.3.18"

[target.'cfg(windows)'.dependencies]
once_cell = "1.13.1"

[features]
# Implement std-only traits for getrandom::Error
std = []
Expand Down
82 changes: 82 additions & 0 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,22 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Implementation copied from rustc's `library/std/src/sys/windows/rand.rs`.
// Includes a fallback to `RtlGenRandom` in case `BCryptGenRandom` fails.

use crate::Error;
use core::{ffi::c_void, num::NonZeroU32, ptr};
use once_cell::sync::OnceCell;

const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;

/// The kinds of RNG that may be available
#[derive(Clone, Copy, Debug, PartialEq)]
enum Rng {
Preferred,
Fallback,
}

#[link(name = "bcrypt")]
extern "system" {
fn BCryptGenRandom(
Expand All @@ -21,7 +32,59 @@ extern "system" {
) -> u32;
}

#[link(name = "advapi32")]
extern "system" {
// Forbidden when targeting UWP
#[link_name = "SystemFunction036"]
pub fn RtlGenRandom(RandomBuffer: *mut u8, RandomBufferLength: u32) -> u8;
}

pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
match get_rng() {
Rng::Preferred => {
preferred_rng(dest)
}
Rng::Fallback => {
fallback_rng(dest)
}
}
}

/// Returns the RNG that should be used
///
/// Panics if they are both broken
fn get_rng() -> Rng {
// Assume that if the preferred RNG is broken the first time we use it, it likely means
// that: the DLL has failed to load, there is no point to calling it over-and-over again,
// and we should cache the result
static VALUE: OnceCell<Rng> = OnceCell::new();
*VALUE.get_or_init(choose_rng)
}

/// Test whether we should use the preferred or fallback RNG
///
/// If the preferred RNG is successful, we choose it. Otherwise, if the fallback RNG is successful,
/// we choose that
///
/// Panics if both the preferred and the fallback RNG are both non-functional
fn choose_rng() -> Rng {
let mut dest = [0; 1];

let preferred_error = match preferred_rng(&mut dest) {
Ok(_) => return Rng::Preferred,
Err(e) => e,
};

match fallback_rng(&mut dest) {
Ok(_) => return Rng::Fallback,
Err(fallback_error) => panic!(
"preferred RNG broken: `{}`, fallback RNG broken: `{}`",
preferred_error, fallback_error
),
}
}

fn preferred_rng(dest: &mut [u8]) -> Result<(), Error> {
// Prevent overflow of u32
for chunk in dest.chunks_mut(u32::max_value() as usize) {
// BCryptGenRandom was introduced in Windows Vista
Expand All @@ -47,3 +110,22 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
}
Ok(())
}

/// Generate random numbers using the fallback RNG function (RtlGenRandom)
#[cfg(not(target_vendor = "uwp"))]
fn fallback_rng(dest: &mut [u8]) -> Result<(), Error> {
// Prevent overflow of u32
for chunk in dest.chunks_mut(u32::max_value() as usize) {
let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr(), chunk.len() as u32) };
if ret == 0 {
return Err(Error::WINDOWS_RTL_GEN_RANDOM);
}
}
Ok(())
}

/// We can't use RtlGenRandom with UWP, so there is no fallback
#[cfg(target_vendor = "uwp")]
fn fallback_rng(_dest: &mut [u8]) -> Result<(), Error> {
Err(Error::UNSUPPORTED)
}

0 comments on commit 432ded5

Please sign in to comment.