Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ProcessPrng on Windows #415

Merged
merged 6 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-22.04, windows-2022]
toolchain: [nightly, beta, stable, 1.38]
toolchain: [nightly, beta, stable, 1.56]
# Only Test macOS on stable to reduce macOS CI jobs
include:
# x86_64-apple-darwin.
Expand Down Expand Up @@ -159,6 +159,18 @@ jobs:
- uses: Swatinem/rust-cache@v2
- run: cargo test --features=std

windows7:
name: Test Windows 7 impl on Windows 10
runs-on: windows-2022
josephlr marked this conversation as resolved.
Show resolved Hide resolved
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- uses: Swatinem/rust-cache@v2
- run: cargo test --target=x86_64-win7-windows-msvc -Z build-std --features=std
- run: cargo test --target=i686-win7-windows-msvc -Z build-std --features=std

cross-tests:
name: Cross Test
runs-on: ubuntu-22.04
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "getrandom"
version = "0.2.15" # Also update html_root_url in lib.rs when bumping this
edition = "2018"
rust-version = "1.56"
authors = ["The Rand Project Developers"]
license = "MIT OR Apache-2.0"
description = "A small cross-platform library for retrieving random data from system source"
Expand All @@ -23,6 +24,9 @@ libc = { version = "0.2.154", default-features = false }
[target.'cfg(target_os = "wasi")'.dependencies]
wasi = { version = "0.11", default-features = false }

[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
windows-targets = "0.52"

[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies]
wasm-bindgen = { version = "0.2.62", default-features = false, optional = true }
js-sys = { version = "0.3", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ crate features, WASM support and Custom RNGs see the

## Minimum Supported Rust Version

This crate requires Rust 1.38.0 or later.
This crate requires Rust 1.56.0 or later.

## Platform Support

Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ impl Error {
/// Called from an ES module on Node.js. This is unsupported, see:
/// <https://docs.rs/getrandom#nodejs-es-module-support>.
pub const NODE_ES_MODULE: Error = internal_error(14);
/// Calling Windows ProcessPrng failed.
pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15);

/// Codes below this point represent OS Errors (i.e. positive i32 values).
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
Expand Down Expand Up @@ -172,6 +174,7 @@ fn internal_desc(error: Error) -> Option<&'static str> {
Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"),
Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"),
Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"),
Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"),
_ => None,
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
//! | Target | Target Triple | Implementation
//! | ----------------- | ------------------ | --------------
//! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random`
//! | Windows | `*‑windows‑*` | [`BCryptGenRandom`]
//! | Windows 10+ | `*‑windows‑*` | [`ProcessPrng`]
//! | Windows 7 and 8 | `*-win7‑windows‑*` | [`RtlGenRandom`]
//! | macOS | `*‑apple‑darwin` | [`getentropy`][3]
//! | iOS, tvOS, watchOS | `*‑apple‑ios`, `*-apple-tvos`, `*-apple-watchos` | [`CCRandomGenerateBytes`]
//! | FreeBSD | `*‑freebsd` | [`getrandom`][5]
Expand Down Expand Up @@ -182,7 +183,8 @@
//! [17]: https://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getrandom
//! [18]: https://github.com/rust3ds/shim-3ds/commit/b01d2568836dea2a65d05d662f8e5f805c64389d
//!
//! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
//! [`ProcessPrng`]: https://learn.microsoft.com/en-us/windows/win32/seccng/processprng
//! [`RtlGenRandom`]: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
//! [`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues
//! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide
//! [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html
Expand Down Expand Up @@ -323,6 +325,8 @@ cfg_if! {
#[path = "solid.rs"] mod imp;
} else if #[cfg(target_os = "espidf")] {
#[path = "espidf.rs"] mod imp;
} else if #[cfg(all(windows, target_vendor = "win7"))] {
#[path = "windows7.rs"] mod imp;
} else if #[cfg(windows)] {
#[path = "windows.rs"] mod imp;
} else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] {
Expand Down
88 changes: 34 additions & 54 deletions src/windows.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,40 @@
//! Implementation for Windows
//! Implementation for Windows 10 and later
//!
//! On Windows 10 and later, ProcessPrng "is the primary interface to the
//! user-mode per-processer PRNGs" and only requires BCryptPrimitives.dll,
//! making it a better option than the other Windows RNG APIs:
//! - BCryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
//! - Requires Bcrypt.dll (which loads BCryptPrimitives.dll anyway)
//! - Can cause crashes/hangs as BCrypt accesses the Windows Registry:
//! https://github.com/rust-lang/rust/issues/99341
//! - Causes issues inside sandboxed code:
//! https://issues.chromium.org/issues/40277768
//! - CryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
//! - Deprecated and not available on UWP targets
//! - Requires Advapi32.lib/Advapi32.dll
//! - Wrapper around ProcessPrng
//! - RtlGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
//! - Deprecated and not available on UWP targets
//! - Requires Advapi32.dll (and using name "SystemFunction036")
//! - Wrapper around ProcessPrng
josephlr marked this conversation as resolved.
Show resolved Hide resolved
//! For more information see the Windows RNG Whitepaper: https://aka.ms/win10rng
use crate::Error;
use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr};
use core::mem::MaybeUninit;

