331 lines
10 KiB
Diff
331 lines
10 KiB
Diff
From 22667bc8d6e6971dc9e25f713ca4805112b71791 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
|
|
Date: Tue, 19 Aug 2025 19:22:18 +0200
|
|
Subject: [PATCH] Use cryptographically-secure pseudo-random generator
|
|
everywhere
|
|
|
|
It was discovered in an upcoming academic paper that a xoshiro128**
|
|
internal state can be recovered by an external 3rd party allowing to
|
|
predict UDP ports and DNS IDs in the outgoing queries. This could lead
|
|
to an attacker spoofing the DNS answers with great efficiency and
|
|
poisoning the DNS cache.
|
|
|
|
Change the internal random generator to system CSPRNG with buffering to
|
|
avoid excessive syscalls.
|
|
|
|
Thanks Omer Ben Simhon and Amit Klein of Hebrew University of Jerusalem
|
|
for responsibly reporting this to us. Very cool research!
|
|
|
|
(cherry picked from commit cffcab9d5f3e709002f331b72498fcc229786ae2)
|
|
(cherry picked from commit 8330b49fb90bfeae14b47b7983e9459cc2bbaffe)
|
|
---
|
|
lib/isc/include/isc/random.h | 2 +-
|
|
lib/isc/random.c | 225 ++++++++++++++---------------------
|
|
tests/isc/random_test.c | 4 +-
|
|
3 files changed, 96 insertions(+), 135 deletions(-)
|
|
|
|
diff --git a/lib/isc/include/isc/random.h b/lib/isc/include/isc/random.h
|
|
index 1e30d0c87d..fd55343778 100644
|
|
--- a/lib/isc/include/isc/random.h
|
|
+++ b/lib/isc/include/isc/random.h
|
|
@@ -20,7 +20,7 @@
|
|
#include <isc/types.h>
|
|
|
|
/*! \file isc/random.h
|
|
- * \brief Implements wrapper around a non-cryptographically secure
|
|
+ * \brief Implements wrapper around a cryptographically secure
|
|
* pseudo-random number generator.
|
|
*
|
|
*/
|
|
diff --git a/lib/isc/random.c b/lib/isc/random.c
|
|
index fb0466953d..6f37f5dedf 100644
|
|
--- a/lib/isc/random.c
|
|
+++ b/lib/isc/random.c
|
|
@@ -31,176 +31,135 @@
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
-#include <stdlib.h>
|
|
-#include <string.h>
|
|
-#include <unistd.h>
|
|
+#include <stdio.h>
|
|
|
|
-#include <isc/once.h>
|
|
+#include <isc/os.h>
|
|
#include <isc/random.h>
|
|
-#include <isc/result.h>
|
|
#include <isc/thread.h>
|
|
-#include <isc/types.h>
|
|
#include <isc/util.h>
|
|
|
|
#include "entropy_private.h"
|
|
|
|
-/*
|
|
- * The specific implementation for PRNG is included as a C file
|
|
- * that has to provide a static variable named seed, and a function
|
|
- * uint32_t next(void) that provides next random number.
|
|
- *
|
|
- * The implementation must be thread-safe.
|
|
- */
|
|
-
|
|
-/*
|
|
- * Two contestants have been considered: the xoroshiro family of the
|
|
- * functions by Villa&Blackman, and PCG by O'Neill. After
|
|
- * consideration, the xoshiro128starstar function has been chosen as
|
|
- * the uint32_t random number provider because it is very fast and has
|
|
- * good enough properties for our usage pattern.
|
|
- */
|
|
-
|
|
-/*
|
|
- * Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org)
|
|
- *
|
|
- * To the extent possible under law, the author has dedicated all
|
|
- * copyright and related and neighboring rights to this software to the
|
|
- * public domain worldwide. This software is distributed without any
|
|
- * warranty.
|
|
- *
|
|
- * See <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
- */
|
|
+#define ISC_RANDOM_BUFSIZE (ISC_OS_CACHELINE_SIZE / sizeof(uint32_t))
|
|
|
|
-/*
|
|
- * This is xoshiro128** 1.0, our 32-bit all-purpose, rock-solid generator.
|
|
- * It has excellent (sub-ns) speed, a state size (128 bits) that is large
|
|
- * enough for mild parallelism, and it passes all tests we are aware of.
|
|
- *
|
|
- * For generating just single-precision (i.e., 32-bit) floating-point
|
|
- * numbers, xoshiro128+ is even faster.
|
|
- *
|
|
- * The state must be seeded so that it is not everywhere zero.
|
|
- */
|
|
-static thread_local uint32_t seed[4] = { 0 };
|
|
+thread_local static uint32_t isc__random_pool[ISC_RANDOM_BUFSIZE];
|
|
+thread_local static size_t isc__random_pos = ISC_RANDOM_BUFSIZE;
|
|
|
|
static uint32_t
|
|
-rotl(const uint32_t x, int k) {
|
|
- return (x << k) | (x >> (32 - k));
|
|
-}
|
|
-
|
|
-static uint32_t
|
|
-next(void) {
|
|
- uint32_t result_starstar, t;
|
|
-
|
|
- result_starstar = rotl(seed[0] * 5, 7) * 9;
|
|
- t = seed[1] << 9;
|
|
-
|
|
- seed[2] ^= seed[0];
|
|
- seed[3] ^= seed[1];
|
|
- seed[1] ^= seed[2];
|
|
- seed[0] ^= seed[3];
|
|
-
|
|
- seed[2] ^= t;
|
|
-
|
|
- seed[3] = rotl(seed[3], 11);
|
|
-
|
|
- return result_starstar;
|
|
-}
|
|
-
|
|
-static thread_local isc_once_t isc_random_once = ISC_ONCE_INIT;
|
|
-
|
|
-static void
|
|
-isc_random_initialize(void) {
|
|
- int useed[4] = { 0, 0, 0, 1 };
|
|
+random_u32(void) {
|
|
#if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
|
/*
|
|
- * Set a constant seed to help in problem reproduction should fuzzing
|
|
- * find a crash or a hang. The seed array must be non-zero else
|
|
- * xoshiro128starstar will generate an infinite series of zeroes.
|
|
+ * A fixed stream of numbers helps with problem reproduction when
|
|
+ * fuzzing. The first result needs to be non-zero as expected by
|
|
+ * random_test.c (it starts with ISC_RANDOM_BUFSIZE, see above).
|
|
*/
|
|
-#else /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
|
|
- isc_entropy_get(useed, sizeof(useed));
|
|
+ return (uint32_t)(isc__random_pos++);
|
|
#endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
|
|
- memmove(seed, useed, sizeof(seed));
|
|
+
|
|
+ if (isc__random_pos == ISC_RANDOM_BUFSIZE) {
|
|
+ isc_entropy_get(isc__random_pool, sizeof(isc__random_pool));
|
|
+ isc__random_pos = 0;
|
|
+ }
|
|
+
|
|
+ return isc__random_pool[isc__random_pos++];
|
|
}
|
|
|
|
uint8_t
|
|
isc_random8(void) {
|
|
- RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
|
|
- ISC_R_SUCCESS);
|
|
- return next() & 0xff;
|
|
+ return (uint8_t)random_u32();
|
|
}
|
|
|
|
uint16_t
|
|
isc_random16(void) {
|
|
- RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
|
|
- ISC_R_SUCCESS);
|
|
- return next() & 0xffff;
|
|
+ return (uint16_t)random_u32();
|
|
}
|
|
|
|
uint32_t
|
|
isc_random32(void) {
|
|
- RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
|
|
- ISC_R_SUCCESS);
|
|
- return next();
|
|
+ return random_u32();
|
|
}
|
|
|
|
void
|
|
isc_random_buf(void *buf, size_t buflen) {
|
|
- int i;
|
|
- uint32_t r;
|
|
-
|
|
- REQUIRE(buf != NULL);
|
|
- REQUIRE(buflen > 0);
|
|
-
|
|
- RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
|
|
- ISC_R_SUCCESS);
|
|
+ REQUIRE(buflen == 0 || buf != NULL);
|
|
|
|
- for (i = 0; i + sizeof(r) <= buflen; i += sizeof(r)) {
|
|
- r = next();
|
|
- memmove((uint8_t *)buf + i, &r, sizeof(r));
|
|
+ if (buf == NULL || buflen == 0) {
|
|
+ return;
|
|
}
|
|
- r = next();
|
|
- memmove((uint8_t *)buf + i, &r, buflen % sizeof(r));
|
|
- return;
|
|
+
|
|
+ isc_entropy_get(buf, buflen);
|
|
}
|
|
|
|
uint32_t
|
|
-isc_random_uniform(uint32_t upper_bound) {
|
|
- /* Copy of arc4random_uniform from OpenBSD */
|
|
- uint32_t r, min;
|
|
-
|
|
- RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
|
|
- ISC_R_SUCCESS);
|
|
-
|
|
- if (upper_bound < 2) {
|
|
- return 0;
|
|
- }
|
|
-
|
|
-#if (ULONG_MAX > 0xffffffffUL)
|
|
- min = 0x100000000UL % upper_bound;
|
|
-#else /* if (ULONG_MAX > 0xffffffffUL) */
|
|
- /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
|
|
- if (upper_bound > 0x80000000) {
|
|
- min = 1 + ~upper_bound; /* 2**32 - upper_bound */
|
|
- } else {
|
|
- /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
|
|
- min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound;
|
|
- }
|
|
-#endif /* if (ULONG_MAX > 0xffffffffUL) */
|
|
-
|
|
+isc_random_uniform(uint32_t limit) {
|
|
/*
|
|
- * This could theoretically loop forever but each retry has
|
|
- * p > 0.5 (worst case, usually far better) of selecting a
|
|
- * number inside the range we need, so it should rarely need
|
|
- * to re-roll.
|
|
+ * Daniel Lemire's nearly-divisionless unbiased bounded random numbers.
|
|
+ *
|
|
+ * https://lemire.me/blog/?p=17551
|
|
+ *
|
|
+ * The raw random number generator `next()` returns a 32-bit value.
|
|
+ * We do a 64-bit multiply `next() * limit` and treat the product as a
|
|
+ * 32.32 fixed-point value less than the limit. Our result will be the
|
|
+ * integer part (upper 32 bits), and we will use the fraction part
|
|
+ * (lower 32 bits) to determine whether or not we need to resample.
|
|
*/
|
|
- for (;;) {
|
|
- r = next();
|
|
- if (r >= min) {
|
|
- break;
|
|
+ uint64_t num = (uint64_t)random_u32() * (uint64_t)limit;
|
|
+ /*
|
|
+ * In the fast path, we avoid doing a division in most cases by
|
|
+ * comparing the fraction part of `num` with the limit, which is
|
|
+ * a slight over-estimate for the exact resample threshold.
|
|
+ */
|
|
+ if ((uint32_t)(num) < limit) {
|
|
+ /*
|
|
+ * We are in the slow path where we re-do the approximate test
|
|
+ * more accurately. The exact threshold for the resample loop
|
|
+ * is the remainder after dividing the raw RNG limit `1 << 32`
|
|
+ * by the caller's limit. We use a trick to calculate it
|
|
+ * within 32 bits:
|
|
+ *
|
|
+ * (1 << 32) % limit
|
|
+ * == ((1 << 32) - limit) % limit
|
|
+ * == (uint32_t)(-limit) % limit
|
|
+ *
|
|
+ * This division is safe: we know that `limit` is strictly
|
|
+ * greater than zero because of the slow-path test above.
|
|
+ */
|
|
+ uint32_t residue = (uint32_t)(-limit) % limit;
|
|
+ /*
|
|
+ * Unless we get one of `N = (1 << 32) - residue` valid
|
|
+ * values, we reject the sample. This `N` is a multiple of
|
|
+ * `limit`, so our results will be unbiased; and `N` is the
|
|
+ * largest multiple that fits in 32 bits, so rejections are as
|
|
+ * rare as possible.
|
|
+ *
|
|
+ * There are `limit` possible values for the integer part of
|
|
+ * our fixed-point number. Each one corresponds to `N/limit`
|
|
+ * or `N/limit + 1` possible fraction parts. For our result to
|
|
+ * be unbiased, every possible integer part must have the same
|
|
+ * number of possible valid fraction parts. So, when we get
|
|
+ * the superfluous value in the `N/limit + 1` cases, we need
|
|
+ * to reject and resample.
|
|
+ *
|
|
+ * Because of the multiplication, the possible values in the
|
|
+ * fraction part are equally spaced by `limit`, with varying
|
|
+ * gaps at each end of the fraction's 32-bit range. We will
|
|
+ * choose a range of size `N` (a multiple of `limit`) into
|
|
+ * which valid fraction values must fall, with the rest of the
|
|
+ * 32-bit range covered by the `residue`. Lemire's paper says
|
|
+ * that exactly `N/limit` possible values spaced apart by
|
|
+ * `limit` will fit into our size `N` valid range, regardless
|
|
+ * of the size of the end gaps, the phase alignment of the
|
|
+ * values, or the position of the range.
|
|
+ *
|
|
+ * So, when a fraction value falls in the `residue` outside
|
|
+ * our valid range, it is superfluous, and we resample.
|
|
+ */
|
|
+ while ((uint32_t)(num) < residue) {
|
|
+ num = (uint64_t)random_u32() * (uint64_t)limit;
|
|
}
|
|
}
|
|
-
|
|
- return r % upper_bound;
|
|
+ /*
|
|
+ * Return the integer part (upper 32 bits).
|
|
+ */
|
|
+ return (uint32_t)(num >> 32);
|
|
}
|
|
diff --git a/tests/isc/random_test.c b/tests/isc/random_test.c
|
|
index ccba317023..52f219bb3a 100644
|
|
--- a/tests/isc/random_test.c
|
|
+++ b/tests/isc/random_test.c
|
|
@@ -321,7 +321,9 @@ random_test(pvalue_func_t *func, isc_random_func test_func) {
|
|
}
|
|
break;
|
|
case ISC_RANDOM_BYTES:
|
|
- isc_random_buf(values, sizeof(values));
|
|
+ for (i = 0; i < ARRAY_SIZE(values); i++) {
|
|
+ values[i] = isc_random32();
|
|
+ }
|
|
break;
|
|
case ISC_RANDOM_UNIFORM:
|
|
uniform_values = (uint16_t *)values;
|
|
--
|
|
2.51.0
|
|
|