-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of #108060 - ChrisDenton:rtlgenrandom, r=thomcc
Revert to using `RtlGenRandom` as a fallback This is required due to `BCryptGenRandom` failing to load a dll it depends on. Fixes #108059
- Loading branch information
Showing
2 changed files
with
31 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,106 +1,39 @@ | ||
//! # Random key generation | ||
//! | ||
//! This module wraps the RNG provided by the OS. There are a few different | ||
//! ways to interface with the OS RNG so it's worth exploring each of the options. | ||
//! Note that at the time of writing these all go through the (undocumented) | ||
//! `bcryptPrimitives.dll` but they use different route to get there. | ||
//! | ||
//! Originally we were using [`RtlGenRandom`], however that function is | ||
//! deprecated and warns it "may be altered or unavailable in subsequent versions". | ||
//! | ||
//! So we switched to [`BCryptGenRandom`] with the `BCRYPT_USE_SYSTEM_PREFERRED_RNG` | ||
//! flag to query and find the system configured RNG. However, this change caused a small | ||
//! but significant number of users to experience panics caused by a failure of | ||
//! this function. See [#94098]. | ||
//! | ||
//! The current version falls back to using `BCryptOpenAlgorithmProvider` if | ||
//! `BCRYPT_USE_SYSTEM_PREFERRED_RNG` fails for any reason. | ||
//! | ||
//! [#94098]: https://github.com/rust-lang/rust/issues/94098 | ||
//! [`RtlGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom | ||
//! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom | ||
use crate::io; | ||
use crate::mem; | ||
use crate::ptr; | ||
use crate::sys::c; | ||
|
||
/// Generates high quality secure random keys for use by [`HashMap`]. | ||
/// | ||
/// This is used to seed the default [`RandomState`]. | ||
/// | ||
/// [`HashMap`]: crate::collections::HashMap | ||
/// [`RandomState`]: crate::collections::hash_map::RandomState | ||
pub fn hashmap_random_keys() -> (u64, u64) { | ||
Rng::SYSTEM.gen_random_keys().unwrap_or_else(fallback_rng) | ||
let mut v = (0, 0); | ||
let ret = unsafe { | ||
c::BCryptGenRandom( | ||
ptr::null_mut(), | ||
&mut v as *mut _ as *mut u8, | ||
mem::size_of_val(&v) as c::ULONG, | ||
c::BCRYPT_USE_SYSTEM_PREFERRED_RNG, | ||
) | ||
}; | ||
if c::nt_success(ret) { v } else { fallback_rng() } | ||
} | ||
|
||
struct Rng { | ||
algorithm: c::BCRYPT_ALG_HANDLE, | ||
flags: u32, | ||
} | ||
impl Rng { | ||
const SYSTEM: Self = unsafe { Self::new(ptr::null_mut(), c::BCRYPT_USE_SYSTEM_PREFERRED_RNG) }; | ||
|
||
/// Create the RNG from an existing algorithm handle. | ||
/// | ||
/// # Safety | ||
/// | ||
/// The handle must either be null or a valid algorithm handle. | ||
const unsafe fn new(algorithm: c::BCRYPT_ALG_HANDLE, flags: u32) -> Self { | ||
Self { algorithm, flags } | ||
} | ||
|
||
/// Open a handle to the RNG algorithm. | ||
fn open() -> Result<Self, c::NTSTATUS> { | ||
use crate::sync::atomic::AtomicPtr; | ||
use crate::sync::atomic::Ordering::{Acquire, Release}; | ||
|
||
// An atomic is used so we don't need to reopen the handle every time. | ||
static HANDLE: AtomicPtr<crate::ffi::c_void> = AtomicPtr::new(ptr::null_mut()); | ||
|
||
let mut handle = HANDLE.load(Acquire); | ||
if handle.is_null() { | ||
let status = unsafe { | ||
c::BCryptOpenAlgorithmProvider( | ||
&mut handle, | ||
c::BCRYPT_RNG_ALGORITHM.as_ptr(), | ||
ptr::null(), | ||
0, | ||
) | ||
}; | ||
if c::nt_success(status) { | ||
// If another thread opens a handle first then use that handle instead. | ||
let result = HANDLE.compare_exchange(ptr::null_mut(), handle, Release, Acquire); | ||
if let Err(previous_handle) = result { | ||
// Close our handle and return the previous one. | ||
unsafe { c::BCryptCloseAlgorithmProvider(handle, 0) }; | ||
handle = previous_handle; | ||
} | ||
Ok(unsafe { Self::new(handle, 0) }) | ||
} else { | ||
Err(status) | ||
} | ||
} else { | ||
Ok(unsafe { Self::new(handle, 0) }) | ||
} | ||
} | ||
/// Generate random numbers using the fallback RNG function (RtlGenRandom) | ||
/// | ||
/// This is necessary because of a failure to load the SysWOW64 variant of the | ||
/// bcryptprimitives.dll library from code that lives in bcrypt.dll | ||
/// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1788004#c9> | ||
#[cfg(not(target_vendor = "uwp"))] | ||
#[inline(never)] | ||
fn fallback_rng() -> (u64, u64) { | ||
let mut v = (0, 0); | ||
let ret = | ||
unsafe { c::RtlGenRandom(&mut v as *mut _ as *mut u8, mem::size_of_val(&v) as c::ULONG) }; | ||
|
||
fn gen_random_keys(self) -> Result<(u64, u64), c::NTSTATUS> { | ||
let mut v = (0, 0); | ||
let status = unsafe { | ||
let size = mem::size_of_val(&v).try_into().unwrap(); | ||
c::BCryptGenRandom(self.algorithm, ptr::addr_of_mut!(v).cast(), size, self.flags) | ||
}; | ||
if c::nt_success(status) { Ok(v) } else { Err(status) } | ||
} | ||
if ret != 0 { v } else { panic!("fallback RNG broken: {}", io::Error::last_os_error()) } | ||
} | ||
|
||
/// Generate random numbers using the fallback RNG function | ||
/// We can't use RtlGenRandom with UWP, so there is no fallback | ||
#[cfg(target_vendor = "uwp")] | ||
#[inline(never)] | ||
fn fallback_rng(rng_status: c::NTSTATUS) -> (u64, u64) { | ||
match Rng::open().and_then(|rng| rng.gen_random_keys()) { | ||
Ok(keys) => keys, | ||
Err(status) => { | ||
panic!("RNG broken: {rng_status:#x}, fallback RNG broken: {status:#x}") | ||
} | ||
} | ||
fn fallback_rng() -> (u64, u64) { | ||
panic!("fallback RNG broken: RtlGenRandom() not supported on UWP"); | ||
} |