Skip to content

Commit 875081b

Browse files
authored
Use ProcessPrng on Windows (#415)
Use [`ProcessPrng`](https://learn.microsoft.com/en-us/windows/win32/seccng/processprng) on Windows 10 and up, and use [`RtlGenRandom`](https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) on older legacy Windows versions. Don't use [`BCryptGenRandom`](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom) due to stability issues. --------- Signed-off-by: Joe Richey <[email protected]>
1 parent 0afee65 commit 875081b

File tree

7 files changed

+95
-58
lines changed

7 files changed

+95
-58
lines changed

.github/workflows/tests.yml

+15-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
strategy:
4545
matrix:
4646
os: [ubuntu-22.04, windows-2022]
47-
toolchain: [nightly, beta, stable, 1.38]
47+
toolchain: [nightly, beta, stable, 1.56]
4848
# Only Test macOS on stable to reduce macOS CI jobs
4949
include:
5050
# x86_64-apple-darwin.
@@ -159,6 +159,20 @@ jobs:
159159
- uses: Swatinem/rust-cache@v2
160160
- run: cargo test --features=std
161161

162+
windows7:
163+
name: Test Windows 7 impl on Windows 10
164+
runs-on: windows-2022
165+
steps:
166+
- uses: actions/checkout@v3
167+
# Win7 targets are Tier3, so pin a nightly where libstd builds.
168+
- uses: dtolnay/rust-toolchain@master
169+
with:
170+
toolchain: nightly-2024-05-20
171+
components: rust-src
172+
- uses: Swatinem/rust-cache@v2
173+
- run: cargo test --target=x86_64-win7-windows-msvc -Z build-std --features=std
174+
- run: cargo test --target=i686-win7-windows-msvc -Z build-std --features=std
175+
162176
cross-tests:
163177
name: Cross Test
164178
runs-on: ubuntu-22.04

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name = "getrandom"
33
version = "0.2.15" # Also update html_root_url in lib.rs when bumping this
44
edition = "2018"
5+
rust-version = "1.56"
56
authors = ["The Rand Project Developers"]
67
license = "MIT OR Apache-2.0"
78
description = "A small cross-platform library for retrieving random data from system source"
@@ -23,6 +24,9 @@ libc = { version = "0.2.154", default-features = false }
2324
[target.'cfg(target_os = "wasi")'.dependencies]
2425
wasi = { version = "0.11", default-features = false }
2526

27+
[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
28+
windows-targets = "0.52"
29+
2630
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies]
2731
wasm-bindgen = { version = "0.2.62", default-features = false, optional = true }
2832
js-sys = { version = "0.3", optional = true }

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ crate features, WASM support and Custom RNGs see the
5252

5353
## Minimum Supported Rust Version
5454

55-
This crate requires Rust 1.38.0 or later.
55+
This crate requires Rust 1.56.0 or later.
5656

5757
## Platform Support
5858

src/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ impl Error {
5353
/// Called from an ES module on Node.js. This is unsupported, see:
5454
/// <https://docs.rs/getrandom#nodejs-es-module-support>.
5555
pub const NODE_ES_MODULE: Error = internal_error(14);
56+
/// Calling Windows ProcessPrng failed.
57+
pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15);
5658

5759
/// Codes below this point represent OS Errors (i.e. positive i32 values).
5860
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
@@ -172,6 +174,7 @@ fn internal_desc(error: Error) -> Option<&'static str> {
172174
Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"),
173175
Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"),
174176
Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"),
177+
Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"),
175178
_ => None,
176179
}
177180
}

src/lib.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
//! | Target | Target Triple | Implementation
66
//! | ----------------- | ------------------ | --------------
77
//! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random`
8-
//! | Windows | `*‑windows‑*` | [`BCryptGenRandom`]
8+
//! | Windows 10+ | `*‑windows‑*` | [`ProcessPrng`]
9+
//! | Windows 7 and 8 | `*-win7‑windows‑*` | [`RtlGenRandom`]
910
//! | macOS | `*‑apple‑darwin` | [`getentropy`][3]
1011
//! | iOS, tvOS, watchOS | `*‑apple‑ios`, `*-apple-tvos`, `*-apple-watchos` | [`CCRandomGenerateBytes`]
1112
//! | FreeBSD | `*‑freebsd` | [`getrandom`][5]
@@ -182,7 +183,8 @@
182183
//! [17]: https://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getrandom
183184
//! [18]: https://github.com/rust3ds/shim-3ds/commit/b01d2568836dea2a65d05d662f8e5f805c64389d
184185
//!
185-
//! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
186+
//! [`ProcessPrng`]: https://learn.microsoft.com/en-us/windows/win32/seccng/processprng
187+
//! [`RtlGenRandom`]: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
186188
//! [`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues
187189
//! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide
188190
//! [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html
@@ -323,6 +325,8 @@ cfg_if! {
323325
#[path = "solid.rs"] mod imp;
324326
} else if #[cfg(target_os = "espidf")] {
325327
#[path = "espidf.rs"] mod imp;
328+
} else if #[cfg(all(windows, target_vendor = "win7"))] {
329+
#[path = "windows7.rs"] mod imp;
326330
} else if #[cfg(windows)] {
327331
#[path = "windows.rs"] mod imp;
328332
} else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] {

src/windows.rs

+32-54
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,38 @@
1-
//! Implementation for Windows
1+
//! Implementation for Windows 10 and later
2+
//!
3+
//! On Windows 10 and later, ProcessPrng "is the primary interface to the
4+
//! user-mode per-processer PRNGs" and only requires bcryptprimitives.dll,
5+
//! making it a better option than the other Windows RNG APIs:
6+
//! - BCryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
7+
//! - Requires bcrypt.dll (which loads bcryptprimitives.dll anyway)
8+
//! - Can cause crashes/hangs as BCrypt accesses the Windows Registry:
9+
//! https://github.com/rust-lang/rust/issues/99341
10+
//! - Causes issues inside sandboxed code:
11+
//! https://issues.chromium.org/issues/40277768
12+
//! - CryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
13+
//! - Deprecated and not available on UWP targets
14+
//! - Requires advapi32.lib/advapi32.dll (in addition to bcryptprimitives.dll)
15+
//! - Thin wrapper around ProcessPrng
16+
//! - RtlGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
17+
//! - Deprecated and not available on UWP targets
18+
//! - Requires advapi32.dll (in addition to bcryptprimitives.dll)
19+
//! - Requires using name "SystemFunction036"
20+
//! - Thin wrapper around ProcessPrng
21+
//! For more information see the Windows RNG Whitepaper: https://aka.ms/win10rng
222
use crate::Error;
3-
use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr};
23+
use core::mem::MaybeUninit;
424