const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;

#[link(name = "bcrypt")]
extern "system" {
fn BCryptGenRandom(
hAlgorithm: *mut c_void,
pBuffer: *mut u8,
cbBuffer: u32,
dwFlags: u32,
) -> u32;
}

// Forbidden when targetting UWP
#[cfg(not(target_vendor = "uwp"))]
#[link(name = "advapi32")]
extern "system" {
#[link_name = "SystemFunction036"]
fn RtlGenRandom(RandomBuffer: *mut c_void, RandomBufferLength: u32) -> u8;
}
// ProcessPrng lacks an import library, so we use the windows-targets crate to
// link to it. The following code was generated via windows-bindgen with APIs:
// Windows.Win32.Foundation.TRUE
// Windows.Win32.Security.Cryptography.ProcessPrng
windows_targets::link!("bcryptprimitives.dll" "system" fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> BOOL);
#[repr(transparent)]
#[derive(PartialEq, Eq)]
pub struct BOOL(pub i32);
pub const TRUE: BOOL = BOOL(1i32);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate to have this ugliness without the program that generated it to confirm that it actually created this ugliness. However, I also think it's fine because of how repr(transparent) works so it isn't the end of the world.

Copy link
Member Author

@josephlr josephlr May 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good point. For both APIs I didn't directly use the output of windows-bindgen, but instead took that output as a starting point, then deleted lines until I got code that looked decent.

I think that referencing windows-bindgen in the comments at all was misleading, so I changed the comments to just reference the API metadata names directly. Given that, I also changed the BOOL/BOOLEAN types to just be typedefs (which is more honest IMO).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, here's the full output for running windows-bindgen with the following arguments:

// Bindings generated by `windows-bindgen` 0.56.0

