From 286e48666796b7c901021716a8def8f122b40013 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 9 Oct 2024 16:03:26 +0300 Subject: [PATCH 1/3] Test `getrandom_uninit` (#505) --- tests/mod.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/tests/mod.rs b/tests/mod.rs index 6738c0e1..951b65dc 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,4 +1,5 @@ -use getrandom::getrandom; +use core::mem::MaybeUninit; +use getrandom::{getrandom, getrandom_uninit}; #[cfg(getrandom_browser_test)] use wasm_bindgen_test::wasm_bindgen_test as test; @@ -9,6 +10,8 @@ wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); fn test_zero() { // Test that APIs are happy with zero-length requests getrandom(&mut [0u8; 0]).unwrap(); + let res = getrandom_uninit(&mut []).unwrap(); + assert!(res.is_empty()); } // Return the number of bits in which s1 and s2 differ @@ -20,34 +23,51 @@ fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { .sum() } +// TODO: use `[const { MaybeUninit::uninit() }; N]` after MSRV is bumped to 1.79+ +// or `MaybeUninit::uninit_array` +fn uninit_vec(n: usize) -> Vec> { + vec![MaybeUninit::uninit(); n] +} + // Tests the quality of calling getrandom on two large buffers #[test] fn test_diff() { - let mut v1 = [0u8; 1000]; + const N: usize = 1000; + let mut v1 = [0u8; N]; + let mut v2 = [0u8; N]; getrandom(&mut v1).unwrap(); - - let mut v2 = [0u8; 1000]; getrandom(&mut v2).unwrap(); + let mut t1 = uninit_vec(N); + let mut t2 = uninit_vec(N); + let r1 = getrandom_uninit(&mut t1).unwrap(); + let r2 = getrandom_uninit(&mut t2).unwrap(); + assert_eq!(r1.len(), N); + assert_eq!(r2.len(), N); + // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] - let d = num_diff_bits(&v1, &v2); - assert!(d > 3500); - assert!(d < 4500); + let d1 = num_diff_bits(&v1, &v2); + assert!(d1 > 3500); + assert!(d1 < 4500); + let d2 = num_diff_bits(r1, r2); + assert!(d2 > 3500); + assert!(d2 < 4500); } -// Tests the quality of calling getrandom repeatedly on small buffers #[test] fn test_small() { - let mut buf1 = [0u8; 64]; - let mut buf2 = [0u8; 64]; + const N: usize = 64; // For each buffer size, get at least 256 bytes and check that between // 3 and 5 bits per byte differ. Probability of failure: // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] - for size in 1..=64 { + for size in 1..=N { let mut num_bytes = 0; let mut diff_bits = 0; while num_bytes < 256 { + let mut buf1 = [0u8; N]; + let mut buf2 = [0u8; N]; + let s1 = &mut buf1[..size]; let s2 = &mut buf2[..size]; @@ -62,12 +82,50 @@ fn test_small() { } } +// Tests the quality of calling getrandom repeatedly on small buffers +#[test] +fn test_small_uninit() { + const N: usize = 64; + // For each buffer size, get at least 256 bytes and check that between + // 3 and 5 bits per byte differ. Probability of failure: + // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] + for size in 1..=N { + let mut num_bytes = 0; + let mut diff_bits = 0; + while num_bytes < 256 { + let mut buf1 = uninit_vec(N); + let mut buf2 = uninit_vec(N); + + let s1 = &mut buf1[..size]; + let s2 = &mut buf2[..size]; + + let r1 = getrandom_uninit(s1).unwrap(); + let r2 = getrandom_uninit(s2).unwrap(); + assert_eq!(r1.len(), size); + assert_eq!(r2.len(), size); + + num_bytes += size; + diff_bits += num_diff_bits(r1, r2); + } + assert!(diff_bits > 3 * num_bytes); + assert!(diff_bits < 5 * num_bytes); + } +} + #[test] fn test_huge() { let mut huge = [0u8; 100_000]; getrandom(&mut huge).unwrap(); } +#[test] +fn test_huge_uninit() { + const N: usize = 100_000; + let mut huge = uninit_vec(N); + let res = getrandom_uninit(&mut huge).unwrap(); + assert_eq!(res.len(), N); +} + #[test] #[cfg_attr( target_arch = "wasm32", From 0f787bc7faee7b782cd98a06833507c47ff0b9c8 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 9 Oct 2024 16:04:52 +0300 Subject: [PATCH 2/3] Test file fallback on Linux (#506) Introduces new `getrandom_test_linux_fallback` configuration flag which forces `is_getrandom_available` to always return `false`. This flag is used in CI to test that file fallback works correctly in the `linux_android_with_fallback` backend. --- .github/workflows/tests.yml | 3 +++ Cargo.toml | 1 + src/linux_android_with_fallback.rs | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 49828c8e..885e2458 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -71,6 +71,9 @@ jobs: - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" run: cargo test ${{ matrix.cargo_test_opts }} --target=${{ matrix.target }} --features=std + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback + run: cargo test --features=std - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" run: cargo test --features=std diff --git a/Cargo.toml b/Cargo.toml index a688779e..83e22501 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ level = "warn" check-cfg = [ 'cfg(getrandom_backend, values("custom", "rdrand", "linux_getrandom", "wasm_js", "esp_idf"))', 'cfg(getrandom_browser_test)', + 'cfg(getrandom_test_linux_fallback)', ] [package.metadata.docs.rs] diff --git a/src/linux_android_with_fallback.rs b/src/linux_android_with_fallback.rs index b633faad..ec7a1216 100644 --- a/src/linux_android_with_fallback.rs +++ b/src/linux_android_with_fallback.rs @@ -19,7 +19,9 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { } fn is_getrandom_available() -> bool { - if linux_android::getrandom_syscall(&mut []) < 0 { + if cfg!(getrandom_test_linux_fallback) { + false + } else if linux_android::getrandom_syscall(&mut []) < 0 { match last_os_error().raw_os_error() { Some(libc::ENOSYS) => false, // No kernel support // The fallback on EPERM is intentionally not done on Android since this workaround From 387248f917f434d537d62a6b48d2f83e79d6b87a Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 9 Oct 2024 16:06:31 +0300 Subject: [PATCH 3/3] Remove `impl From for Error` and `Error::code` (#507) Also adds `Error::{new_custom, new_internal}` methods and changes return type of `__getrandom_custom` to `Result<(), getrandom::Error>`. --- CHANGELOG.md | 3 +++ src/custom.rs | 10 +++------ src/error.rs | 59 +++++++++++++++++++++++---------------------------- src/lib.rs | 10 ++++++--- tests/mod.rs | 8 ++++--- 5 files changed, 44 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b781ac15..196350e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes - Update MSRV to 1.60 [#472] - Remove support of the `wasm32-wasi` target (use `wasm32-wasip1` or `wasm32-wasip2` instead) [#499] +- Remove `impl From for Error` and `Error::code` method [#507] ### Changed - Switch to `futex` on Linux and to `nanosleep`-based wait loop on other targets @@ -16,10 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `wasm32-wasip1` and `wasm32-wasip2` support [#499] +- `Error::new_custom` method [#507] [#472]: https://github.com/rust-random/getrandom/pull/472 [#490]: https://github.com/rust-random/getrandom/pull/490 [#499]: https://github.com/rust-random/getrandom/pull/499 +[#507]: https://github.com/rust-random/getrandom/pull/507 ## [0.2.15] - 2024-05-06 ### Added diff --git a/src/custom.rs b/src/custom.rs index 923b1cf8..fc216e08 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -1,14 +1,10 @@ //! An implementation which calls out to an externally defined function. use crate::Error; -use core::{mem::MaybeUninit, num::NonZeroU32}; +use core::mem::MaybeUninit; pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { extern "Rust" { - fn __getrandom_custom(dest: *mut u8, len: usize) -> u32; - } - let ret = unsafe { __getrandom_custom(dest.as_mut_ptr().cast(), dest.len()) }; - match NonZeroU32::new(ret) { - None => Ok(()), - Some(code) => Err(Error::from(code)), + fn __getrandom_custom(dest: *mut u8, len: usize) -> Result<(), Error>; } + unsafe { __getrandom_custom(dest.as_mut_ptr().cast(), dest.len()) } } diff --git a/src/error.rs b/src/error.rs index 5eff99eb..1c158439 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,44 +20,38 @@ use core::{fmt, num::NonZeroU32}; #[derive(Copy, Clone, Eq, PartialEq)] pub struct Error(NonZeroU32); -const fn internal_error(n: u16) -> Error { - // SAFETY: code > 0 as INTERNAL_START > 0 and adding n won't overflow a u32. - let code = Error::INTERNAL_START + (n as u32); - Error(unsafe { NonZeroU32::new_unchecked(code) }) -} - impl Error { /// This target/platform is not supported by `getrandom`. - pub const UNSUPPORTED: Error = internal_error(0); + pub const UNSUPPORTED: Error = Self::new_internal(0); /// The platform-specific `errno` returned a non-positive value. - pub const ERRNO_NOT_POSITIVE: Error = internal_error(1); + pub const ERRNO_NOT_POSITIVE: Error = Self::new_internal(1); /// Encountered an unexpected situation which should not happen in practice. - pub const UNEXPECTED: Error = internal_error(2); + pub const UNEXPECTED: Error = Self::new_internal(2); /// Call to [`CCRandomGenerateBytes`](https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html) failed /// on iOS, tvOS, or waatchOS. // TODO: Update this constant name in the next breaking release. - pub const IOS_SEC_RANDOM: Error = internal_error(3); + pub const IOS_SEC_RANDOM: Error = Self::new_internal(3); /// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed. - pub const WINDOWS_RTL_GEN_RANDOM: Error = internal_error(4); + pub const WINDOWS_RTL_GEN_RANDOM: Error = Self::new_internal(4); /// RDRAND instruction failed due to a hardware issue. - pub const FAILED_RDRAND: Error = internal_error(5); + pub const FAILED_RDRAND: Error = Self::new_internal(5); /// RDRAND instruction unsupported on this target. - pub const NO_RDRAND: Error = internal_error(6); + pub const NO_RDRAND: Error = Self::new_internal(6); /// The environment does not support the Web Crypto API. - pub const WEB_CRYPTO: Error = internal_error(7); + pub const WEB_CRYPTO: Error = Self::new_internal(7); /// Calling Web Crypto API `crypto.getRandomValues` failed. - pub const WEB_GET_RANDOM_VALUES: Error = internal_error(8); + pub const WEB_GET_RANDOM_VALUES: Error = Self::new_internal(8); /// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized). - pub const VXWORKS_RAND_SECURE: Error = internal_error(11); + pub const VXWORKS_RAND_SECURE: Error = Self::new_internal(11); /// Node.js does not have the `crypto` CommonJS module. - pub const NODE_CRYPTO: Error = internal_error(12); + pub const NODE_CRYPTO: Error = Self::new_internal(12); /// Calling Node.js function `crypto.randomFillSync` failed. - pub const NODE_RANDOM_FILL_SYNC: Error = internal_error(13); + pub const NODE_RANDOM_FILL_SYNC: Error = Self::new_internal(13); /// Called from an ES module on Node.js. This is unsupported, see: /// . - pub const NODE_ES_MODULE: Error = internal_error(14); + pub const NODE_ES_MODULE: Error = Self::new_internal(14); /// Calling Windows ProcessPrng failed. - pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15); + pub const WINDOWS_PROCESS_PRNG: Error = Self::new_internal(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 @@ -108,13 +102,18 @@ impl Error { } } - /// Extract the bare error code. - /// - /// This code can either come from the underlying OS, or be a custom error. - /// Use [`Error::raw_os_error()`] to disambiguate. - #[inline] - pub const fn code(self) -> NonZeroU32 { - self.0 + /// Creates a new instance of an `Error` from a particular custom error code. + pub const fn new_custom(n: u16) -> Error { + // SAFETY: code > 0 as CUSTOM_START > 0 and adding n won't overflow a u32. + let code = Error::CUSTOM_START + (n as u32); + Error(unsafe { NonZeroU32::new_unchecked(code) }) + } + + /// Creates a new instance of an `Error` from a particular internal error code. + const fn new_internal(n: u16) -> Error { + // SAFETY: code > 0 as INTERNAL_START > 0 and adding n won't overflow a u32. + let code = Error::INTERNAL_START + (n as u32); + Error(unsafe { NonZeroU32::new_unchecked(code) }) } } @@ -153,12 +152,6 @@ impl fmt::Display for Error { } } -impl From for Error { - fn from(code: NonZeroU32) -> Self { - Self(code) - } -} - fn internal_desc(error: Error) -> Option<&'static str> { match error { Error::UNSUPPORTED => Some("getrandom: this target is not supported"), diff --git a/src/lib.rs b/src/lib.rs index 62c2dab3..cab0c69e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,8 +105,10 @@ //! signature: //! //! ``` +//! use getrandom::Error; +//! //! #[no_mangle] -//! unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { +//! unsafe extern "Rust" fn __getrandom_custom(dest: *mut u8, len: usize) -> Result<(), Error> { //! todo!() //! } //! ``` @@ -126,9 +128,11 @@ //! it gets pulled nevertheless by one of your dependencies, then you can //! use the following custom backend which always returns "unsupported" error: //! ``` +//! use getrandom::Error; +//! //! #[no_mangle] -//! unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { -//! getrandom::Error::UNSUPPORTED.code().get() +//! unsafe extern "Rust" fn __getrandom_custom(dest: *mut u8, len: usize) -> Result<(), Error> { +//! Err(Error::UNSUPPORTED) //! } //! ``` //! diff --git a/tests/mod.rs b/tests/mod.rs index 951b65dc..33083df5 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -160,6 +160,8 @@ fn test_multithreading() { #[cfg(getrandom_backend = "custom")] mod custom { + use getrandom::Error; + struct Xoshiro128PlusPlus { s: [u32; 4], } @@ -204,13 +206,13 @@ mod custom { // // WARNING: this custom implementation is for testing purposes ONLY! #[no_mangle] - unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { + unsafe extern "Rust" fn __getrandom_custom(dest: *mut u8, len: usize) -> Result<(), Error> { use std::time::{SystemTime, UNIX_EPOCH}; assert_ne!(len, 0); if len == 142 { - return getrandom::Error::CUSTOM_START + 142; + return Err(Error::new_custom(142)); } let dest_u32 = dest.cast::(); @@ -227,7 +229,7 @@ mod custom { core::ptr::write_unaligned(dest.add(i), val as u8); } } - 0 + Ok(()) } // Test that enabling the custom feature indeed uses the custom implementation