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
Based on rust-lang/rust#96917,
which became obsolete with rust-lang/rust#102044
  • Loading branch information
badboy committed Jan 11, 2023
1 parent d83f4b5 commit 086a1dc
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 1 deletion.
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(any(target_arch = "wasm32", target_arch = "wasm64"), 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
73 changes: 72 additions & 1 deletion src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@

use crate::Error;
use core::{convert::TryInto, ffi::c_void, mem::MaybeUninit, 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,6 +29,14 @@ extern "system" {
) -> u32;
}

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

// BCryptGenRandom was introduced in Windows Vista. However, CNG Algorithm
// Pseudo-handles (specifically BCRYPT_RNG_ALG_HANDLE) weren't introduced
// until Windows 10, so we cannot use them yet. Note that on older systems
Expand Down Expand Up @@ -52,10 +68,65 @@ fn bcrypt_random(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
Err(Error::from(code))
}

/// Generate random numbers using the fallback RNG function (RtlGenRandom)
#[cfg(not(target_vendor = "uwp"))]
fn fallback_rng(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
let ret = unsafe { RtlGenRandom(dest.as_mut_ptr() as *mut u8, dest.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 [MaybeUninit<u8>]) -> Result<(), Error> {
Err(Error::UNSUPPORTED)
}

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
let rng_fn = get_rng();

// Prevent overflow of u32
for chunk in dest.chunks_mut(u32::max_value() as usize) {
bcrypt_random(chunk)?;
rng_fn(chunk)?;
}
Ok(())
}

/// Returns the RNG that should be used
///
/// Panics if they are both broken
fn get_rng() -> fn(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// 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();
match VALUE.get_or_init(choose_rng) {
Rng::Preferred => bcrypt_random,
Rng::Fallback => fallback_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 = [MaybeUninit::uninit(); 1];

let preferred_error = match bcrypt_random(&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
),
}
}

0 comments on commit 086a1dc

Please sign in to comment.