Skip to content

Commit 2def6f9

Browse files
committed
Make the RNG fall back to RtlGenRandom if BCryptGenRandom fails
Based on rust-lang/rust#96917
1 parent d3aa089 commit 2def6f9

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
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(target_arch = "wasm32", 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: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,22 @@
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
88

9+
// Implementation copied from rustc's `library/std/src/sys/windows/rand.rs`.
10+
// Includes a fallback to `RtlGenRandom` in case `BCryptGenRandom` fails.
11+
912
use crate::Error;
1013
use core::{ffi::c_void, num::NonZeroU32, ptr};
14+
use once_cell::sync::OnceCell;
1115

1216
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;
1317

18+
/// The kinds of RNG that may be available
19+
#[derive(Clone, Copy, Debug, PartialEq)]
20+
enum Rng {
21+
Preferred,
22+
Fallback,
23+
}
24+
1425
#[link(name = "bcrypt")]
1526
extern "system" {
1627
fn BCryptGenRandom(
@@ -21,7 +32,59 @@ extern "system" {
2132
) -> u32;
2233
}
2334

35+
#[link(name = "advapi32")]
36+
extern "system" {
37+
// Forbidden when targeting UWP
38+
#[link_name = "SystemFunction036"]
39+
pub fn RtlGenRandom(RandomBuffer: *mut u8, RandomBufferLength: u32) -> u8;
40+
}
41+
2442
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
43+
match get_rng() {
44+
Rng::Preferred => {
45+
preferred_rng(dest)
46+
}
47+
Rng::Fallback => {
48+
fallback_rng(dest)
49+
}
50+
}
51+
}
52+
53+
/// Returns the RNG that should be used
54+
///
55+
/// Panics if they are both broken
56+
fn get_rng() -> Rng {
57+
// Assume that if the preferred RNG is broken the first time we use it, it likely means
58+
// that: the DLL has failed to load, there is no point to calling it over-and-over again,
59+
// and we should cache the result
60+
static VALUE: OnceCell<Rng> = OnceCell::new();
61+
*VALUE.get_or_init(choose_rng)
62+
}
63+
64+
/// Test whether we should use the preferred or fallback RNG
65+
///
66+
/// If the preferred RNG is successful, we choose it. Otherwise, if the fallback RNG is successful,
67+
/// we choose that
68+
///
69+
/// Panics if both the preferred and the fallback RNG are both non-functional
70+
fn choose_rng() -> Rng {
71+
let mut dest = [0; 1];
72+
73+
let preferred_error = match preferred_rng(&mut dest) {
74+
Ok(_) => return Rng::Preferred,
75+
Err(e) => e,
76+
};
77+
78+
match fallback_rng(&mut dest) {
79+
Ok(_) => return Rng::Fallback,
80+
Err(fallback_error) => panic!(
81+
"preferred RNG broken: `{}`, fallback RNG broken: `{}`",
82+
preferred_error, fallback_error
83+
),
84+
}
85+
}
86+
87+
fn preferred_rng(dest: &mut [u8]) -> Result<(), Error> {
2588
// Prevent overflow of u32
2689
for chunk in dest.chunks_mut(u32::max_value() as usize) {
2790
// BCryptGenRandom was introduced in Windows Vista
@@ -33,6 +96,7 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
3396
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
3497
)
3598
};
99+
36100
// NTSTATUS codes use the two highest bits for severity status.
37101
if ret >> 30 == 0b11 {
38102
// We zeroize the highest bit, so the error code will reside
@@ -47,3 +111,22 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
47111
}
48112
Ok(())
49113
}
114+
115+
/// Generate random numbers using the fallback RNG function (RtlGenRandom)
116+
#[cfg(not(target_vendor = "uwp"))]
117+
fn fallback_rng(dest: &mut [u8]) -> Result<(), Error> {
118+
// Prevent overflow of u32
119+
for chunk in dest.chunks_mut(u32::max_value() as usize) {
120+
let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr(), chunk.len() as u32) };
121+
if ret == 0 {
122+
return Err(Error::WINDOWS_RTL_GEN_RANDOM);
123+
}
124+
}
125+
Ok(())
126+
}
127+
128+
/// We can't use RtlGenRandom with UWP, so there is no fallback
129+
#[cfg(target_vendor = "uwp")]
130+
fn fallback_rng(_dest: &mut [u8]) -> Result<(), Error> {
131+
Err(Error::UNSUPPORTED)
132+
}

0 commit comments

Comments
 (0)