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 17enum entropy_instr { 18 ENTROPY_INSTR_RDSEED, 19 ENTROPY_INSTR_RDRAND, 20}; 21static ssize_t get_entropy_from_instruction(void* buf, size_t len, bool block, 22 enum entropy_instr instr); 23static ssize_t get_entropy_from_rdseed(void* buf, size_t len, bool block); 24static 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 */ 37static 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 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 69static 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 91static 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 95static 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 105size_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