summary refs log tree commit diff
path: root/src/libnm-glib-aux
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2022-08-12 19:21:11 +0200
committerMichael Biebl <biebl@debian.org>2022-08-12 19:21:11 +0200
commit6accbd3ec0e42d8633bbde4d47ed7bfe854e7e0b (patch)
treea77a0617213a5183a4de43824e3fbae9779e9b8e /src/libnm-glib-aux
parentf7c750061dab327e638c0129cfafd5b2215b3a2e (diff)
New upstream version 1.38.4 upstream/1.38.4
Diffstat (limited to 'src/libnm-glib-aux')
-rw-r--r--src/libnm-glib-aux/nm-hash-utils.c2
-rw-r--r--src/libnm-glib-aux/nm-random-utils.c340
-rw-r--r--src/libnm-glib-aux/nm-random-utils.h10
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.c9
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.h29
-rw-r--r--src/libnm-glib-aux/nm-uuid.c4
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c2
7 files changed, 296 insertions, 100 deletions
diff --git a/src/libnm-glib-aux/nm-hash-utils.c b/src/libnm-glib-aux/nm-hash-utils.c
index 68b33998..941aba01 100644
--- a/src/libnm-glib-aux/nm-hash-utils.c
+++ b/src/libnm-glib-aux/nm-hash-utils.c
@@ -45,7 +45,7 @@ again:
             guint8 _extra_entropy[3 * HASH_KEY_SIZE];
         } t_arr;
 
-        nm_utils_random_bytes(&t_arr, sizeof(t_arr));
+        nm_random_get_bytes(&t_arr, sizeof(t_arr));
 
         /* We only initialize one random hash key. So we can spend some effort
          * of getting this right. For one, we collect more random bytes than
diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c
index 3a8ba64f..93eee7c4 100644
--- a/src/libnm-glib-aux/nm-random-utils.c
+++ b/src/libnm-glib-aux/nm-random-utils.c
@@ -10,6 +10,7 @@
 #include <fcntl.h>
 #include <sys/auxv.h>
 #include <sys/syscall.h>
+#include <poll.h>
 
 #if USE_SYS_RANDOM_H
 #include <sys/random.h>
@@ -34,18 +35,121 @@
 #define GRND_INSECURE 0x04
 #endif
 
-#if !HAVE_GETRANDOM && defined(SYS_getrandom)
-static int
+#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
 }
-#undef HAVE_GETRANDOM
-#define HAVE_GETRANDOM 1
 #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;
@@ -72,7 +176,8 @@ typedef struct _nm_packed {
         guint8  u8[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2];
         guint32 u32[((NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2) + 3) / 4];
     } rand_vals;
-    GRand *rand;
+    guint8 rand_vals_getrandom[16];
+    gint64 rand_vals_timestamp;
 } BadRandState;
 
 static void
@@ -106,18 +211,7 @@ _bad_random_init_seed(BadRandSeed *seed)
         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
+    _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();
@@ -168,6 +262,10 @@ _bad_random_bytes(guint8 *buf, gsize n)
             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;
 
@@ -197,103 +295,155 @@ _bad_random_bytes(guint8 *buf, gsize n)
     }
 }
 
+/*****************************************************************************/
+
 /**
- * nm_utils_random_bytes:
+ * nm_random_get_bytes_full:
  * @p: the buffer to fill
  * @n: the number of bytes to write to @p.
+ * @out_high_quality: (allow-none) (out): whether the returned
+ *   random bytes are of high quality.
  *
- * 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.
+ * - 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().
  */
-gboolean
-nm_utils_random_bytes(void *p, size_t n)
+void
+nm_random_get_bytes_full(void *p, size_t n, gboolean *out_high_quality)
 {
     int      fd;
     int      r;
-    gboolean has_high_quality = TRUE;
-    guint8  *buf              = p;
+    gboolean has_high_quality;
+    ssize_t  l;
 
-    g_return_val_if_fail(p, FALSE);
-    g_return_val_if_fail(n > 0, FALSE);
+    if (n == 0) {
+        NM_SET_OUT(out_high_quality, TRUE);
+        return;
+    }
 
-#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;
+    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;
             }
         }
     }
-#endif
 
-fd_open:
+again_open:
     fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY);
     if (fd < 0) {
         if (errno == EINTR)
-            goto fd_open;
-        goto out_bad_random;
+            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;
+        }
     }
-    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.
+     * Fill the bits using our fallback approach (which obviously
+     * cannot give high quality random).
      */
