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