1// Copyright 2018 Developers of the Rand project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8use crate::{
9 util::{slice_as_uninit, LazyBool},
10 Error,
11};
12use core::mem::{size_of, MaybeUninit};
13
14cfg_if! {
15 if #[cfg(target_arch = "x86_64")] {
16 use core::arch::x86_64 as arch;
17 use arch::_rdrand64_step as rdrand_step;
18 } else if #[cfg(target_arch = "x86")] {
19 use core::arch::x86 as arch;
20 use arch::_rdrand32_step as rdrand_step;
21 }
22}
23
24// Recommendation from "Intel® Digital Random Number Generator (DRNG) Software
25// Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures
26// Software Developer’s Manual" - Volume 1 - Section 7.3.17.1.
27const RETRY_LIMIT: usize = 10;
28
29#[target_feature(enable = "rdrand")]
30unsafe fn rdrand() -> Option<usize> {
31 for _ in 0..RETRY_LIMIT {
32 let mut val = 0;
33 if rdrand_step(&mut val) == 1 {
34 return Some(val as usize);
35 }
36 }
37 None
38}
39
40// "rdrand" target feature requires "+rdrand" flag, see https://github.com/rust-lang/rust/issues/49653.
41#[cfg(all(target_env = "sgx", not(target_feature = "rdrand")))]
42compile_error!(
43 "SGX targets require 'rdrand' target feature. Enable by using -C target-feature=+rdrand."
44);
45
46// Run a small self-test to make sure we aren't repeating values
47// Adapted from Linux's test in arch/x86/kernel/cpu/rdrand.c
48// Fails with probability < 2^(-90) on 32-bit systems
49#[target_feature(enable = "rdrand")]
50unsafe fn self_test() -> bool {
51 // On AMD, RDRAND returns 0xFF...FF on failure, count it as a collision.
52 let mut prev = !0; // TODO(MSRV 1.43): Move to usize::MAX
53 let mut fails = 0;
54 for _ in 0..8 {
55 match rdrand() {
56 Some(val) if val == prev => fails += 1,
57 Some(val) => prev = val,
58 None => return false,
59 };
60 }
61 fails <= 2
62}
63
64fn is_rdrand_good() -> bool {
65 #[cfg(not(target_feature = "rdrand"))]
66 {
67 // SAFETY: All Rust x86 targets are new enough to have CPUID, and we
68 // check that leaf 1 is supported before using it.
69 let cpuid0 = unsafe { arch::__cpuid(0) };
70 if cpuid0.eax < 1 {
71 return false;
72 }
73 let cpuid1 = unsafe { arch::__cpuid(1) };
74
75 let vendor_id = [
76 cpuid0.ebx.to_le_bytes(),
77 cpuid0.edx.to_le_bytes(),
78 cpuid0.ecx.to_le_bytes(),
79 ];
80 if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] {
81 let mut family = (cpuid1.eax >> 8) & 0xF;
82 if family == 0xF {
83 family += (cpuid1.eax >> 20) & 0xFF;
84 }
85 // AMD CPUs families before 17h (Zen) sometimes fail to set CF when
86 // RDRAND fails after suspend. Don't use RDRAND on those families.
87 // See https://bugzilla.redhat.com/show_bug.cgi?id=1150286
88 if family < 0x17 {
89 return false;
90 }
91 }
92
93 const RDRAND_FLAG: u32 = 1 << 30;
94 if cpuid1.ecx & RDRAND_FLAG == 0 {
95 return false;
96 }
97 }
98
99 // SAFETY: We have already checked that rdrand is available.
100 unsafe { self_test() }
101}
102
103pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
104 static RDRAND_GOOD: LazyBool = LazyBool::new();
105 if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
106 return Err(Error::NO_RDRAND);
107 }
108 // SAFETY: After this point, we know rdrand is supported.
109 unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND)
110}
111
112// TODO: make this function safe when we have feature(target_feature_11)
113#[target_feature(enable = "rdrand")]
114unsafe fn rdrand_exact(dest: &mut [MaybeUninit<u8>]) -> Option<()> {
115 // We use chunks_exact_mut instead of chunks_mut as it allows almost all
116 // calls to memcpy to be elided by the compiler.
117 let mut chunks = dest.chunks_exact_mut(size_of::<usize>());
118 for chunk in chunks.by_ref() {
119 let src = rdrand()?.to_ne_bytes();
120 chunk.copy_from_slice(slice_as_uninit(&src));
121 }
122
123 let tail = chunks.into_remainder();
124 let n = tail.len();
125 if n > 0 {
126 let src = rdrand()?.to_ne_bytes();
127 tail.copy_from_slice(slice_as_uninit(&src[..n]));
128 }
129 Some(())
130}
131