Skip to content

Commit e5bf112

Browse files
committed
Make the RNG fall back to using an algorithm handle if BCryptGenRandom fails
Based on rust-lang/rust#102044
1 parent 7f73e3c commit e5bf112

1 file changed

Lines changed: 119 additions & 19 deletions

File tree

src/windows.rs

Lines changed: 119 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,145 @@
55
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
8+
#![allow(non_camel_case_types)]
89

910
use crate::Error;
10-
use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr};
11+
use core::{
12+
convert::TryInto,
13+
ffi::{c_long, c_void},
14+
mem::MaybeUninit,
15+
num::NonZeroU32,
16+
ptr,
17+
};
1118

19+
type BCRYPT_ALG_HANDLE = *mut c_void;
20+
type NTSTATUS = c_long;
21+
22+
// "RNG\0"
23+
const BCRYPT_RNG_ALGORITHM: &[u16] = &[b'R' as u16, b'N' as u16, b'G' as u16, 0];
1224
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;
1325

26+
// Equivalent to the `NT_SUCCESS` C preprocessor macro.
27+
// See: https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/using-ntstatus-values
28+
fn nt_success(status: NTSTATUS) -> bool {
29+
status >= 0
30+
}
31+
32+
/// Extract error code and turn into an `Error`
33+
fn nt_error(status: NTSTATUS) -> Error {
34+
// We zeroize the highest bit, so the error code will reside
35+
// inside the range designated for OS codes.
36+
let code = status as u32 ^ (1 << 31);
37+
// SAFETY: the second highest bit is always equal to one,
38+
// so it's impossible to get zero. Unfortunately the type
39+
// system does not have a way to express this yet.
40+
let code = unsafe { NonZeroU32::new_unchecked(code) };
41+
Error::from(code)
42+
}
43+
1444
#[link(name = "bcrypt")]
1545
extern "system" {
1646
fn BCryptGenRandom(
17-
hAlgorithm: *mut c_void,
47+
hAlgorithm: BCRYPT_ALG_HANDLE,
1848
pBuffer: *mut u8,
1949
cbBuffer: u32,
2050
dwFlags: u32,
21-
) -> u32;
51+
) -> NTSTATUS;
52+
pub fn BCryptOpenAlgorithmProvider(
53+
phalgorithm: *mut BCRYPT_ALG_HANDLE,
54+
pszAlgId: *const u16,
55+
pszimplementation: *const u16,
56+
dwflags: u32,
57+
) -> NTSTATUS;
58+
pub fn BCryptCloseAlgorithmProvider(hAlgorithm: BCRYPT_ALG_HANDLE, dwFlags: u32) -> NTSTATUS;
2259
}
2360

2461
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
62+
let mut rng = Rng::SYSTEM;
63+
let mut failed_once = false;
2564
// Prevent overflow of u32
2665
for chunk in dest.chunks_mut(u32::max_value() as usize) {
27-
// BCryptGenRandom was introduced in Windows Vista
66+
let res = rng.random(chunk);
67+
if res.is_err() {
68+
if failed_once {
69+
return res;
70+
}
71+
rng = Rng::open()?;
72+
rng.random(chunk)?;
73+
failed_once = true;
74+
}
75+
}
76+
Ok(())
77+
}
78+
79+
struct Rng {
80+
algorithm: BCRYPT_ALG_HANDLE,
81+
flags: u32,
82+
}
83+
84+
impl Rng {
85+
const SYSTEM: Self = unsafe { Self::new(ptr::null_mut(), BCRYPT_USE_SYSTEM_PREFERRED_RNG) };
86+
87+
/// Create the RNG from an existing algorithm handle.
88+
///
89+
/// # Safety
90+
///
91+
/// The handle must either be null or a valid algorithm handle.
92+
const unsafe fn new(algorithm: BCRYPT_ALG_HANDLE, flags: u32) -> Self {
93+
Self { algorithm, flags }
94+
}
95+
96+
/// Open a handle to the RNG algorithm.
97+
fn open() -> Result<Self, Error> {
98+
use core::sync::atomic::AtomicPtr;
99+
use core::sync::atomic::Ordering::{Acquire, Release};
100+
101+
// An atomic is used so we don't need to reopen the handle every time.
102+
static HANDLE: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
103+
104+
let mut handle = HANDLE.load(Acquire);
105+
if handle.is_null() {
106+
let status = unsafe {
107+
BCryptOpenAlgorithmProvider(
108+
&mut handle,
109+
BCRYPT_RNG_ALGORITHM.as_ptr(),
110+
ptr::null(),
111+
0,
112+
)
113+
};
114+
if nt_success(status) {
115+
// If another thread opens a handle first then use that handle instead.
116+
let result = HANDLE.compare_exchange(ptr::null_mut(), handle, Release, Acquire);
117+
if let Err(previous_handle) = result {
118+
// Close our handle and return the previous one.
119+
unsafe { BCryptCloseAlgorithmProvider(handle, 0) };
120+
handle = previous_handle;
121+
}
122+
Ok(unsafe { Self::new(handle, 0) })
123+
} else {
124+
Err(nt_error(status))
125+
}
126+
} else {
127+
Ok(unsafe { Self::new(handle, 0) })
128+
}
129+
}
130+
131+
fn random(&self, dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
132+
let len: u32 = dest.len().try_into().unwrap();
133+
// SAFETY: dest is valid, writable buffer of length len
28134
let ret = unsafe {
29135
BCryptGenRandom(
30-
ptr::null_mut(),
31-
chunk.as_mut_ptr() as *mut u8,
32-
chunk.len() as u32,
33-
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
136+
self.algorithm,
137+
dest.as_mut_ptr() as *mut u8,
138+
len,
139+
self.flags,
34140
)
35141
};
36-
// NTSTATUS codes use the two highest bits for severity status.
37-
if ret >> 30 == 0b11 {
38-
// We zeroize the highest bit, so the error code will reside
39-
// inside the range designated for OS codes.
40-
let code = ret ^ (1 << 31);
41-
// SAFETY: the second highest bit is always equal to one,
42-
// so it's impossible to get zero. Unfortunately the type
43-
// system does not have a way to express this yet.
44-
let code = unsafe { NonZeroU32::new_unchecked(code) };
45-
return Err(Error::from(code));
142+
143+
if nt_success(ret) {
144+
return Ok(());
46145
}
146+
147+
Err(nt_error(ret))
47148
}
48-
Ok(())
49149
}

0 commit comments

Comments
 (0)