#include <mach/machine.h>
#include <mach/processor.h>
#include <kern/processor.h>
#include <kern/cpu_data.h>
#include <kern/cpu_number.h>
#include <kern/kalloc.h>
#include <kern/machine.h>
#include <kern/misc_protos.h>
#include <kern/startup.h>
#include <kern/sched.h>
#include <kern/thread.h>
#include <kern/thread_call.h>
#include <machine/cpu_data.h>
#include <machine/simple_lock.h>
#include <vm/pmap.h>
#include <vm/vm_page.h>
#include <sys/kdebug.h>
#include <sys/random.h>
#include <prng/random.h>
#include <corecrypto/ccdrbg.h>
#include <corecrypto/ccsha1.h>
#include <pexpert/pexpert.h>
#include <console/serial_protos.h>
#include <IOKit/IOPlatformExpert.h>
static lck_grp_t *gPRNGGrp;
static lck_attr_t *gPRNGAttr;
static lck_grp_attr_t *gPRNGGrpAttr;
static lck_mtx_t *gPRNGMutex = NULL;
typedef struct prngContext {
struct ccdrbg_info *infop;
struct ccdrbg_state *statep;
uint64_t bytes_generated;
uint64_t bytes_reseeded;
} *prngContextp;
ccdrbg_factory_t prng_ccdrbg_factory = NULL;
entropy_data_t EntropyData = { .index_ptr = EntropyData.buffer };
boolean_t erandom_seed_set = FALSE;
char erandom_seed[EARLY_RANDOM_SEED_SIZE];
typedef struct ccdrbg_state ccdrbg_state_t;
uint8_t master_erandom_state[EARLY_RANDOM_STATE_STATIC_SIZE];
ccdrbg_state_t *erandom_state[MAX_CPUS];
struct ccdrbg_info erandom_info;
decl_simple_lock_data(,entropy_lock);
struct ccdrbg_nisthmac_custom erandom_custom = {
.di = &ccsha1_eay_di,
.strictFIPS = 0,
};
static void read_erandom(void *buffer, u_int numBytes);
void
entropy_buffer_read(char *buffer,
unsigned int *count)
{
boolean_t current_state;
unsigned int i, j;
if (!erandom_seed_set) {
panic("early_random was never invoked");
}
if ((*count) > (ENTROPY_BUFFER_SIZE * sizeof(unsigned int)))
*count = ENTROPY_BUFFER_SIZE * sizeof(unsigned int);
current_state = ml_set_interrupts_enabled(FALSE);
#if defined (__x86_64__)
simple_lock(&entropy_lock);
#endif
memcpy((char *) buffer, (char *) EntropyData.buffer, *count);
for (i = 0, j = (ENTROPY_BUFFER_SIZE - 1); i < ENTROPY_BUFFER_SIZE; j = i, i++)
EntropyData.buffer[i] = EntropyData.buffer[i] ^ EntropyData.buffer[j];
#if defined (__x86_64__)
simple_unlock(&entropy_lock);
#endif
(void) ml_set_interrupts_enabled(current_state);
#if DEVELOPMENT || DEBUG
uint32_t *word = (uint32_t *) (void *) buffer;
for (i = 0; i < ENTROPY_BUFFER_SIZE; i += 4)
KERNEL_DEBUG_EARLY(ENTROPY_READ(i/4),
word[i+0], word[i+1], word[i+2], word[i+3]);
#endif
}
uint64_t
early_random(void)
{
uint32_t cnt = 0;
uint64_t result;
uint64_t nonce;
int rc;
int ps;
ccdrbg_state_t *state;
if (!erandom_seed_set) {
simple_lock_init(&entropy_lock,0);
erandom_seed_set = TRUE;
cnt = PE_get_random_seed((unsigned char *) EntropyData.buffer,
sizeof(EntropyData.buffer));
if (cnt < sizeof(EntropyData.buffer)) {
panic("EntropyData needed %lu bytes, but got %u.\n",
sizeof(EntropyData.buffer), cnt);
}
bcopy(EntropyData.buffer, &erandom_seed, sizeof(erandom_seed));
ccdrbg_factory_nisthmac(&erandom_info, &erandom_custom);
assert(erandom_info.size <= sizeof(master_erandom_state));
state = (ccdrbg_state_t *) master_erandom_state;
erandom_state[0] = state;
assert(sizeof(erandom_seed) > sizeof(nonce));
nonce = ml_get_timebase();
ps = 0;
rc = ccdrbg_init(&erandom_info, state,
sizeof(erandom_seed), erandom_seed,
sizeof(nonce), &nonce,
sizeof(ps), &ps);
cc_clear(sizeof(nonce), &nonce);
if (rc != CCDRBG_STATUS_OK)
panic("ccdrbg_init() returned %d", rc);
rc = ccdrbg_generate(&erandom_info, state,
sizeof(result), &result,
0, NULL);
if (rc != CCDRBG_STATUS_OK)
panic("ccdrbg_generate() returned %d", rc);
return result;
};
read_erandom(&result, sizeof(result));
return result;
}
static void
read_erandom(void *buffer, u_int numBytes)
{
int cpu;
int rc;
uint32_t cnt;
ccdrbg_state_t *state;
mp_disable_preemption();
cpu = cpu_number();
state = erandom_state[cpu];
assert(state);
while (TRUE) {
rc = ccdrbg_generate(&erandom_info, state,
numBytes, buffer,
0, NULL);
if (rc == CCDRBG_STATUS_OK)
break;
if (rc == CCDRBG_STATUS_NEED_RESEED) {
cnt = sizeof(erandom_seed);
entropy_buffer_read(erandom_seed, &cnt);
assert(cnt == sizeof(erandom_seed));
rc = ccdrbg_reseed(&erandom_info, state,
sizeof(erandom_seed), erandom_seed,
0, NULL);
cc_clear(sizeof(erandom_seed), erandom_seed);
if (rc == CCDRBG_STATUS_OK)
continue;
panic("read_erandom reseed error %d\n", rc);
}
panic("read_erandom ccdrbg error %d\n", rc);
}
mp_enable_preemption();
}
void
read_frandom(void *buffer, u_int numBytes)
{
char *cp = (char *) buffer;
int nbytes;
while (numBytes) {
nbytes = MIN(numBytes, PAGE_SIZE);
read_erandom(cp, nbytes);
cp += nbytes;
numBytes -= nbytes;
}
}
void
prng_factory_register(ccdrbg_factory_t factory)
{
prng_ccdrbg_factory = factory;
thread_wakeup((event_t) &prng_ccdrbg_factory);
}
void
prng_cpu_init(int cpu)
{
uint64_t nonce;
int rc;
ccdrbg_state_t *state;
prngContextp pp;
if (erandom_state[cpu] == NULL) {
state = kalloc(erandom_info.size);
if (state == NULL) {
panic("prng_init kalloc failed\n");
}
erandom_state[cpu] = state;
nonce = ml_get_timebase();
rc = ccdrbg_init(&erandom_info, state,
sizeof(erandom_seed), erandom_seed,
sizeof(nonce), &nonce,
sizeof(cpu), &cpu);
cc_clear(sizeof(nonce), &nonce);
if (rc != CCDRBG_STATUS_OK)
panic("ccdrbg_init() returned %d", rc);
}
if (cpu != master_cpu) {
cpu_datap(cpu)->cpu_prng = master_prng_context();
return;
}
assert(gPRNGMutex == NULL);
gPRNGGrpAttr = lck_grp_attr_alloc_init();
gPRNGGrp = lck_grp_alloc_init("random", gPRNGGrpAttr);
gPRNGAttr = lck_attr_alloc_init();
gPRNGMutex = lck_mtx_alloc_init(gPRNGGrp, gPRNGAttr);
pp = kalloc(sizeof(*pp));
if (pp == NULL)
panic("Unable to allocate prng context");
pp->bytes_generated = 0;
pp->bytes_reseeded = 0;
pp->infop = NULL;
prng_factory_register(ccdrbg_factory_yarrow);
master_prng_context() = pp;
}
static ccdrbg_info_t *
prng_infop(prngContextp pp)
{
lck_mtx_assert(gPRNGMutex, LCK_MTX_ASSERT_OWNED);
if (pp->infop)
return pp->infop;
while (prng_ccdrbg_factory == NULL ) {
wait_result_t wait_result;
assert_wait_timeout((event_t) &prng_ccdrbg_factory, TRUE,
10, NSEC_PER_USEC);
lck_mtx_unlock(gPRNGMutex);
wait_result = thread_block(THREAD_CONTINUE_NULL);
if (wait_result == THREAD_TIMED_OUT)
panic("prng_ccdrbg_factory registration timeout");
lck_mtx_lock(gPRNGMutex);
}
if (pp->infop)
return pp->infop;
pp->infop = (ccdrbg_info_t *) kalloc(sizeof(ccdrbg_info_t));
if (pp->infop == NULL)
panic("Unable to allocate prng info");
prng_ccdrbg_factory(pp->infop, NULL);
pp->statep = kalloc(pp->infop->size);
if (pp->statep == NULL)
panic("Unable to allocate prng state");
char rdBuffer[ENTROPY_BUFFER_BYTE_SIZE];
unsigned int bytesToInput = sizeof(rdBuffer);
entropy_buffer_read(rdBuffer, &bytesToInput);
(void) ccdrbg_init(pp->infop, pp->statep,
bytesToInput, rdBuffer,
0, NULL,
0, NULL);
cc_clear(sizeof(rdBuffer), rdBuffer);
return pp->infop;
}
static void
Reseed(prngContextp pp)
{
char rdBuffer[ENTROPY_BUFFER_BYTE_SIZE];
unsigned int bytesToInput = sizeof(rdBuffer);
entropy_buffer_read(rdBuffer, &bytesToInput);
PRNG_CCDRBG((void) ccdrbg_reseed(pp->infop, pp->statep,
bytesToInput, rdBuffer,
0, NULL));
cc_clear(sizeof(rdBuffer), rdBuffer);
pp->bytes_reseeded = pp->bytes_generated;
}
void
read_random(void* buffer, u_int numbytes)
{
prngContextp pp;
ccdrbg_info_t *infop;
int ccdrbg_err;
lck_mtx_lock(gPRNGMutex);
pp = current_prng_context();
infop = prng_infop(pp);
while (TRUE) {
PRNG_CCDRBG(
ccdrbg_err = ccdrbg_generate(infop, pp->statep,
numbytes, buffer,
0, NULL));
if (ccdrbg_err == CCDRBG_STATUS_OK)
break;
if (ccdrbg_err == CCDRBG_STATUS_NEED_RESEED) {
Reseed(pp);
continue;
}
panic("read_random ccdrbg error %d\n", ccdrbg_err);
}
pp->bytes_generated += numbytes;
lck_mtx_unlock(gPRNGMutex);
}
int
write_random(void* buffer, u_int numbytes)
{
#if 0
int retval = 0;
prngContextp pp;
lck_mtx_lock(gPRNGMutex);
pp = current_prng_context();
if (ccdrbg_reseed(prng_infop(pp), pp->statep,
bytesToInput, rdBuffer, 0, NULL) != 0)
retval = EIO;
lck_mtx_unlock(gPRNGMutex);
return retval;
#else
#pragma unused(buffer, numbytes)
return 0;
#endif
}