summary refs log tree commit diff
path: root/src/libnm-glib-aux
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2023-06-12 11:25:29 +0200
committerMichael Biebl <biebl@debian.org>2023-06-12 11:25:29 +0200
commit150fe9eef8dd22307ee16687509acde616663982 (patch)
tree200fa1178c9aeee2ff874bb63afe93623da4be0d /src/libnm-glib-aux
parentdfaaf221cfee4ffcc507d793dae051d402646679 (diff)
New upstream version 1.42.6 upstream/1.42.6
Diffstat (limited to 'src/libnm-glib-aux')
-rw-r--r--src/libnm-glib-aux/nm-random-utils.c68
-rw-r--r--src/libnm-glib-aux/nm-random-utils.h35
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c50
3 files changed, 145 insertions, 8 deletions
diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c
index 93eee7c4..2050d2f9 100644
--- a/src/libnm-glib-aux/nm-random-utils.c
+++ b/src/libnm-glib-aux/nm-random-utils.c
@@ -187,9 +187,9 @@ _bad_random_init_seed(BadRandSeed *seed)
     int           seed_idx;
     GRand        *rand;
 
-    /* g_rand_new() reads /dev/urandom, but we already noticed that
+    /* 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 randomness" code path). So this may not be as
+     * 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();
@@ -231,14 +231,14 @@ _bad_random_bytes(guint8 *buf, gsize n)
      * 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().
+     * 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* randomness. And with the inability to get a good
-     * seed, the CPRNG is not going to give us truly good randomness. */
+     * 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;
@@ -277,11 +277,11 @@ _bad_random_bytes(guint8 *buf, gsize n)
             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.
+             * 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 randomness. That
-             * is for example required to generate a random UUID. */
+             * 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]
@@ -447,3 +447,55 @@ again_getrandom:
 
     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);
+}
diff --git a/src/libnm-glib-aux/nm-random-utils.h b/src/libnm-glib-aux/nm-random-utils.h
index ab8aee1b..729d71a4 100644
--- a/src/libnm-glib-aux/nm-random-utils.h
+++ b/src/libnm-glib-aux/nm-random-utils.h
@@ -16,4 +16,39 @@ nm_random_get_bytes(void *p, size_t n)
 
 int nm_random_get_crypto_bytes(void *p, size_t n);
 
+static inline guint32
+nm_random_u32(void)
+{
+    guint32 v;
+
+    nm_random_get_bytes(&v, sizeof(v));
+    return v;
+}
+
+static inline guint64
+nm_random_u64(void)
+{
+    guint64 v;
+
+    nm_random_get_bytes(&v, sizeof(v));
+    return v;
+}
+
+static inline bool
+nm_random_bool(void)
+{
+    guint8 ch;
+
+    nm_random_get_bytes(&ch, sizeof(ch));
+    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);
+}
+
 #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 7503dc9b..3eaca547 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -137,6 +137,55 @@ test_nmhash(void)
 
 /*****************************************************************************/
 
+static void
+test_nm_random(void)
+{
+    int i_run;
+
+    for (i_run = 0; i_run < 1000; i_run++) {
+        guint64 begin;
+        guint64 end;
+        guint64 m;
+        guint64 x;
+
+        m = nmtst_get_rand_uint64();
+        m = m >> (nmtst_get_rand_uint32() % 64);
+
+        if (m == 0)
+            continue;
+
+        switch (nmtst_get_rand_uint32() % 4) {
+        case 0:
+            begin = 0;
+            break;
+        case 1:
+            begin = nmtst_get_rand_uint64() % 1000;
+            break;
+        case 2:
+            begin = ((G_MAXUINT64 - m) - 500) + (nmtst_get_rand_uint64() % 1000);
+            break;
+        default:
+            begin = nmtst_get_rand_uint64() % (G_MAXUINT64 - m);
+            break;
+        }
+
+        end = (begin + m) - 10 + (nmtst_get_rand_uint64() % 5);
+
+        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());
+
+        g_assert_cmpuint(x, >=, begin);
+        g_assert_cmpuint(x, <, end);
+    }
+}
+
+/*****************************************************************************/
+
 static const char *
 _make_strv_foo(void)
 {
@@ -2417,6 +2466,7 @@ main(int argc, char **argv)
     g_test_add_func("/general/test_inet_utils", test_inet_utils);
     g_test_add_func("/general/test_garray", test_garray);
     g_test_add_func("/general/test_nm_prioq", test_nm_prioq);
+    g_test_add_func("/general/test_nm_random", test_nm_random);
 
     return g_test_run();
 }