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