]> git.saurik.com Git - apple/xnu.git/blobdiff - osfmk/prng/random.c
xnu-2782.1.97.tar.gz
[apple/xnu.git] / osfmk / prng / random.c
diff --git a/osfmk/prng/random.c b/osfmk/prng/random.c
new file mode 100644 (file)
index 0000000..44045cc
--- /dev/null
@@ -0,0 +1,481 @@
+/*
+ * Copyright (c) 2013 Apple Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+
+#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);        /* Forward */
+
+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;
+       /* Good for both 32-bit and 64-bit kernels. */
+       for (i = 0; i < ENTROPY_BUFFER_SIZE; i += 4)
+               /* 
+                * We use "EARLY" here so that we can grab early entropy on
+                * ARM, where tracing is not started until after PRNG is
+                * initialized.
+               */
+               KERNEL_DEBUG_EARLY(ENTROPY_READ(i/4),
+                       word[i+0], word[i+1], word[i+2], word[i+3]);
+#endif
+}
+
+/*
+ * Return a uniformly distributed 64-bit random number.
+ *
+ * This interface should have minimal dependencies on kernel
+ * services, and thus be available very early in the life
+ * of the kernel.
+ * This provides cryptographically secure randomness.
+ * Each processor has its own generator instance.
+ * It is seeded (lazily) with entropy provided by the Booter.
+*
+ * For <rdar://problem/17292592> the algorithm switched from LCG to
+ * NIST HMAC DBRG as follows:
+ *  - When first called (on OSX this is very early while page tables are being
+ *    built) early_random() calls ccdrbg_factory_hmac() to set-up a ccdbrg info
+ *    structure.
+ *  - The boot processor's ccdrbg state structure is a statically allocated area
+ *    which is then initialized by calling the ccdbrg_init method.
+ *    The initial entropy is 16 bytes of boot entropy.
+ *    The nonce is the first 8 bytes of entropy xor'ed with a timestamp
+ *    from ml_get_timebase().
+ *    The personalization data provided is null. 
+ *  - The first 64-bit random value is returned on the boot processor from
+ *    an invocation of the ccdbrg_generate method.
+ *  - Non-boot processor's DRBG state structures are allocated dynamically
+ *    from prng_init(). Each is initialized with the same 16 bytes of entropy
+ *    but with a different timestamped nonce and cpu number as personalization.
+ *  - Subsequent calls to early_random() pass to read_erandom() to generate
+ *    an 8-byte random value.  read_erandom() ensures that pre-emption is
+ *    disabled and selects the DBRG state from the current processor.
+ *    The ccdbrg_generate method is called for the required random output.
+ *    If this method returns CCDRBG_STATUS_NEED_RESEED, the erandom_seed buffer
+ *    is re-filled with kernel-harvested entropy and the ccdbrg_reseed method is
+ *    called with this new entropy. The kernel panics if a reseed fails.
+ */
+uint64_t
+early_random(void)
+{
+       uint32_t        cnt = 0;
+       uint64_t        result;
+       uint64_t        nonce;
+       int             rc;
+       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)) {
+                       /*
+                        * Insufficient entropy is fatal.  We must fill the
+                        * entire entropy buffer during initializaton.
+                        */
+                       panic("EntropyData needed %lu bytes, but got %u.\n",
+                               sizeof(EntropyData.buffer), cnt);
+               }               
+
+               /*
+                * Use some of the supplied entropy as a basis for early_random;
+                * reuse is ugly, but simplifies things. Ideally, we would guard
+                * early random values well enough that it isn't safe to attack
+                * them, but this cannot be guaranteed; thus, initial entropy
+                * can be considered 8 bytes weaker for a given boot if any
+                * early random values are conclusively determined.
+                *
+                * early_random_seed could be larger than EntopyData.buffer...
+                * but it won't be.
+                */
+               bcopy(EntropyData.buffer, &erandom_seed, sizeof(erandom_seed));
+
+               /* Init DRBG for NIST HMAC */
+               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;
+
+               /*
+                * Init our DBRG from the boot entropy and a nonce composed of
+                * a timestamp swizzled with the first 8 bytes of this entropy.
+                */
+               assert(sizeof(erandom_seed) > sizeof(nonce));
+               bcopy(erandom_seed, &nonce, sizeof(nonce));
+               nonce ^= ml_get_timebase();
+               rc = ccdrbg_init(&erandom_info, state,
+                                sizeof(erandom_seed), erandom_seed,
+                                sizeof(nonce), &nonce,
+                                0, NULL);
+               assert(rc == CCDRBG_STATUS_OK);
+
+               /* Generate output */
+               rc = ccdrbg_generate(&erandom_info, state,
+                                    sizeof(result), &result,
+                                    0, NULL);
+               assert(rc == CCDRBG_STATUS_OK);
+       
+               return result;
+       };
+
+       read_erandom(&result, sizeof(result));
+
+       return result;
+}
+
+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) {
+               /* Generate output */
+               rc = ccdrbg_generate(&erandom_info, state,
+                                    numBytes, buffer,
+                                    0, NULL);
+               if (rc == CCDRBG_STATUS_OK)
+                       break;
+               if (rc == CCDRBG_STATUS_NEED_RESEED) {
+                       /* It's time to reseed. Get more entropy */
+                       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);
+                       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;
+
+       /*
+        * Split up into requests for blocks smaller than
+        * than the DBRG request limit. iThis limit is private but
+        * for NISTHMAC it's known to be greater then 4096.
+        */
+       while (numBytes) {
+               nbytes = MIN(numBytes, PAGE_SIZE);
+               read_erandom(cp, nbytes);
+               cp += nbytes;
+               numBytes -= nbytes;
+       }
+}
+
+/*
+ * Register a DRBG factory routine to e used in constructing the kernel PRNG.
+ * XXX to be called from the corecrypto kext.
+ */
+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;
+
+       /*
+        * Allocate state and initialize DBRG state for early_random()
+        * for this processor, if necessary.
+        */
+       if (erandom_state[cpu] == NULL) {
+               
+               state = kalloc(erandom_info.size);
+               if (state == NULL) {
+                       panic("prng_init kalloc failed\n");
+               }
+               erandom_state[cpu] = state;
+
+               /*
+                * Init our DBRG from boot entropy, nonce as timestamp xor'ed
+                * with the first 8 bytes of entropy, and use the cpu number
+                * as the personalization parameter.
+                */
+               bcopy(erandom_seed, &nonce, sizeof(nonce));
+               nonce ^= ml_get_timebase();
+               rc = ccdrbg_init(&erandom_info, state,
+                                sizeof(erandom_seed), erandom_seed,
+                                sizeof(nonce), &nonce,
+                                sizeof(cpu), &cpu);
+               assert(rc == CCDRBG_STATUS_OK);
+       }
+
+       /* Non-boot cpus use the master cpu's global context */
+       if (cpu != master_cpu) {
+               cpu_datap(cpu)->cpu_prng = master_prng_context();
+               return;
+       }
+
+       assert(gPRNGMutex == NULL);             /* Once only, please */
+
+       /* make a mutex to control access */
+       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;
+
+       /* XXX Temporary registration */
+       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);
+
+       /* Usual case: the info is all set */
+       if (pp->infop)
+               return pp->infop;
+
+       /*
+        * Possibly wait for the CCDRBG factory routune to be registered
+        * by corecypto. But panic after waiting for more than 10 seconds.
+        */
+       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);
+       }
+       /* Check we didn't lose the set-up race */
+       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);
+       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)); 
+
+       pp->bytes_reseeded = pp->bytes_generated;
+}
+
+
+/* export good random numbers to the rest of the kernel */
+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);
+
+       /*
+        * Call DRBG, reseeding and retrying if requested.
+        */
+       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
+}