Skip to content

Commit 72ae456

Browse files
committed
Make the RNG fall back to RtlGenRandom if BCryptGenRandom fails
Based on rust-lang/rust#96917, which became obsolete with rust-lang/rust#102044
1 parent d233b32 commit 72ae456

File tree

2 files changed

+75
-1
lines changed

2 files changed

+75
-1
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ js-sys = { version = "0.3", optional = true }
2929
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies]
3030
wasm-bindgen-test = "0.3.18"
3131

32+
[target.'cfg(windows)'.dependencies]
33+
once_cell = "1.13.1"
34+
3235
[features]
3336
# Implement std-only traits for getrandom::Error
3437
std = []

src/windows.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@
88

99
use crate::Error;
1010
use core::{convert::TryInto, ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr};
11+
use once_cell::sync::OnceCell;
1112

1213
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;
1314

15+
/// The kinds of RNG that may be available
16+
#[derive(Clone, Copy, Debug, PartialEq)]
17+
enum Rng {
18+
Preferred,
19+
Fallback,
20+
}
21+
1422
#[link(name = "bcrypt")]
1523
extern "system" {
1624
fn BCryptGenRandom(
@@ -21,6 +29,14 @@ extern "system" {
2129
) -> u32;
2230
}
2331

32+
#[cfg(not(target_vendor = "uwp"))]
33+
#[link(name = "advapi32")]
34+
extern "system" {
35+
// Forbidden when targeting UWP
36+
#[link_name = "SystemFunction036"]
37+
pub fn RtlGenRandom(RandomBuffer: *mut u8, RandomBufferLength: u32) -> u8;
38+
}
39+
2440
// BCryptGenRandom was introduced in Windows Vista. However, CNG Algorithm
2541
// Pseudo-handles (specifically BCRYPT_RNG_ALG_HANDLE) weren't introduced
2642
// until Windows 10, so we cannot use them yet. Note that on older systems
@@ -52,10 +68,65 @@ fn bcrypt_random(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
5268
Err(Error::from(code))
5369
}
5470

71+
/// Generate random numbers using the fallback RNG function (RtlGenRandom)
72+
#[cfg(not(target_vendor = "uwp"))]
73+
fn fallback_rng(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
74+
let ret = unsafe { RtlGenRandom(dest.as_mut_ptr() as *mut u8, dest.len() as u32) };
75+
if ret == 0 {
76+
return Err(Error::WINDOWS_RTL_GEN_RANDOM);
77+
}
78+
Ok(())
79+
}
80+
81+
/// We can't use RtlGenRandom with UWP, so there is no fallback
82+
#[cfg(target_vendor = "uwp")]
83+
fn fallback_rng(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
84+
Err(Error::UNSUPPORTED)
85+
}
86+
5587
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
88+
let rng_fn = get_rng();
89+
5690
// Prevent overflow of u32
5791
for chunk in dest.chunks_mut(u32::max_value() as usize) {
58-
bcrypt_random(chunk)?;
92+
rng_fn(chunk)?;
5993
}
6094
Ok(())
6195
}
96+
97+
/// Returns the RNG that should be used
98+
///
99+
/// Panics if they are both broken
100+
fn get_rng() -> fn(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
101+
// Assume that if the preferred RNG is broken the first time we use it, it likely means
102+
// that: the DLL has failed to load, there is no point to calling it over-and-over again,
103+
// and we should cache the result
104+
static VALUE: OnceCell<Rng> = OnceCell::new();
105+
match VALUE.get_or_init(choose_rng) {
106+
Rng::Preferred => bcrypt_random,
107+
Rng::Fallback => fallback_rng,
108+
}
109+
}
110+
111+
/// Test whether we should use the preferred or fallback RNG
112+
///
113+
/// If the preferred RNG is successful, we choose it. Otherwise, if the fallback RNG is successful,
114+
/// we choose that
115+
///
116+
/// Panics if both the preferred and the fallback RNG are both non-functional
117+
fn choose_rng() -> Rng {
118+
let mut dest = [MaybeUninit::uninit(); 1];
119+
120+
let preferred_error = match bcrypt_random(&mut dest) {
121+
Ok(_) => return Rng::Preferred,
122+
Err(e) => e,
123+
};
124+
125+
match fallback_rng(&mut dest) {
126+
Ok(_) => return Rng::Fallback,
127+
Err(fallback_error) => panic!(
128+
"preferred RNG broken: `{}`, fallback RNG broken: `{}`",
129+
preferred_error, fallback_error
130+
),
131+
}
132+
}

0 commit comments

Comments
 (0)