blob: 2d93caf662076cdf2e020bf8b06feef6914a0a65 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <dev/hw_rng.h>
#include <arch/x86/feature.h>
#include <arch/x86/x86intrin.h>
#include <fbl/algorithm.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
enum entropy_instr {
ENTROPY_INSTR_RDSEED,
ENTROPY_INSTR_RDRAND,
};
static ssize_t get_entropy_from_instruction(void* buf, size_t len, bool block,
enum entropy_instr instr);
static ssize_t get_entropy_from_rdseed(void* buf, size_t len, bool block);
static ssize_t get_entropy_from_rdrand(void* buf, size_t len, bool block);
/* @brief Get entropy from the CPU using RDSEED.
*
* len must be at most SSIZE_MAX
*
* If |block|=true, it will retry the RDSEED instruction until |len| bytes are
* written to |buf|. Otherwise, it will fetch data from RDSEED until either
* |len| bytes are written to |buf| or RDSEED is unable to return entropy.
*
* Returns the number of bytes written to the buffer on success (potentially 0),
* and a negative value on error.
*/
static ssize_t get_entropy_from_cpu(void* buf, size_t len, bool block) {
/* TODO(security, ZX-984): Move this to a shared kernel/user lib, so we can write usermode
* tests against this code */
if (len >= SSIZE_MAX) {
static_assert(ZX_ERR_INVALID_ARGS < 0, "");
return ZX_ERR_INVALID_ARGS;
}
if (x86_feature_test(X86_FEATURE_RDSEED)) {
return get_entropy_from_rdseed(buf, len, block);
} else if (x86_feature_test(X86_FEATURE_RDRAND)) {
return get_entropy_from_rdrand(buf, len, block);
}
/* We don't have an entropy source */
static_assert(ZX_ERR_NOT_SUPPORTED < 0, "");
return ZX_ERR_NOT_SUPPORTED;
}
__attribute__((target("rdrnd,rdseed"))) static bool instruction_step(enum entropy_instr instr,
unsigned long long int* val) {
switch (instr) {
case ENTROPY_INSTR_RDRAND:
return _rdrand64_step(val);
case ENTROPY_INSTR_RDSEED:
return _rdseed64_step(val);
default:
panic("Invalid entropy instruction %d\n", (int)instr);
}
}
static ssize_t get_entropy_from_instruction(void* buf, size_t len, bool block,
enum entropy_instr instr) {
size_t written = 0;
while (written < len) {
unsigned long long int val = 0;
if (!instruction_step(instr, &val)) {
if (!block) {
break;
}
continue;
}
const size_t to_copy = fbl::min(len - written, sizeof(val));
memcpy(static_cast<uint8_t*>(buf) + written, &val, to_copy);
written += to_copy;
}
if (block) {
DEBUG_ASSERT(written == len);
}
return (ssize_t)written;
}
static ssize_t get_entropy_from_rdseed(void* buf, size_t len, bool block) {
return get_entropy_from_instruction(buf, len, block, ENTROPY_INSTR_RDSEED);
}
static ssize_t get_entropy_from_rdrand(void* buf, size_t len, bool block) {
// TODO(security, ZX-983): This method is not compliant with Intel's "Digital Random
// Number Generator (DRNG) Software Implementation Guide". We are using
// rdrand in a way that is explicitly against their recommendations. This
// needs to be corrected, but this fallback is a compromise to allow our
// development platforms that don't support RDSEED to get some degree of
// hardware-based randomization.
return get_entropy_from_instruction(buf, len, block, ENTROPY_INSTR_RDRAND);
}
size_t hw_rng_get_entropy(void* buf, size_t len, bool block) {
if (!len) {
return 0;
}
ssize_t res = get_entropy_from_cpu(buf, len, block);
if (res < 0) {
return 0;
}
return (size_t)res;
}