#![allow(
    non_snake_case,
    non_upper_case_globals,
    non_camel_case_types,
    dead_code,
    clippy::all
)]
#[inline]
pub unsafe fn RtlGenRandom(
    randombuffer: *mut core::ffi::c_void,
    randombufferlength: u32,
) -> BOOLEAN {
    windows_targets::link!("advapi32.dll" "system" "SystemFunction036" fn RtlGenRandom(randombuffer : *mut core::ffi::c_void, randombufferlength : u32) -> BOOLEAN);
    RtlGenRandom(randombuffer, randombufferlength)
}
#[inline]
pub unsafe fn ProcessPrng(pbdata: &mut [u8]) -> BOOL {
    windows_targets::link!("bcryptprimitives.dll" "system" fn ProcessPrng(pbdata : *mut u8, cbdata : usize) -> BOOL);
    ProcessPrng(
        core::mem::transmute(pbdata.as_ptr()),
        pbdata.len().try_into().unwrap(),
    )
}
#[repr(transparent)]
#[derive(PartialEq, Eq)]
pub struct BOOL(pub i32);
impl Default for BOOL {
    fn default() -> Self {
        unsafe { core::mem::zeroed() }
    }
}
impl Clone for BOOL {
    fn clone(&self) -> Self {
        *self
    }
}
impl Copy for BOOL {}
impl core::fmt::Debug for BOOL {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_tuple("BOOL").field(&self.0).finish()
    }
}
impl windows_core::TypeKind for BOOL {
    type TypeKind = windows_core::CopyType;
}
#[repr(transparent)]
#[derive(PartialEq, Eq)]
pub struct BOOLEAN(pub u8);
impl Default for BOOLEAN {
    fn default() -> Self {
        unsafe { core::mem::zeroed() }
    }
}
impl Clone for BOOLEAN {
    fn clone(&self) -> Self {
        *self
    }
}
impl Copy for BOOLEAN {}
impl core::fmt::Debug for BOOLEAN {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_tuple("BOOLEAN").field(&self.0).finish()
    }
}
impl windows_core::TypeKind for BOOLEAN {
    type TypeKind = windows_core::CopyType;
}
pub const TRUE: BOOL = BOOL(1i32);

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// Prevent overflow of u32
for chunk in dest.chunks_mut(u32::max_value() as usize) {
// BCryptGenRandom was introduced in Windows Vista
let ret = unsafe {
BCryptGenRandom(
ptr::null_mut(),
chunk.as_mut_ptr().cast::<u8>(),
chunk.len() as u32,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
};
// NTSTATUS codes use the two highest bits for severity status.
if ret >> 30 == 0b11 {
// Failed. Try RtlGenRandom as a fallback.
#[cfg(not(target_vendor = "uwp"))]
{
let ret = unsafe {
RtlGenRandom(chunk.as_mut_ptr().cast::<c_void>(), chunk.len() as u32)
};
if ret != 0 {
continue;
}
}
// We zeroize the highest bit, so the error code will reside
// inside the range designated for OS codes.
let code = ret ^ (1 << 31);
// SAFETY: the second highest bit is always equal to one,
// so it's impossible to get zero. Unfortunately the type
// system does not have a way to express this yet.
let code = unsafe { NonZeroU32::new_unchecked(code) };
return Err(Error::from(code));
}
// ProcessPrng should always return TRUE, but we check just in case.
match unsafe { ProcessPrng(dest.as_mut_ptr().cast::<u8>(), dest.len()) } {
TRUE => Ok(()),
_ => Err(Error::WINDOWS_PROCESS_PRNG),
}
josephlr marked this conversation as resolved.
Show resolved Hide resolved
josephlr marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}
37 changes: 37 additions & 0 deletions src/windows7.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Implementation for Windows 7 and 8
josephlr marked this conversation as resolved.
Show resolved Hide resolved
//!
//! For targets where we cannot use ProcessPrng (added in Windows 10), we use
//! RtlGenRandom. See windows.rs for a more detailed discussion of the Windows
//! RNG APIs (and why we don't use BCryptGenRandom). On versions prior to
//! Windows 10, this implementation works, while on Windows 10 and later, this
//! is a thin wrapper around ProcessPrng.
josephlr marked this conversation as resolved.
Show resolved Hide resolved
//!
//! This implementation will not work on UWP targets, but those targets require
//! Windows 10 regardless, so can use the ProcessPrng implementation.
use crate::Error;
use core::{ffi::c_void, mem::MaybeUninit};

// This code is based on that produced by windows-bindgen with the APIs:
// Windows.Win32.Foundation.TRUE
// Windows.Win32.Security.Authentication.Identity.RtlGenRandom
// but we avoid using windows-targets as it doesn't support older Windows.
#[link(name = "advapi32")]
extern "system" {
#[link_name = "SystemFunction036"]
fn RtlGenRandom(randombuffer: *mut c_void, randombufferlength: u32) -> BOOLEAN;
}
#[repr(transparent)]
#[derive(PartialEq, Eq)]
pub struct BOOLEAN(pub u8);
pub const TRUE: BOOLEAN = BOOLEAN(1u8);
josephlr marked this conversation as resolved.
Show resolved Hide resolved

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// Prevent overflow of u32
for chunk in dest.chunks_mut(u32::max_value() as usize) {
let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr().cast::<c_void>(), chunk.len() as u32) };
if ret != TRUE {
return Err(Error::WINDOWS_RTL_GEN_RANDOM);
}
}
Ok(())
}
Loading