5-
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;
6-
7-
#[link(name = "bcrypt")]
8-
extern "system" {
9-
fn BCryptGenRandom(
10-
hAlgorithm: *mut c_void,
11-
pBuffer: *mut u8,
12-
cbBuffer: u32,
13-
dwFlags: u32,
14-
) -> u32;
15-
}
16-
17-
// Forbidden when targetting UWP
18-
#[cfg(not(target_vendor = "uwp"))]
19-
#[link(name = "advapi32")]
20-
extern "system" {
21-
#[link_name = "SystemFunction036"]
22-
fn RtlGenRandom(RandomBuffer: *mut c_void, RandomBufferLength: u32) -> u8;
23-
}
25+
// Binding to the Windows.Win32.Security.Cryptography.ProcessPrng API. As
26+
// bcryptprimitives.dll lacks an import library, we use the windows-targets
27+
// crate to link to it.
28+
windows_targets::link!("bcryptprimitives.dll" "system" fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> BOOL);
29+
pub type BOOL = i32;
30+
pub const TRUE: BOOL = 1i32;
2431

2532
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
26-
// Prevent overflow of u32
27-
for chunk in dest.chunks_mut(u32::max_value() as usize) {
28-
// BCryptGenRandom was introduced in Windows Vista
29-
let ret = unsafe {
30-
BCryptGenRandom(
31-
ptr::null_mut(),
32-
chunk.as_mut_ptr().cast::<u8>(),
33-
chunk.len() as u32,
34-
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
35-
)
36-
};
37-
// NTSTATUS codes use the two highest bits for severity status.
38-
if ret >> 30 == 0b11 {
39-
// Failed. Try RtlGenRandom as a fallback.
40-
#[cfg(not(target_vendor = "uwp"))]
41-
{
42-
let ret = unsafe {
43-
RtlGenRandom(chunk.as_mut_ptr().cast::<c_void>(), chunk.len() as u32)
44-
};
45-
if ret != 0 {
46-
continue;
47-
}
48-
}
49-
// We zeroize the highest bit, so the error code will reside
50-
// inside the range designated for OS codes.
51-
let code = ret ^ (1 << 31);
52-
// SAFETY: the second highest bit is always equal to one,
53-
// so it's impossible to get zero. Unfortunately the type
54-
// system does not have a way to express this yet.
55-
let code = unsafe { NonZeroU32::new_unchecked(code) };
56-
return Err(Error::from(code));
57-
}
33+
// ProcessPrng should always return TRUE, but we check just in case.
34+
match unsafe { ProcessPrng(dest.as_mut_ptr().cast::<u8>(), dest.len()) } {
35+
TRUE => Ok(()),
36+
_ => Err(Error::WINDOWS_PROCESS_PRNG),
5837
}
59-
Ok(())
6038
}

src/windows7.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//! Legacy implementation for Windows XP and later
2+
//!
3+
//! For targets where we cannot use ProcessPrng (added in Windows 10), we use
4+
//! RtlGenRandom. See windows.rs for a more detailed discussion of the Windows
5+
//! RNG APIs (and why we don't use BCryptGenRandom). On versions prior to
6+
//! Windows 10, this implementation is secure. On Windows 10 and later, this
7+
//! implementation behaves identically to the windows.rs implementation, except
8+
//! that it forces the loading of an additonal DLL (advapi32.dll).
9+
//!
10+
//! This implementation will not work on UWP targets (which lack advapi32.dll),
11+
//! but such targets require Windows 10, so can use the standard implementation.
12+
use crate::Error;
13+
use core::{ffi::c_void, mem::MaybeUninit};
14+
15+
// Binding to the Windows.Win32.Security.Authentication.Identity.RtlGenRandom
16+
// API. Don't use windows-targets as it doesn't support Windows 7 targets.
17+
#[link(name = "advapi32")]
18+
extern "system" {
19+
#[link_name = "SystemFunction036"]
20+
fn RtlGenRandom(randombuffer: *mut c_void, randombufferlength: u32) -> BOOLEAN;
21+
}
22+
type BOOLEAN = u8;
23+
const TRUE: BOOLEAN = 1u8;
24+
25+
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
26+
// Prevent overflow of u32
27+
for chunk in dest.chunks_mut(u32::max_value() as usize) {
28+
let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr().cast::<c_void>(), chunk.len() as u32) };
29+
if ret != TRUE {
30+
return Err(Error::WINDOWS_RTL_GEN_RANDOM);
31+
}
32+
}
33+
Ok(())
34+
}

0 commit comments

Comments
 (0)