about summary refs log tree commit diff
path: root/library/std
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-06-24 01:39:19 +0000
committerbors <bors@rust-lang.org>2025-06-24 01:39:19 +0000
commit99b18d6c5062449db8e7ccded4cb69b555a239c3 (patch)
tree26d624ba1b9891e96ea8688943c4224fe3c1f0fa /library/std
parent706f244db581212cabf2e619e0113d70999b2bbe (diff)
parentb7a9cd871c06d97aa7dadbbcf018c6aec24b1ffd (diff)
downloadrust-99b18d6c5062449db8e7ccded4cb69b555a239c3.tar.gz
rust-99b18d6c5062449db8e7ccded4cb69b555a239c3.zip
Auto merge of #142929 - workingjubilee:rollup-4p3ypz1, r=workingjubilee
Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#140985 (Change `core::iter::Fuse`'s `Default` impl to do what its docs say it does)
 - rust-lang/rust#141324 (std: sys: random: uefi: Provide rdrand based fallback)
 - rust-lang/rust#142134 (Reject unsupported `extern "{abi}"`s consistently in all positions)
 - rust-lang/rust#142784 (Add codegen timing section)
 - rust-lang/rust#142827 (Move error code explanation removal check into tidy)
 - rust-lang/rust#142873 (Don't suggest changing a  method inside a expansion)
 - rust-lang/rust#142908 (Fix install-template.sh for Solaris tr)
 - rust-lang/rust#142922 (Fix comment on NoMangle)
 - rust-lang/rust#142923 (fix `-Zmin-function-alignment` on functions without attributes)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'library/std')
