diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /src/libnm-glib-aux/nm-random-utils.c | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/libnm-glib-aux/nm-random-utils.c')
| -rw-r--r-- | src/libnm-glib-aux/nm-random-utils.c | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c new file mode 100644 index 00000000..b055bc3f --- /dev/null +++ b/src/libnm-glib-aux/nm-random-utils.c @@ -0,0 +1,299 @@ +/* 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 <fcntl.h> +#include <sys/auxv.h> +#include <sys/syscall.h> + +#if USE_SYS_RANDOM_H + #include <sys/random.h> +#else + #include <linux/random.h> +#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 && defined(SYS_getrandom) +static int +getrandom(void *buf, size_t buflen, unsigned flags) +{ + return syscall(SYS_getrandom, buf, buflen, flags); +} + #undef HAVE_GETRANDOM + #define HAVE_GETRANDOM 1 +#endif + +/*****************************************************************************/ + +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; + GRand *rand; +} 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, but we already noticed that + * /dev/urandom fails to give us good randomness (which is why + * we hit the "bad randomness" 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); + } + +#if HAVE_GETRANDOM + { + ssize_t r; + + /* This is likely to fail, because we already failed a moment earlier. Still, give + * it a try. */ + r = getrandom(seed->getrandom_buf, + sizeof(seed->getrandom_buf), + GRND_INSECURE | GRND_NONBLOCK); + (void) r; + } +#endif + + 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 not be good randomness. See _bad_random_init_seed(). + * + * 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* randomness. And with the inability to get a good + * seed, the CPRNG is not going to give us truly good 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); + } + + 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 + * 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 randomness. That + * is for example required to generate 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_utils_random_bytes: + * @p: the buffer to fill + * @n: the number of bytes to write to @p. + * + * Uses getrandom() or reads /dev/urandom to fill the buffer + * with random data. If all fails, as last fallback it uses + * GRand to fill the buffer with pseudo random numbers. + * The function always succeeds in writing some random numbers + * to the buffer. The return value of FALSE indicates that the + * obtained bytes are probably not of good randomness. + * + * Returns: whether the written bytes are good. If you + * don't require good randomness, you can ignore the return + * value. + * + * Note that if calling getrandom() fails because there is not enough + * entropy (at early boot), the function will read /dev/urandom. + * Which of course, still has low entropy, and cause kernel to log + * a warning. + */ +gboolean +nm_utils_random_bytes(void *p, size_t n) +{ + int fd; + int r; + gboolean has_high_quality = TRUE; + guint8 * buf = p; + + g_return_val_if_fail(p, FALSE); + g_return_val_if_fail(n > 0, FALSE); + +#if HAVE_GETRANDOM + { + static gboolean have_syscall = TRUE; + + if (have_syscall) { + ssize_t r2; + int errsv; + + r2 = getrandom(buf, n, GRND_NONBLOCK); + if (r2 >= 0) { + if ((size_t) r2 == n) + return TRUE; + + /* no or partial read. There is not enough entropy. + * Fill the rest reading with the fallback code and remember + * that some bits are not high quality. */ + nm_assert((size_t) r2 < n); + buf += r2; + n -= r2; + + /* At this point, we don't want to read /dev/urandom, because + * the entropy pool is low (early boot?), and asking for more + * entropy causes kernel messages to be logged. + * + * Note that we fall back to _bad_random_bytes(), which (among others) seeds + * itself with g_rand_new(). That also will read /dev/urandom, but as + * we do that only once, we don't care. But in general, we are here in + * a situation where we want to avoid reading /dev/urandom too much. */ + goto out_bad_random; + } + errsv = errno; + if (errsv == ENOSYS) { + /* no support for getrandom(). We don't know whether + * we /dev/urandom will give us good quality. Assume yes. */ + have_syscall = FALSE; + } else if (errsv == EAGAIN) { + /* No entropy. We avoid reading /dev/urandom. */ + goto out_bad_random; + } else { + /* Unknown error, likely no entropy. We'll read /dev/urandom below, but we don't + * have high-quality randomness. */ + has_high_quality = FALSE; + } + } + } +#endif + +fd_open: + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd < 0) { + if (errno == EINTR) + goto fd_open; + goto out_bad_random; + } + r = nm_utils_fd_read_loop_exact(fd, buf, n, TRUE); + nm_close(fd); + if (r >= 0) + return has_high_quality; + +out_bad_random: + /* we failed to fill the bytes reading from /dev/urandom. + * Fill the bits using our pseudo random numbers. + * + * We don't have good quality. + */ + _bad_random_bytes(buf, n); + return FALSE; +} |