123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- /* ----------------------------------------------------------------------------
- Copyright (c) 2019-2021, Microsoft Research, Daan Leijen
- This is free software; you can redistribute it and/or modify it under the
- terms of the MIT license. A copy of the license can be found in the file
- "LICENSE" at the root of this distribution.
- -----------------------------------------------------------------------------*/
- #include "mimalloc.h"
- #include "mimalloc-internal.h"
- #include <string.h> // memset
- /* ----------------------------------------------------------------------------
- We use our own PRNG to keep predictable performance of random number generation
- and to avoid implementations that use a lock. We only use the OS provided
- random source to initialize the initial seeds. Since we do not need ultimate
- performance but we do rely on the security (for secret cookies in secure mode)
- we use a cryptographically secure generator (chacha20).
- -----------------------------------------------------------------------------*/
- #define MI_CHACHA_ROUNDS (20) // perhaps use 12 for better performance?
- /* ----------------------------------------------------------------------------
- Chacha20 implementation as the original algorithm with a 64-bit nonce
- and counter: https://en.wikipedia.org/wiki/Salsa20
- The input matrix has sixteen 32-bit values:
- Position 0 to 3: constant key
- Position 4 to 11: the key
- Position 12 to 13: the counter.
- Position 14 to 15: the nonce.
- The implementation uses regular C code which compiles very well on modern compilers.
- (gcc x64 has no register spills, and clang 6+ uses SSE instructions)
- -----------------------------------------------------------------------------*/
- static inline uint32_t rotl(uint32_t x, uint32_t shift) {
- return (x << shift) | (x >> (32 - shift));
- }
- static inline void qround(uint32_t x[16], size_t a, size_t b, size_t c, size_t d) {
- x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 16);
- x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 12);
- x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 8);
- x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 7);
- }
- static void chacha_block(mi_random_ctx_t* ctx)
- {
- // scramble into `x`
- uint32_t x[16];
- for (size_t i = 0; i < 16; i++) {
- x[i] = ctx->input[i];
- }
- for (size_t i = 0; i < MI_CHACHA_ROUNDS; i += 2) {
- qround(x, 0, 4, 8, 12);
- qround(x, 1, 5, 9, 13);
- qround(x, 2, 6, 10, 14);
- qround(x, 3, 7, 11, 15);
- qround(x, 0, 5, 10, 15);
- qround(x, 1, 6, 11, 12);
- qround(x, 2, 7, 8, 13);
- qround(x, 3, 4, 9, 14);
- }
- // add scrambled data to the initial state
- for (size_t i = 0; i < 16; i++) {
- ctx->output[i] = x[i] + ctx->input[i];
- }
- ctx->output_available = 16;
- // increment the counter for the next round
- ctx->input[12] += 1;
- if (ctx->input[12] == 0) {
- ctx->input[13] += 1;
- if (ctx->input[13] == 0) { // and keep increasing into the nonce
- ctx->input[14] += 1;
- }
- }
- }
- static uint32_t chacha_next32(mi_random_ctx_t* ctx) {
- if (ctx->output_available <= 0) {
- chacha_block(ctx);
- ctx->output_available = 16; // (assign again to suppress static analysis warning)
- }
- const uint32_t x = ctx->output[16 - ctx->output_available];
- ctx->output[16 - ctx->output_available] = 0; // reset once the data is handed out
- ctx->output_available--;
- return x;
- }
- static inline uint32_t read32(const uint8_t* p, size_t idx32) {
- const size_t i = 4*idx32;
- return ((uint32_t)p[i+0] | (uint32_t)p[i+1] << 8 | (uint32_t)p[i+2] << 16 | (uint32_t)p[i+3] << 24);
- }
- static void chacha_init(mi_random_ctx_t* ctx, const uint8_t key[32], uint64_t nonce)
- {
- // since we only use chacha for randomness (and not encryption) we
- // do not _need_ to read 32-bit values as little endian but we do anyways
- // just for being compatible :-)
- memset(ctx, 0, sizeof(*ctx));
- for (size_t i = 0; i < 4; i++) {
- const uint8_t* sigma = (uint8_t*)"expand 32-byte k";
- ctx->input[i] = read32(sigma,i);
- }
- for (size_t i = 0; i < 8; i++) {
- ctx->input[i + 4] = read32(key,i);
- }
- ctx->input[12] = 0;
- ctx->input[13] = 0;
- ctx->input[14] = (uint32_t)nonce;
- ctx->input[15] = (uint32_t)(nonce >> 32);
- }
- static void chacha_split(mi_random_ctx_t* ctx, uint64_t nonce, mi_random_ctx_t* ctx_new) {
- memset(ctx_new, 0, sizeof(*ctx_new));
- _mi_memcpy(ctx_new->input, ctx->input, sizeof(ctx_new->input));
- ctx_new->input[12] = 0;
- ctx_new->input[13] = 0;
- ctx_new->input[14] = (uint32_t)nonce;
- ctx_new->input[15] = (uint32_t)(nonce >> 32);
- mi_assert_internal(ctx->input[14] != ctx_new->input[14] || ctx->input[15] != ctx_new->input[15]); // do not reuse nonces!
- chacha_block(ctx_new);
- }
- /* ----------------------------------------------------------------------------
- Random interface
- -----------------------------------------------------------------------------*/
- #if MI_DEBUG>1
- static bool mi_random_is_initialized(mi_random_ctx_t* ctx) {
- return (ctx != NULL && ctx->input[0] != 0);
- }
- #endif
- void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* ctx_new) {
- mi_assert_internal(mi_random_is_initialized(ctx));
- mi_assert_internal(ctx != ctx_new);
- chacha_split(ctx, (uintptr_t)ctx_new /*nonce*/, ctx_new);
- }
- uintptr_t _mi_random_next(mi_random_ctx_t* ctx) {
- mi_assert_internal(mi_random_is_initialized(ctx));
- #if MI_INTPTR_SIZE <= 4
- return chacha_next32(ctx);
- #elif MI_INTPTR_SIZE == 8
- return (((uintptr_t)chacha_next32(ctx) << 32) | chacha_next32(ctx));
- #else
- # error "define mi_random_next for this platform"
- #endif
- }
- /* ----------------------------------------------------------------------------
- To initialize a fresh random context we rely on the OS:
- - Windows : BCryptGenRandom (or RtlGenRandom)
- - osX,bsd,wasi: arc4random_buf
- - Linux : getrandom,/dev/urandom
- If we cannot get good randomness, we fall back to weak randomness based on a timer and ASLR.
- -----------------------------------------------------------------------------*/
- #if defined(_WIN32)
- #if !defined(MI_USE_RTLGENRANDOM)
- // We prefer BCryptGenRandom over RtlGenRandom
- #pragma comment (lib,"bcrypt.lib")
- #include <bcrypt.h>
- static bool os_random_buf(void* buf, size_t buf_len) {
- return (BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)buf_len, BCRYPT_USE_SYSTEM_PREFERRED_RNG) >= 0);
- }
- #else
- // Use (unofficial) RtlGenRandom
- #pragma comment (lib,"advapi32.lib")
- #define RtlGenRandom SystemFunction036
- #ifdef __cplusplus
- extern "C" {
- #endif
- BOOLEAN NTAPI RtlGenRandom(PVOID RandomBuffer, ULONG RandomBufferLength);
- #ifdef __cplusplus
- }
- #endif
- static bool os_random_buf(void* buf, size_t buf_len) {
- return (RtlGenRandom(buf, (ULONG)buf_len) != 0);
- }
- #endif
- #elif defined(ANDROID) || defined(XP_DARWIN) || defined(__APPLE__) || defined(__DragonFly__) || \
- defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
- defined(__sun) || defined(__wasi__)
- #include <stdlib.h>
- static bool os_random_buf(void* buf, size_t buf_len) {
- arc4random_buf(buf, buf_len);
- return true;
- }
- #elif defined(__linux__)
- #include <sys/syscall.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <errno.h>
- static bool os_random_buf(void* buf, size_t buf_len) {
- // Modern Linux provides `getrandom` but different distributions either use `sys/random.h` or `linux/random.h`
- // and for the latter the actual `getrandom` call is not always defined.
- // (see <https://stackoverflow.com/questions/45237324/why-doesnt-getrandom-compile>)
- // We therefore use a syscall directly and fall back dynamically to /dev/urandom when needed.
- #ifdef SYS_getrandom
- #ifndef GRND_NONBLOCK
- #define GRND_NONBLOCK (1)
- #endif
- static _Atomic(uintptr_t) no_getrandom; // = 0
- if (mi_atomic_load_acquire(&no_getrandom)==0) {
- ssize_t ret = syscall(SYS_getrandom, buf, buf_len, GRND_NONBLOCK);
- if (ret >= 0) return (buf_len == (size_t)ret);
- if (ret != ENOSYS) return false;
- mi_atomic_store_release(&no_getrandom, 1UL); // don't call again, and fall back to /dev/urandom
- }
- #endif
- int flags = O_RDONLY;
- #if defined(O_CLOEXEC)
- flags |= O_CLOEXEC;
- #endif
- int fd = open("/dev/urandom", flags, 0);
- if (fd < 0) return false;
- size_t count = 0;
- while(count < buf_len) {
- ssize_t ret = read(fd, (char*)buf + count, buf_len - count);
- if (ret<=0) {
- if (errno!=EAGAIN && errno!=EINTR) break;
- }
- else {
- count += ret;
- }
- }
- close(fd);
- return (count==buf_len);
- }
- #else
- static bool os_random_buf(void* buf, size_t buf_len) {
- return false;
- }
- #endif
- #if defined(_WIN32)
- #include <windows.h>
- #elif defined(__APPLE__)
- #include <mach/mach_time.h>
- #else
- #include <time.h>
- #endif
- uintptr_t _os_random_weak(uintptr_t extra_seed) {
- uintptr_t x = (uintptr_t)&_os_random_weak ^ extra_seed; // ASLR makes the address random
-
- #if defined(_WIN32)
- LARGE_INTEGER pcount;
- QueryPerformanceCounter(&pcount);
- x ^= (uintptr_t)(pcount.QuadPart);
- #elif defined(__APPLE__)
- x ^= (uintptr_t)mach_absolute_time();
- #else
- struct timespec time;
- clock_gettime(CLOCK_MONOTONIC, &time);
- x ^= (uintptr_t)time.tv_sec;
- x ^= (uintptr_t)time.tv_nsec;
- #endif
- // and do a few randomization steps
- uintptr_t max = ((x ^ (x >> 17)) & 0x0F) + 1;
- for (uintptr_t i = 0; i < max; i++) {
- x = _mi_random_shuffle(x);
- }
- mi_assert_internal(x != 0);
- return x;
- }
- void _mi_random_init(mi_random_ctx_t* ctx) {
- uint8_t key[32];
- if (!os_random_buf(key, sizeof(key))) {
- // if we fail to get random data from the OS, we fall back to a
- // weak random source based on the current time
- _mi_warning_message("unable to use secure randomness\n");
- uintptr_t x = _os_random_weak(0);
- for (size_t i = 0; i < 8; i++) { // key is eight 32-bit words.
- x = _mi_random_shuffle(x);
- ((uint32_t*)key)[i] = (uint32_t)x;
- }
- }
- chacha_init(ctx, key, (uintptr_t)ctx /*nonce*/ );
- }
- /* --------------------------------------------------------
- test vectors from <https://tools.ietf.org/html/rfc8439>
- ----------------------------------------------------------- */
- /*
- static bool array_equals(uint32_t* x, uint32_t* y, size_t n) {
- for (size_t i = 0; i < n; i++) {
- if (x[i] != y[i]) return false;
- }
- return true;
- }
- static void chacha_test(void)
- {
- uint32_t x[4] = { 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567 };
- uint32_t x_out[4] = { 0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb };
- qround(x, 0, 1, 2, 3);
- mi_assert_internal(array_equals(x, x_out, 4));
- uint32_t y[16] = {
- 0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a,
- 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0x2a5f714c,
- 0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963,
- 0x5c971061, 0x3d631689, 0x2098d9d6, 0x91dbd320 };
- uint32_t y_out[16] = {
- 0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a,
- 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0xcfacafd2,
- 0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963,
- 0x5c971061, 0xccc07c79, 0x2098d9d6, 0x91dbd320 };
- qround(y, 2, 7, 8, 13);
- mi_assert_internal(array_equals(y, y_out, 16));
- mi_random_ctx_t r = {
- { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
- 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c,
- 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c,
- 0x00000001, 0x09000000, 0x4a000000, 0x00000000 },
- {0},
- 0
- };
- uint32_t r_out[16] = {
- 0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3,
- 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3,
- 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9,
- 0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2 };
- chacha_block(&r);
- mi_assert_internal(array_equals(r.output, r_out, 16));
- }
- */
|