-    _bad_random_bytes(buf, n);
-    return FALSE;
+    _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);
 }
diff --git a/src/libnm-glib-aux/nm-random-utils.h b/src/libnm-glib-aux/nm-random-utils.h
index d0eae103..ab8aee1b 100644
--- a/src/libnm-glib-aux/nm-random-utils.h
+++ b/src/libnm-glib-aux/nm-random-utils.h
@@ -6,6 +6,14 @@
 #ifndef __NM_RANDOM_UTILS_H__
 #define __NM_RANDOM_UTILS_H__
 
-gboolean nm_utils_random_bytes(void *p, size_t n);
+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);
 
 #endif /* __NM_RANDOM_UTILS_H__ */
diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c
index e6ee04d7..f770b96d 100644
--- a/src/libnm-glib-aux/nm-shared-utils.c
+++ b/src/libnm-glib-aux/nm-shared-utils.c
@@ -3347,6 +3347,8 @@ nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec)
     struct timespec ts, *pts;
     int             r;
 
+    nm_assert(fd >= 0);
+
     if (timeout_nsec < 0)
         pts = NULL;
     else {
@@ -3360,6 +3362,13 @@ nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec)
         return -NM_ERRNO_NATIVE(errno);
     if (r == 0)
         return 0;
+
+    nm_assert(r == 1);
+    nm_assert(pollfd.revents > 0);
+
+    if (pollfd.revents & POLLNVAL)
+        return nm_assert_unreachable_val(-EBADF);
+
     return pollfd.revents;
 }
 
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index 52cf1d4c..0589eeac 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -623,6 +623,16 @@ nm_utils_is_separator(const char c)
 
 /*****************************************************************************/
 
+static inline GBytes *
+nm_g_bytes_ref(GBytes *b)
+{
+    if (b)
+        g_bytes_ref(b);
+    return b;
+}
+
+/*****************************************************************************/
+
 GBytes *nm_g_bytes_get_empty(void);
 
 GBytes *nm_g_bytes_new_from_str(const char *str);
@@ -2311,6 +2321,25 @@ nm_g_hash_table_contains(GHashTable *hash, gconstpointer key)
     return hash ? g_hash_table_contains(hash, key) : FALSE;
 }
 
+#define nm_g_hash_table_contains_any(hash, ...)                              \
+    ({                                                                       \
+        GHashTable *const   _hash   = (hash);                                \
+        gconstpointer const _keys[] = {__VA_ARGS__};                         \
+        int                 _i_key;                                          \
+        gboolean            _contains = FALSE;                               \
+                                                                             \
+        if (_hash) {                                                         \
+            for (_i_key = 0; _i_key < (int) G_N_ELEMENTS(_keys); _i_key++) { \
+                if (g_hash_table_contains(_hash, _keys[_i_key])) {           \
+                    _contains = TRUE;                                        \
+                    break;                                                   \
+                }                                                            \
+            }                                                                \
+        }                                                                    \
+                                                                             \
+        _contains;                                                           \
+    })
+
 static inline gboolean
 nm_g_hash_table_remove(GHashTable *hash, gconstpointer key)
 {
diff --git a/src/libnm-glib-aux/nm-uuid.c b/src/libnm-glib-aux/nm-uuid.c
index ff0649d0..464bbf58 100644
--- a/src/libnm-glib-aux/nm-uuid.c
+++ b/src/libnm-glib-aux/nm-uuid.c
@@ -115,12 +115,12 @@ nm_uuid_generate_random(NMUuid *out_uuid)
 
     /* See also, systemd's id128_make_v4_uuid() */
 
-    /* nm_utils_random_bytes() is supposed to try hard to give good
+    /* nm_random_get_bytes() is supposed to try hard to give good
      * randomness. If it fails, it still makes an effort to fill
      * random data into the buffer. There is not much we can do about
      * that case, except making sure that it does not happen in the
      * first place. */
-    nm_utils_random_bytes(out_uuid, sizeof(*out_uuid));
+    nm_random_get_bytes(out_uuid, sizeof(*out_uuid));
 
     /* Set the four most significant bits (bits 12 through 15) of the
      * time_hi_and_version field to the 4-bit version number from
diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c
index e7fd2b6b..9acd535d 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -93,7 +93,7 @@ test_nmhash(void)
 {
     int rnd;
 
-    nm_utils_random_bytes(&rnd, sizeof(rnd));
+    nm_random_get_bytes(&rnd, sizeof(rnd));
 
     g_assert(nm_hash_val(555, 4) != 0);
 }