Linux: Upgrade random_get_pseudo_bytes() to xoshiro256++ 1.0

The motivation for upgrading our PRNG is the recent buildbot failures in
the ZTS' tests/functional/fault/decompress_fault test. The probability
of a failure in that test is 0.8^256, which is ~1.6e-25 out of 1, yet we
have observed multiple test failures in it. This suggests a problem with
our random number generation.

The xorshift128+ generator that we were using has been replaced by newer
generators that have "better statistical properties". After doing some
reading, it turns out that these generators have "low linear complexity
of the lowest bits", which could explain the ZTS test failures.

We do two things to try to fix this:

	1. We upgrade from xorshift128+ to xoshiro256++ 1.0.

	2. We tweak random_get_pseudo_bytes() to copy the higher order
	   bytes first.

It is hoped that this will fix the test failures in
tests/functional/fault/decompress_fault, although I have not done
simulations. I am skeptical that any simulations I do on a PRNG with a
period of 2^256 - 1 would be meaningful.

Since we have raised the minimum kernel version to 3.10 since this was
first implemented, we have the option of using the Linux kernel's
get_random_int(). However, I am not currently prepared to do performance
tests to ensure that this would not be a regression (for the time
being), so we opt for upgrading our PRNG to a newer one from Sebastiano
Vigna.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Tino Reichardt <milky-zfs@mcmilk.de>
Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Closes #13983
This commit is contained in:
Richard Yao 2022-10-20 17:14:42 -04:00 committed by GitHub
parent 9dcdee7889
commit a06df8d7c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -23,6 +23,7 @@
* Solaris Porting Layer (SPL) Generic Implementation. * Solaris Porting Layer (SPL) Generic Implementation.
*/ */
#include <sys/isa_defs.h>
#include <sys/sysmacros.h> #include <sys/sysmacros.h>
#include <sys/systeminfo.h> #include <sys/systeminfo.h>
#include <sys/vmsystm.h> #include <sys/vmsystm.h>
@ -61,10 +62,10 @@ proc_t p0;
EXPORT_SYMBOL(p0); EXPORT_SYMBOL(p0);
/* /*
* Xorshift Pseudo Random Number Generator based on work by Sebastiano Vigna * xoshiro256++ 1.0 PRNG by David Blackman and Sebastiano Vigna
* *
* "Further scramblings of Marsaglia's xorshift generators" * "Scrambled Linear Pseudorandom Number Generators"
* http://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf * https://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
* *
* random_get_pseudo_bytes() is an API function on Illumos whose sole purpose * random_get_pseudo_bytes() is an API function on Illumos whose sole purpose
* is to provide bytes containing random numbers. It is mapped to /dev/urandom * is to provide bytes containing random numbers. It is mapped to /dev/urandom
@ -76,14 +77,14 @@ EXPORT_SYMBOL(p0);
* free of atomic instructions. * free of atomic instructions.
* *
* A consequence of using a fast PRNG is that using random_get_pseudo_bytes() * A consequence of using a fast PRNG is that using random_get_pseudo_bytes()
* to generate words larger than 128 bits will paradoxically be limited to * to generate words larger than 256 bits will paradoxically be limited to
* `2^128 - 1` possibilities. This is because we have a sequence of `2^128 - 1` * `2^256 - 1` possibilities. This is because we have a sequence of `2^256 - 1`
* 128-bit words and selecting the first will implicitly select the second. If * 256-bit words and selecting the first will implicitly select the second. If
* a caller finds this behavior undesirable, random_get_bytes() should be used * a caller finds this behavior undesirable, random_get_bytes() should be used
* instead. * instead.
* *
* XXX: Linux interrupt handlers that trigger within the critical section * XXX: Linux interrupt handlers that trigger within the critical section
* formed by `s[1] = xp[1];` and `xp[0] = s[0];` and call this function will * formed by `s[3] = xp[3];` and `xp[0] = s[0];` and call this function will
* see the same numbers. Nothing in the code currently calls this in an * see the same numbers. Nothing in the code currently calls this in an
* interrupt handler, so this is considered to be okay. If that becomes a * interrupt handler, so this is considered to be okay. If that becomes a
* problem, we could create a set of per-cpu variables for interrupt handlers * problem, we could create a set of per-cpu variables for interrupt handlers
@ -93,49 +94,68 @@ EXPORT_SYMBOL(p0);
static void __percpu *spl_pseudo_entropy; static void __percpu *spl_pseudo_entropy;
/* /*
* spl_rand_next()/spl_rand_jump() are copied from the following CC-0 licensed * rotl()/spl_rand_next()/spl_rand_jump() are copied from the following CC-0
* file: * licensed file:
* *
* http://xorshift.di.unimi.it/xorshift128plus.c * https://prng.di.unimi.it/xoshiro256plusplus.c
*/ */
static inline uint64_t rotl(const uint64_t x, int k)
{
return ((x << k) | (x >> (64 - k)));
}
static inline uint64_t static inline uint64_t
spl_rand_next(uint64_t *s) spl_rand_next(uint64_t *s)
{ {
uint64_t s1 = s[0]; const uint64_t result = rotl(s[0] + s[3], 23) + s[0];
const uint64_t s0 = s[1];
s[0] = s0; const uint64_t t = s[1] << 17;
s1 ^= s1 << 23; // a
s[1] = s1 ^ s0 ^ (s1 >> 18) ^ (s0 >> 5); // b, c s[2] ^= s[0];
return (s[1] + s0); s[3] ^= s[1];
s[1] ^= s[2];
s[0] ^= s[3];
s[2] ^= t;
s[3] = rotl(s[3], 45);
return (result);
} }
static inline void static inline void
spl_rand_jump(uint64_t *s) spl_rand_jump(uint64_t *s)
{ {
static const uint64_t JUMP[] = static const uint64_t JUMP[] = { 0x180ec6d33cfd0aba,
{ 0x8a5cd789635d2dff, 0x121fd2155c472f96 }; 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c };
uint64_t s0 = 0; uint64_t s0 = 0;
uint64_t s1 = 0; uint64_t s1 = 0;
uint64_t s2 = 0;
uint64_t s3 = 0;
int i, b; int i, b;
for (i = 0; i < sizeof (JUMP) / sizeof (*JUMP); i++) for (i = 0; i < sizeof (JUMP) / sizeof (*JUMP); i++)
for (b = 0; b < 64; b++) { for (b = 0; b < 64; b++) {
if (JUMP[i] & 1ULL << b) { if (JUMP[i] & 1ULL << b) {
s0 ^= s[0]; s0 ^= s[0];
s1 ^= s[1]; s1 ^= s[1];
s2 ^= s[2];
s3 ^= s[3];
} }
(void) spl_rand_next(s); (void) spl_rand_next(s);
} }
s[0] = s0; s[0] = s0;
s[1] = s1; s[1] = s1;
s[2] = s2;
s[3] = s3;
} }
int int
random_get_pseudo_bytes(uint8_t *ptr, size_t len) random_get_pseudo_bytes(uint8_t *ptr, size_t len)
{ {
uint64_t *xp, s[2]; uint64_t *xp, s[4];
ASSERT(ptr); ASSERT(ptr);
@ -143,6 +163,8 @@ random_get_pseudo_bytes(uint8_t *ptr, size_t len)
s[0] = xp[0]; s[0] = xp[0];
s[1] = xp[1]; s[1] = xp[1];
s[2] = xp[2];
s[3] = xp[3];
while (len) { while (len) {
union { union {
@ -154,12 +176,22 @@ random_get_pseudo_bytes(uint8_t *ptr, size_t len)
len -= i; len -= i;
entropy.ui64 = spl_rand_next(s); entropy.ui64 = spl_rand_next(s);
/*
* xoshiro256++ has low entropy lower bytes, so we copy the
* higher order bytes first.
*/
while (i--) while (i--)
#ifdef _ZFS_BIG_ENDIAN
*ptr++ = entropy.byte[i]; *ptr++ = entropy.byte[i];
#else
*ptr++ = entropy.byte[7 - i];
#endif
} }
xp[0] = s[0]; xp[0] = s[0];
xp[1] = s[1]; xp[1] = s[1];
xp[2] = s[2];
xp[3] = s[3];
put_cpu_ptr(spl_pseudo_entropy); put_cpu_ptr(spl_pseudo_entropy);
@ -765,10 +797,10 @@ spl_kvmem_init(void)
static int __init static int __init
spl_random_init(void) spl_random_init(void)
{ {
uint64_t s[2]; uint64_t s[4];
int i = 0; int i = 0;
spl_pseudo_entropy = __alloc_percpu(2 * sizeof (uint64_t), spl_pseudo_entropy = __alloc_percpu(4 * sizeof (uint64_t),
sizeof (uint64_t)); sizeof (uint64_t));
if (!spl_pseudo_entropy) if (!spl_pseudo_entropy)
@ -776,17 +808,19 @@ spl_random_init(void)
get_random_bytes(s, sizeof (s)); get_random_bytes(s, sizeof (s));
if (s[0] == 0 && s[1] == 0) { if (s[0] == 0 && s[1] == 0 && s[2] == 0 && s[3] == 0) {
if (jiffies != 0) { if (jiffies != 0) {
s[0] = jiffies; s[0] = jiffies;
s[1] = ~0 - jiffies; s[1] = ~0 - jiffies;
s[2] = ~jiffies;
s[3] = jiffies - ~0;
} else { } else {
(void) memcpy(s, "improbable seed", sizeof (s)); (void) memcpy(s, "improbable seed", 16);
} }
printk("SPL: get_random_bytes() returned 0 " printk("SPL: get_random_bytes() returned 0 "
"when generating random seed. Setting initial seed to " "when generating random seed. Setting initial seed to "
"0x%016llx%016llx.\n", cpu_to_be64(s[0]), "0x%016llx%016llx%016llx%016llx.\n", cpu_to_be64(s[0]),
cpu_to_be64(s[1])); cpu_to_be64(s[1]), cpu_to_be64(s[2]), cpu_to_be64(s[3]));
} }
for_each_possible_cpu(i) { for_each_possible_cpu(i) {
@ -796,6 +830,8 @@ spl_random_init(void)
wordp[0] = s[0]; wordp[0] = s[0];
wordp[1] = s[1]; wordp[1] = s[1];
wordp[2] = s[2];
wordp[3] = s[3];
} }
return (0); return (0);