-rw-r--r--library/std/src/sys/random/uefi.rs169
1 files changed, 150 insertions, 19 deletions
diff --git a/library/std/src/sys/random/uefi.rs b/library/std/src/sys/random/uefi.rs
index a4d29e66f38..5f001f0f532 100644
--- a/library/std/src/sys/random/uefi.rs
+++ b/library/std/src/sys/random/uefi.rs
@@ -1,27 +1,158 @@
-use r_efi::protocols::rng;
+pub fn fill_bytes(bytes: &mut [u8]) {
+    // Handle zero-byte request
+    if bytes.is_empty() {
+        return;
+    }
+
+    // Try EFI_RNG_PROTOCOL
+    if rng_protocol::fill_bytes(bytes) {
+        return;
+    }
 
-use crate::sys::pal::helpers;
+    // Fallback to rdrand if rng protocol missing.
+    //
+    // For real-world example, see [issue-13825](https://github.com/rust-lang/rust/issues/138252#issuecomment-2891270323)
+    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+    if rdrand::fill_bytes(bytes) {
+        return;
+    }
 
-pub fn fill_bytes(bytes: &mut [u8]) {
-    let handles =
-        helpers::locate_handles(rng::PROTOCOL_GUID).expect("failed to generate random data");
-    for handle in handles {
-        if let Ok(protocol) = helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
-            let r = unsafe {
-                ((*protocol.as_ptr()).get_rng)(
-                    protocol.as_ptr(),
-                    crate::ptr::null_mut(),
-                    bytes.len(),
-                    bytes.as_mut_ptr(),
-                )
+    panic!("failed to generate random data");
+}
+
+mod rng_protocol {
+    use r_efi::protocols::rng;
+
+    use crate::sys::pal::helpers;
+
+    pub(crate) fn fill_bytes(bytes: &mut [u8]) -> bool {
+        if let Ok(handles) = helpers::locate_handles(rng::PROTOCOL_GUID) {
+            for handle in handles {
+                if let Ok(protocol) =
+                    helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID)
+                {
+                    let r = unsafe {
+                        ((*protocol.as_ptr()).get_rng)(
+                            protocol.as_ptr(),
+                            crate::ptr::null_mut(),
+                            bytes.len(),
+                            bytes.as_mut_ptr(),
+                        )
+                    };
+                    if r.is_error() {
+                        continue;
+                    } else {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        false
+    }
+}
+
+/// Port from [getrandom](https://github.com/rust-random/getrandom/blob/master/src/backends/rdrand.rs)
+#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+mod rdrand {
+    cfg_if::cfg_if! {
+        if #[cfg(target_arch = "x86_64")] {
+            use crate::arch::x86_64 as arch;
+            use arch::_rdrand64_step as rdrand_step;
+            type Word = u64;
+        } else if #[cfg(target_arch = "x86")] {
+            use crate::arch::x86 as arch;
+            use arch::_rdrand32_step as rdrand_step;
+            type Word = u32;
+        }
+    }
+
+    static RDRAND_GOOD: crate::sync::LazyLock<bool> = crate::sync::LazyLock::new(is_rdrand_good);
+
+    // Recommendation from "Intel® Digital Random Number Generator (DRNG) Software
+    // Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures
+    // Software Developer’s Manual" - Volume 1 - Section 7.3.17.1.
+    const RETRY_LIMIT: usize = 10;
+
+    unsafe fn rdrand() -> Option<Word> {
+        for _ in 0..RETRY_LIMIT {
+            let mut val = 0;
+            if unsafe { rdrand_step(&mut val) } == 1 {
+                return Some(val);
+            }
+        }
+        None
+    }
+
+    // Run a small self-test to make sure we aren't repeating values
+    // Adapted from Linux's test in arch/x86/kernel/cpu/rdrand.c
+    // Fails with probability < 2^(-90) on 32-bit systems
+    unsafe fn self_test() -> bool {
+        // On AMD, RDRAND returns 0xFF...FF on failure, count it as a collision.
+        let mut prev = Word::MAX;
+        let mut fails = 0;
+        for _ in 0..8 {
+            match unsafe { rdrand() } {
+                Some(val) if val == prev => fails += 1,
+                Some(val) => prev = val,
+                None => return false,
             };
-            if r.is_error() {
-                continue;
-            } else {
-                return;
+        }
+        fails <= 2
+    }
+
+    fn is_rdrand_good() -> bool {
+        #[cfg(not(target_feature = "rdrand"))]
+        {
+            // SAFETY: All Rust x86 targets are new enough to have CPUID, and we
+            // check that leaf 1 is supported before using it.
+            let cpuid0 = unsafe { arch::__cpuid(0) };
+            if cpuid0.eax < 1 {
+                return false;
+            }
+            let cpuid1 = unsafe { arch::__cpuid(1) };
+
+            let vendor_id =
+                [cpuid0.ebx.to_le_bytes(), cpuid0.edx.to_le_bytes(), cpuid0.ecx.to_le_bytes()];
+            if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] {
+                let mut family = (cpuid1.eax >> 8) & 0xF;
+                if family == 0xF {
+                    family += (cpuid1.eax >> 20) & 0xFF;
+                }
+                // AMD CPUs families before 17h (Zen) sometimes fail to set CF when
+                // RDRAND fails after suspend. Don't use RDRAND on those families.
+                // See https://bugzilla.redhat.com/show_bug.cgi?id=1150286
+                if family < 0x17 {
+                    return false;
+                }
+            }
+
+            const RDRAND_FLAG: u32 = 1 << 30;
+            if cpuid1.ecx & RDRAND_FLAG == 0 {
+                return false;
             }
         }
+
+        // SAFETY: We have already checked that rdrand is available.
+        unsafe { self_test() }
     }
 
-    panic!("failed to generate random data");
+    unsafe fn rdrand_exact(dest: &mut [u8]) -> Option<()> {
+        let mut chunks = dest.array_chunks_mut();
+        for chunk in &mut chunks {
+            *chunk = unsafe { rdrand() }?.to_ne_bytes();
+        }
+
+        let tail = chunks.into_remainder();
+        let n = tail.len();
+        if n > 0 {
+            let src = unsafe { rdrand() }?.to_ne_bytes();
+            tail.copy_from_slice(&src[..n]);
+        }
+        Some(())
+    }
+
+    pub(crate) fn fill_bytes(bytes: &mut [u8]) -> bool {
+        if *RDRAND_GOOD { unsafe { rdrand_exact(bytes).is_some() } } else { false }
+    }
 }