diff options
| author | Sebastien Bacher <seb128@ubuntu.com> | 2021-08-25 15:24:42 +0200 |
|---|---|---|
| committer | Sebastien Bacher <seb128@ubuntu.com> | 2021-08-25 15:24:42 +0200 |
| commit | dbb91282fa488964fb20595f9494a9f0e4f36a58 (patch) | |
| tree | 142bc942e5320b35514cdf03a5f9f47a9b89df0e /src/libnm-glib-aux/nm-random-utils.c | |
| parent | 5f2ede3a2813b0e9204befdcfc67509d34be71c6 (diff) | |
| parent | cfb80376641fa49137b9996130352697e7f8b436 (diff) | |
Update upstream source from tag 'upstream/1.32.10'
Update to upstream version '1.32.10' with Debian dir fcf2778b50b013ede3e7375bc1d829e984175658
Diffstat (limited to 'src/libnm-glib-aux/nm-random-utils.c')
| -rw-r--r-- | src/libnm-glib-aux/nm-random-utils.c | 297 |
1 files changed, 224 insertions, 73 deletions
diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c index 56b99d5e..b055bc3f 100644 --- a/src/libnm-glib-aux/nm-random-utils.c +++ b/src/libnm-glib-aux/nm-random-utils.c @@ -8,6 +8,8 @@ #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> @@ -16,9 +18,185 @@ #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 @@ -46,9 +224,7 @@ nm_utils_random_bytes(void *p, size_t n) int fd; int r; gboolean has_high_quality = TRUE; - gboolean urandom_success; - guint8 * buf = p; - gboolean avoid_urandom = FALSE; + guint8 * buf = p; g_return_val_if_fail(p, FALSE); g_return_val_if_fail(n > 0, FALSE); @@ -58,91 +234,66 @@ nm_utils_random_bytes(void *p, size_t n) static gboolean have_syscall = TRUE; if (have_syscall) { - r = getrandom(buf, n, GRND_NONBLOCK); - if (r > 0) { - if ((size_t) r == n) + 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 from urandom, and remember that - * some bits are not high quality. */ - nm_assert(r < n); - buf += r; - n -= r; - has_high_quality = FALSE; + * 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. * - * We use our fallback via GRand. Note that g_rand_new() also - * tries to seed itself with data from /dev/urandom, but since - * we reuse the instance, it shouldn't matter. */ - avoid_urandom = TRUE; + * 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 { - if (errno == ENOSYS) { - /* no support for getrandom(). We don't know whether - * we urandom will give us good quality. Assume yes. */ - have_syscall = FALSE; - } else { - /* unknown error. We'll read urandom below, but we don't have - * high-quality randomness. */ - has_high_quality = FALSE; - } + /* Unknown error, likely no entropy. We'll read /dev/urandom below, but we don't + * have high-quality randomness. */ + has_high_quality = FALSE; } } } #endif - urandom_success = FALSE; - if (!avoid_urandom) { fd_open: - fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); - if (fd < 0) { - r = errno; - if (r == EINTR) - goto fd_open; - } else { - r = nm_utils_fd_read_loop_exact(fd, buf, n, TRUE); - nm_close(fd); - if (r >= 0) - urandom_success = TRUE; - } - } - - if (!urandom_success) { - static _nm_thread_local GRand *rand = NULL; - gsize i; - int j; - - /* we failed to fill the bytes reading from urandom. - * Fill the bits using GRand pseudo random numbers. - * - * We don't have good quality. - */ - has_high_quality = FALSE; - - if (G_UNLIKELY(!rand)) - rand = g_rand_new(); - - nm_assert(n > 0); - i = 0; - for (;;) { - const union { - guint32 v32; - guint8 v8[4]; - } v = { - .v32 = g_rand_int(rand), - }; - - for (j = 0; j < 4;) { - buf[i++] = v.v8[j++]; - if (i >= n) - goto done; - } - } -done:; + 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; - 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; } |