1 // Copyright 2017 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6 
7 #include <dev/hw_rng.h>
8 
9 #include <arch/x86/feature.h>
10 #include <arch/x86/x86intrin.h>
11 #include <fbl/algorithm.h>
12 #include <stdbool.h>
13 #include <stdint.h>
14 #include <string.h>
15 #include <sys/types.h>
16 
17 enum entropy_instr {
18     ENTROPY_INSTR_RDSEED,
19     ENTROPY_INSTR_RDRAND,
20 };
21 static ssize_t get_entropy_from_instruction(void* buf, size_t len, bool block,
22                                             enum entropy_instr instr);
23 static ssize_t get_entropy_from_rdseed(void* buf, size_t len, bool block);
24 static ssize_t get_entropy_from_rdrand(void* buf, size_t len, bool block);
25 
26 /* @brief Get entropy from the CPU using RDSEED.
27  *
28  * len must be at most SSIZE_MAX
29  *
30  * If |block|=true, it will retry the RDSEED instruction until |len| bytes are
31  * written to |buf|.  Otherwise, it will fetch data from RDSEED until either
32  * |len| bytes are written to |buf| or RDSEED is unable to return entropy.
33  *
34  * Returns the number of bytes written to the buffer on success (potentially 0),
35  * and a negative value on error.
36  */
get_entropy_from_cpu(void * buf,size_t len,bool block)37 static ssize_t get_entropy_from_cpu(void* buf, size_t len, bool block) {
38     /* TODO(security, ZX-984): Move this to a shared kernel/user lib, so we can write usermode
39      * tests against this code */
40 
41     if (len >= SSIZE_MAX) {
42         static_assert(ZX_ERR_INVALID_ARGS < 0, "");
43         return ZX_ERR_INVALID_ARGS;
44     }
45 
46     if (x86_feature_test(X86_FEATURE_RDSEED)) {
47         return get_entropy_from_rdseed(buf, len, block);
48     } else if (x86_feature_test(X86_FEATURE_RDRAND)) {
49         return get_entropy_from_rdrand(buf, len, block);
50     }
51 
52     /* We don't have an entropy source */
53     static_assert(ZX_ERR_NOT_SUPPORTED < 0, "");
54     return ZX_ERR_NOT_SUPPORTED;
55 }
56 
instruction_step(enum entropy_instr instr,unsigned long long int * val)57 __attribute__((target("rdrnd,rdseed"))) static bool instruction_step(enum entropy_instr instr,
58                                                                      unsigned long long int* val) {
59     switch (instr) {
60     case ENTROPY_INSTR_RDRAND:
61         return _rdrand64_step(val);
62     case ENTROPY_INSTR_RDSEED:
63         return _rdseed64_step(val);
64     default:
65         panic("Invalid entropy instruction %d\n", (int)instr);
66     }
67 }
68 
get_entropy_from_instruction(void * buf,size_t len,bool block,enum entropy_instr instr)69 static ssize_t get_entropy_from_instruction(void* buf, size_t len, bool block,
70                                             enum entropy_instr instr) {
71 
72     size_t written = 0;
73     while (written < len) {
74         unsigned long long int val = 0;
75         if (!instruction_step(instr, &val)) {
76             if (!block) {
77                 break;
78             }
79             continue;
80         }
81         const size_t to_copy = fbl::min(len - written, sizeof(val));
82         memcpy(static_cast<uint8_t*>(buf) + written, &val, to_copy);
83         written += to_copy;
84     }
85     if (block) {
86         DEBUG_ASSERT(written == len);
87     }
88     return (ssize_t)written;
89 }
90 
get_entropy_from_rdseed(void * buf,size_t len,bool block)91 static ssize_t get_entropy_from_rdseed(void* buf, size_t len, bool block) {
92     return get_entropy_from_instruction(buf, len, block, ENTROPY_INSTR_RDSEED);
93 }
94 
get_entropy_from_rdrand(void * buf,size_t len,bool block)95 static ssize_t get_entropy_from_rdrand(void* buf, size_t len, bool block) {
96     // TODO(security, ZX-983): This method is not compliant with Intel's "Digital Random
97     // Number Generator (DRNG) Software Implementation Guide".  We are using
98     // rdrand in a way that is explicitly against their recommendations.  This
99     // needs to be corrected, but this fallback is a compromise to allow our
100     // development platforms that don't support RDSEED to get some degree of
101     // hardware-based randomization.
102     return get_entropy_from_instruction(buf, len, block, ENTROPY_INSTR_RDRAND);
103 }
104 
hw_rng_get_entropy(void * buf,size_t len,bool block)105 size_t hw_rng_get_entropy(void* buf, size_t len, bool block) {
106     if (!len) {
107         return 0;
108     }
109 
110     ssize_t res = get_entropy_from_cpu(buf, len, block);
111     if (res < 0) {
112         return 0;
113     }
114     return (size_t)res;
115 }
116