diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c index 962058d66..ad2edd8df 100644 --- a/src/core/ndisc/nm-ndisc.c +++ b/src/core/ndisc/nm-ndisc.c @@ -988,9 +988,8 @@ announce_router(NMNDisc *ndisc) /* Schedule next initial announcement retransmit. */ priv->send_ra_id = - g_timeout_add_seconds(nm_random_u64_range_full(NM_NDISC_ROUTER_ADVERT_DELAY, - NM_NDISC_ROUTER_ADVERT_INITIAL_INTERVAL, - FALSE), + g_timeout_add_seconds(nm_random_u64_range(NM_NDISC_ROUTER_ADVERT_DELAY, + NM_NDISC_ROUTER_ADVERT_INITIAL_INTERVAL), (GSourceFunc) announce_router, ndisc); } else { @@ -1024,9 +1023,10 @@ announce_router_initial(NMNDisc *ndisc) /* Schedule the initial send rather early. Clamp the delay by minimal * delay and not the initial advert internal so that we start fast. */ if (G_LIKELY(!priv->send_ra_id)) { - priv->send_ra_id = g_timeout_add_seconds(nm_random_u64_range(NM_NDISC_ROUTER_ADVERT_DELAY), - (GSourceFunc) announce_router, - ndisc); + priv->send_ra_id = + g_timeout_add_seconds(nm_random_u64_range(0, NM_NDISC_ROUTER_ADVERT_DELAY), + (GSourceFunc) announce_router, + ndisc); } } @@ -1042,7 +1042,7 @@ announce_router_solicited(NMNDisc *ndisc) nm_clear_g_source(&priv->send_ra_id); if (!priv->send_ra_id) { - priv->send_ra_id = g_timeout_add(nm_random_u64_range(NM_NDISC_ROUTER_ADVERT_DELAY_MS), + priv->send_ra_id = g_timeout_add(nm_random_u64_range(0, NM_NDISC_ROUTER_ADVERT_DELAY_MS), (GSourceFunc) announce_router, ndisc); } diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index 2cae2d772..895a99177 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -2834,10 +2834,7 @@ _host_id_read(guint8 **out_host_id, gsize *out_host_id_len) int base64_save = 0; gsize len; - if (nm_random_get_crypto_bytes(rnd_buf, sizeof(rnd_buf)) < 0) - nm_random_get_bytes_full(rnd_buf, sizeof(rnd_buf), &success); - else - success = TRUE; + nm_random_get_bytes(rnd_buf, sizeof(rnd_buf)); /* Our key is really binary data. But since we anyway generate a random seed * (with 32 random bytes), don't write it in binary, but instead create @@ -2858,12 +2855,9 @@ _host_id_read(guint8 **out_host_id, gsize *out_host_id_len) secret_arr = _host_id_hash_v2(new_content, len, sha256_digest); secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256; + success = TRUE; - if (!success) - nm_log_warn(LOGD_CORE, - "secret-key: failure to generate good random data for secret-key (use " - "non-persistent key)"); - else if (nm_utils_get_testing()) { + if (nm_utils_get_testing()) { /* for test code, we don't write the generated secret-key to disk. */ } else if (!nm_utils_file_set_contents(SECRET_KEY_FILE, (const char *) new_content, @@ -3738,7 +3732,7 @@ _hw_addr_eth_complete(struct ether_addr *addr, nm_assert((ouis == NULL) ^ (ouis_len != 0)); if (ouis) { - oui = ouis[nm_random_u64_range(ouis_len)]; + oui = ouis[nm_random_u64_range(0, ouis_len)]; g_free(ouis); } else { if (!nm_utils_hwaddr_aton(current_mac_address, &oui, ETH_ALEN)) diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c index bbc5536ae..66c62251d 100644 --- a/src/libnm-glib-aux/nm-random-utils.c +++ b/src/libnm-glib-aux/nm-random-utils.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2017 Red Hat, Inc. + * Copyright (C) 2025 Jason A. Donenfeld . All Rights Reserved. */ #include "libnm-glib-aux/nm-default-glib-i18n-lib.h" @@ -51,420 +52,85 @@ getrandom(void *buf, size_t buflen, unsigned flags) /*****************************************************************************/ static ssize_t -_getrandom(void *buf, size_t buflen, unsigned flags) +getrandom_full(void *buf, size_t count, unsigned flags) { - static int have_getrandom = TRUE; - ssize_t l; - int errsv; + ssize_t ret; + uint8_t *p = buf; - 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(); + do { + ret = getrandom(p, count, flags); + if (ret < 0 && errno == EINTR) + continue; + else if (ret < 0) + return ret; + p += ret; + count -= ret; + } while (count); + return 0; } static void -_bad_random_bytes(guint8 *buf, gsize n) +dev_random_wait(void) { - nm_auto_free_checksum GChecksum *sum = g_checksum_new(G_CHECKSUM_SHA256); + static bool has_waited = false; + struct pollfd random_fd = {.events = POLLIN}; + int ret; - 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); + if (has_waited) return; + + random_fd.fd = open("/dev/random", O_RDONLY); + nm_assert(random_fd.fd >= 0); + for (;;) { + ret = poll(&random_fd, 1, -1); + if (ret == 1) + break; + nm_assert(ret == -1 && errno == EINTR); } - - 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_close(random_fd.fd); + has_waited = true; } -/*****************************************************************************/ +static ssize_t +dev_urandom_read_full(void *buf, size_t count) +{ + nm_auto_close int fd = open("/dev/urandom", O_RDONLY); + + nm_assert(fd >= 0); + return nm_utils_fd_read_loop_exact(fd, buf, count, FALSE); +} /** - * nm_random_get_crypto_bytes: + * nm_random_get_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) +void +nm_random_get_bytes(void *p, size_t n) { - nm_auto_close int fd = -1; - ssize_t l; - int r; + ssize_t ret; - if (n == 0) - return 0; + ret = getrandom_full(p, n, 0); + if (ret == 0) + return; + nm_assert(ret == 0 || (ret == -1 && errno == ENOSYS)); - 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); + dev_random_wait(); + ret = dev_urandom_read_full(p, n); + nm_assert(ret == 0); } /*****************************************************************************/ guint64 -nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes) +nm_random_u64_range(guint64 begin, guint64 end) { - gboolean bad_crypto_bytes = FALSE; - guint64 remainder; - guint64 maxvalue; - guint64 x; - guint64 m; + 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. */ + /* Returns a random #guint64 equally distributed in the range [@begin..@end-1]. */ if (begin >= end) { /* systemd's random_u64_range(0) is an alias for nm_random_u64(). @@ -483,19 +149,9 @@ nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes) 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)); + nm_random_get_bytes(&x, sizeof(x)); while (x >= maxvalue); out: - errno = bad_crypto_bytes ? EAGAIN : 0; return begin + (x % m); } diff --git a/src/libnm-glib-aux/nm-random-utils.h b/src/libnm-glib-aux/nm-random-utils.h index 729d71a4d..435019401 100644 --- a/src/libnm-glib-aux/nm-random-utils.h +++ b/src/libnm-glib-aux/nm-random-utils.h @@ -6,15 +6,7 @@ #ifndef __NM_RANDOM_UTILS_H__ #define __NM_RANDOM_UTILS_H__ -void nm_random_get_bytes_full(void *p, size_t n, gboolean *out_high_quality); - -static inline void -nm_random_get_bytes(void *p, size_t n) -{ - nm_random_get_bytes_full(p, n, NULL); -} - -int nm_random_get_crypto_bytes(void *p, size_t n); +void nm_random_get_bytes(void *p, size_t n); static inline guint32 nm_random_u32(void) @@ -43,12 +35,6 @@ nm_random_bool(void) return ch % 2u; } -guint64 nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes); - -static inline guint64 -nm_random_u64_range(guint64 end) -{ - return nm_random_u64_range_full(0, end, FALSE); -} +guint64 nm_random_u64_range(guint64 begin, guint64 end); #endif /* __NM_RANDOM_UTILS_H__ */ diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c index 48d524019..2f09a5490 100644 --- a/src/libnm-glib-aux/tests/test-shared-general.c +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -197,10 +197,7 @@ test_nm_random(void) if (begin >= end) continue; - if (begin == 0 && nmtst_get_rand_bool()) - x = nm_random_u64_range(end); - else - x = nm_random_u64_range_full(begin, end, nmtst_get_rand_bool()); + x = nm_random_u64_range(begin, end); g_assert_cmpuint(x, >=, begin); g_assert_cmpuint(x, <, end); diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index 170db42e1..b308e4406 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -4113,7 +4113,7 @@ generate_wpa_key(char *key, size_t len) int c; do { - c = nm_random_u64_range_full(48, 122, TRUE); + c = nm_random_u64_range(48, 122); /* skip characters that look similar */ } while (NM_IN_SET(c, '1', 'l', 'I', '0', 'O', 'Q', '8', 'B', '5', 'S') || !g_ascii_isalnum(c)); @@ -4136,7 +4136,7 @@ generate_wep_key(char *key, size_t len) for (i = 0; i < 10; i++) { int digit; - digit = nm_random_u64_range_full(0, 16, TRUE); + digit = nm_random_u64_range(0, 16); key[i] = hexdigits[digit]; } key[10] = '\0';