/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2017 Red Hat, Inc. */ #include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-random-utils.h" #include #include #include #include #if USE_SYS_RANDOM_H #include #else #include #endif #include "nm-shared-utils.h" #include "nm-time-utils.h" /*****************************************************************************/ #if !defined(SYS_getrandom) && defined(__NR_getrandom) #define SYS_getrandom __NR_getrandom #endif #ifndef GRND_NONBLOCK #define GRND_NONBLOCK 0x01 #endif #ifndef GRND_INSECURE #define GRND_INSECURE 0x04 #endif #if !HAVE_GETRANDOM static ssize_t getrandom(void *buf, size_t buflen, unsigned flags) { #if defined(SYS_getrandom) return syscall(SYS_getrandom, buf, buflen, flags); #else errno = ENOSYS; return -1; #endif } #endif /*****************************************************************************/ static ssize_t _getrandom(void *buf, size_t buflen, unsigned flags) { static int have_getrandom = TRUE; ssize_t l; int errsv; nm_assert(buflen > 0); /* This calls getrandom() and either returns the positive * success or an negative errno. ENOSYS means getrandom() * call is not supported. That result is cached and we don't retry. */ if (!have_getrandom) return -ENOSYS; l = getrandom(buf, buflen, flags); if (l > 0) return l; if (l == 0) return -EIO; errsv = errno; if (errsv == ENOSYS) have_getrandom = FALSE; return -errsv; } static ssize_t _getrandom_insecure(void *buf, size_t buflen) { static int have_grnd_insecure = TRUE; ssize_t l; /* GRND_INSECURE was added recently. We catch EINVAL * if kernel does not support the flag (and cache it). */ if (!have_grnd_insecure) return -EINVAL; l = _getrandom(buf, buflen, GRND_INSECURE); if (l == -EINVAL) have_grnd_insecure = FALSE; return l; } static ssize_t _getrandom_best_effort(void *buf, size_t buflen) { ssize_t l; /* To get best-effort bytes, we would use GRND_INSECURE (and we try that * first). However, not all kernel versions support that, so we fallback * to GRND_NONBLOCK. * * Granted, this is called from a fallback path where we have no entropy * already, it's unlikely that GRND_NONBLOCK would succeed. Still... */ l = _getrandom_insecure(buf, buflen); if (l != -EINVAL) return l; return _getrandom(buf, buflen, GRND_NONBLOCK); } static int _random_check_entropy(gboolean block) { static gboolean seen_high_quality = FALSE; nm_auto_close int fd = -1; int r; /* We come here because getrandom() gave ENOSYS. We will fallback to /dev/urandom, * but the caller wants to know whether we have high quality numbers. Poll * /dev/random to find out. */ if (seen_high_quality) { /* We cache the positive result. Once kernel has entropy, we will get * good random numbers. */ return 1; } fd = open("/dev/random", O_RDONLY | O_CLOEXEC | O_NOCTTY); if (fd < 0) return -errno; r = nm_utils_fd_wait_for_event(fd, POLLIN, block ? -1 : 0); if (r <= 0) { nm_assert(r < 0 || !block); return r; } nm_assert(r == 1); seen_high_quality = TRUE; return 1; } /*****************************************************************************/ typedef struct _nm_packed { uintptr_t heap_ptr; uintptr_t stack_ptr; gint64 now_bootime; gint64 now_real; pid_t pid; pid_t ppid; pid_t tid; guint32 grand[16]; guint8 auxval[16]; guint8 getrandom_buf[20]; } BadRandSeed; typedef struct _nm_packed { guint64 counter; union { guint8 full[NM_UTILS_CHECKSUM_LENGTH_SHA256]; struct { guint8 half_1[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2]; guint8 half_2[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2]; }; } sha_digest; union { guint8 u8[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2]; guint32 u32[((NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2) + 3) / 4]; } rand_vals; guint8 rand_vals_getrandom[16]; gint64 rand_vals_timestamp; } BadRandState; static void _bad_random_init_seed(BadRandSeed *seed) { const guint8 *p_at_random; int seed_idx; GRand *rand; /* g_rand_new() reads /dev/urandom too, but we already know that * /dev/urandom fails to give us good randomness (which is why * we hit the "bad random" code path). So this may not be as * good as we wish, but let's hope that it it does something smart * to give some extra entropy... */ rand = g_rand_new(); /* Get some seed material from a GRand. */ for (seed_idx = 0; seed_idx < (int) G_N_ELEMENTS(seed->grand); seed_idx++) seed->grand[seed_idx] = g_rand_int(rand); /* Add an address from the heap and stack, maybe ASLR helps a bit? */ seed->heap_ptr = (uintptr_t) ((gpointer) rand); seed->stack_ptr = (uintptr_t) ((gpointer) &rand); g_rand_free(rand); /* Add the per-process, random number. */ p_at_random = ((gpointer) getauxval(AT_RANDOM)); if (p_at_random) { G_STATIC_ASSERT(sizeof(seed->auxval) == 16); memcpy(&seed->auxval, p_at_random, 16); } _getrandom_best_effort(seed->getrandom_buf, sizeof(seed->getrandom_buf)); seed->now_bootime = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME); seed->now_real = g_get_real_time(); seed->pid = getpid(); seed->ppid = getppid(); seed->tid = nm_utils_gettid(); } static void _bad_random_bytes(guint8 *buf, gsize n) { nm_auto_free_checksum GChecksum *sum = g_checksum_new(G_CHECKSUM_SHA256); nm_assert(n > 0); /* We are in the fallback code path, where getrandom() (and /dev/urandom) failed * to give us good randomness. Try our best. * * Our ability to get entropy for the CPRNG is very limited and thus the overall * result will be bad randomness. * * Once we have some seed material, we combine GRand (which is not a cryptographically * secure PRNG) with some iterative sha256 hashing. It would be nice if we had * easy access to chacha20, but it's probably more cumbersome to fork those * implementations than hack a bad CPRNG by using sha256 hashing. After all, this * is fallback code to get *some* bad randomness. And with the inability to get a good * seed, any CPRNG can only give us bad randomness. */ { static BadRandState gl_state; static GRand *gl_rand; static GMutex gl_mutex; NM_G_MUTEX_LOCKED(&gl_mutex); if (G_UNLIKELY(!gl_rand)) { union { BadRandSeed d_seed; guint32 d_u32[(sizeof(BadRandSeed) + 3) / 4]; } data = { .d_u32 = {0}, }; _bad_random_init_seed(&data.d_seed); gl_rand = g_rand_new_with_seed_array(data.d_u32, G_N_ELEMENTS(data.d_u32)); g_checksum_update(sum, (const guchar *) &data, sizeof(data)); nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full); } _getrandom_best_effort(gl_state.rand_vals_getrandom, sizeof(gl_state.rand_vals_getrandom)); gl_state.rand_vals_timestamp = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME); while (TRUE) { int i; gl_state.counter++; for (i = 0; i < G_N_ELEMENTS(gl_state.rand_vals.u32); i++) gl_state.rand_vals.u32[i] = g_rand_int(gl_rand); g_checksum_reset(sum); g_checksum_update(sum, (const guchar *) &gl_state, sizeof(gl_state)); nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full); /* gl_state.sha_digest.full and gl_state.rand_vals contain now our * bad random values, but they are also the state for the next iteration. * We must not directly expose that state to the caller, so XOR the values. * * That means, per iteration we can generate 16 bytes of bad randomness. That * is suitable to initialize a random UUID. */ for (i = 0; i < (int) (NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2); i++) { nm_assert(n > 0); buf[0] = gl_state.sha_digest.half_1[i] ^ gl_state.sha_digest.half_2[i] ^ gl_state.rand_vals.u8[i]; buf++; n--; if (n == 0) return; } } } } /*****************************************************************************/ /** * nm_random_get_bytes_full: * @p: the buffer to fill * @n: the number of bytes to write to @p. * @out_high_quality: (out) (optional): whether the returned * random bytes are of high quality. * * - will never block * - will always produce some numbers, but they may not * be of high quality. * - Whether they are of high quality, you can know via @out_high_quality. * - will always try hard to produce high quality numbers, and on success * they are as good as nm_random_get_crypto_bytes(). */ void nm_random_get_bytes_full(void *p, size_t n, gboolean *out_high_quality) { int fd; int r; gboolean has_high_quality; ssize_t l; if (n == 0) { NM_SET_OUT(out_high_quality, TRUE); return; } g_return_if_fail(p); again_getrandom: l = _getrandom(p, n, GRND_NONBLOCK); if (l > 0) { if ((size_t) l == n) { NM_SET_OUT(out_high_quality, TRUE); return; } p = ((uint8_t *) p) + l; n -= l; goto again_getrandom; } /* getrandom() failed. Fallback to read /dev/urandom. */ if (l == -ENOSYS) { /* no support for getrandom(). */ if (out_high_quality) { /* The caller wants to know whether we have high quality. Poll /dev/random * to find out. */ has_high_quality = (_random_check_entropy(FALSE) > 0); } else { /* The value doesn't matter in this case. It will be unused. */ has_high_quality = FALSE; } } else { /* Any other failure of getrandom() means we don't have high quality. */ has_high_quality = FALSE; if (l == -EAGAIN) { /* getrandom(GRND_NONBLOCK) failed because lack of entropy. Retry with GRND_INSECURE. */ for (;;) { l = _getrandom_insecure(p, n); if (l > 0) { if ((size_t) l == n) { NM_SET_OUT(out_high_quality, FALSE); return; } p = ((uint8_t *) p) + l; n -= l; continue; } /* Any error. Fallback to /dev/urandom. */ break; } } } again_open: fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); if (fd < 0) { if (errno == EINTR) goto again_open; } else { r = nm_utils_fd_read_loop_exact(fd, p, n, TRUE); nm_close(fd); if (r >= 0) { NM_SET_OUT(out_high_quality, has_high_quality); return; } } /* we failed to fill the bytes reading from /dev/urandom. * Fill the bits using our fallback approach (which obviously * cannot give high quality random). */ _bad_random_bytes(p, n); NM_SET_OUT(out_high_quality, FALSE); } /*****************************************************************************/ /** * nm_random_get_crypto_bytes: * @p: the buffer to fill * @n: the number of bytes to fill * * - can fail (in which case a negative number is returned * and the output buffer is undefined). * - will block trying to get high quality random numbers. */ int nm_random_get_crypto_bytes(void *p, size_t n) { nm_auto_close int fd = -1; ssize_t l; int r; if (n == 0) return 0; nm_assert(p); again_getrandom: l = _getrandom(p, n, 0); if (l > 0) { if ((size_t) l == n) return 0; p = (uint8_t *) p + l; n -= l; goto again_getrandom; } if (l != -ENOSYS) { /* We got a failure, but getrandom seems to be working in principle. We * won't get good numbers. Fail. */ return l; } /* getrandom() failed with ENOSYS. Fallback to reading /dev/urandom. */ r = _random_check_entropy(TRUE); if (r < 0) return r; if (r == 0) return nm_assert_unreachable_val(-EIO); fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); if (fd < 0) return -errno; return nm_utils_fd_read_loop_exact(fd, p, n, FALSE); } /*****************************************************************************/ guint64 nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes) { gboolean bad_crypto_bytes = FALSE; guint64 remainder; guint64 maxvalue; guint64 x; guint64 m; /* Returns a random #guint64 equally distributed in the range [@begin..@end-1]. * * The function always set errno. It either sets it to zero or to EAGAIN * (if crypto_bytes were requested but not obtained). In any case, the function * will always return a random number in the requested range (worst case, it's * not crypto_bytes despite being requested). Check errno if you care. */ if (begin >= end) { /* systemd's random_u64_range(0) is an alias for nm_random_u64(). * Not for us. It's a caller error to request an element from an empty range. */ return nm_assert_unreachable_val(begin); } m = end - begin; if (m == 1) { x = 0; goto out; } remainder = G_MAXUINT64 % m; maxvalue = G_MAXUINT64 - remainder; do if (crypto_bytes) { if (nm_random_get_crypto_bytes(&x, sizeof(x)) < 0) { /* Cannot get good crypto numbers. We will try our best, but fail * and set errno below. */ crypto_bytes = FALSE; bad_crypto_bytes = TRUE; continue; } } else nm_random_get_bytes(&x, sizeof(x)); while (x >= maxvalue); out: errno = bad_crypto_bytes ? EAGAIN : 0; return begin + (x % m); }