From bfaf31ce2c7f2d9c3241ec4550889dab9763f2f9 Mon Sep 17 00:00:00 2001 From: Nathan Sashihara <21227491+n8sh@users.noreply.github.com> Date: Thu, 1 Mar 2018 02:34:06 -0500 Subject: [PATCH] Fix Issue 18596: unpredictableSeed could use something better than MinstdRand0 Use arc4random when available, otherwise replace MinstdRand0 with xorshift64*/32. --- std/random.d | 104 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/std/random.d b/std/random.d index c0cf7e13b43..676963f0a0c 100644 --- a/std/random.d +++ b/std/random.d @@ -1290,6 +1290,74 @@ alias Xorshift = Xorshift128; /// ditto } } +version (CRuntime_Bionic) + version = SecureARC4Random; // ChaCha20 +version (OSX) + version = SecureARC4Random; // AES +version (OpenBSD) + version = SecureARC4Random; // ChaCha20 +version (NetBSD) + version = SecureARC4Random; // ChaCha20 + +version (CRuntime_UClibc) + version = LegacyARC4Random; // ARC4 +version (FreeBSD) + version = LegacyARC4Random; // ARC4 +version (DragonFlyBSD) + version = LegacyARC4Random; // ARC4 +version (BSD) + version = LegacyARC4Random; // Unknown implementation + +// For the current purpose of unpredictableSeed the difference between +// a secure arc4random implementation and a legacy implementation is +// unimportant. The source code documents this distinction in case in the +// future Phobos is altered to require cryptographically secure sources +// of randomness, and also so other people reading this source code (as +// Phobos is often looked to as an example of good D programming practices) +// do not mistakenly use insecure versions of arc4random in contexts where +// crypographically secure sources of randomness are needed. + +// Performance note: ChaCha20 is about 70% faster than ARC4, contrary to +// what one might assume from it being more secure. + +version (SecureARC4Random) + version = AnyARC4Random; +version (LegacyARC4Random) + version = AnyARC4Random; + +version (AnyARC4Random) +{ + extern(C) private @nogc nothrow + { + uint arc4random() @safe; + } +} +else +{ + private ulong _seeder; // 0 indicates uninitialized. + + private ulong bootstrapSeed() @nogc nothrow @trusted + { + pragma (inline, false); + ulong result = void; + enum ulong m = 0xc6a4_a793_5bd1_e995UL; // MurmurHash2_64A constant. + void updateResult(ulong x) + { + x *= m; + x = (x ^ (x >>> 47)) * m; + result = (result ^ x) * m; + } + + import core.thread : getpid; + import core.time : MonoTime; + + update_result(cast(ulong) &_seeder); // Distinct for each thread. + update_result(cast(ulong) getpid()); + update_result(cast(ulong) MonoTime.currTime.ticks); + result = (result ^ (result >>> 47)) * m; + return result ^ (result >>> 47); + } +} /** A "good" seed for initializing random number engines. Initializing @@ -1299,19 +1367,35 @@ random number sequences every run. Returns: A single unsigned integer seed value, different on each successive call */ -@property uint unpredictableSeed() @trusted nothrow @nogc +@property uint unpredictableSeed() @nogc nothrow @trusted { - import core.thread : Thread, getpid, MonoTime; - static bool seeded; - static MinstdRand0 rand; - if (!seeded) + version (AnyARC4Random) + { + return arc4random(); + } + else { - uint threadID = cast(uint) cast(void*) Thread.getThis(); - rand.seed((getpid() + threadID) ^ cast(uint) MonoTime.currTime.ticks); - seeded = true; + import core.time : MonoTime; + ulong x = _seeder; + if (x == 0) // 0 means uninitialized (update can never transition non-zero to zero). + x = bootstrapSeed(); + // State is updated with a 64-bit xorshift. Output is produced + // by multiplying state by a constant to eliminate linear artifacts + // except in the low-order bits. This approach appears in "Numerical + // Recipes 3rd edition" (2007). The specific constants used here are + // taken from "An experimental exploration of Marsaglia’s xorshift + // generators, scrambled" (Vigna 2016). + x ^= x >>> 12; + x ^= x << 25; + x ^= x >>> 37; + _seeder = x; + enum ulong mult = 2_685_821_657_736_338_717UL; + // As we don't need all 64 bits use the highest bits + // because they have better statistical properties. + // Finally the result is combined with the clock. + return cast(uint) ((x * mult) >>> 32) + ^ cast(uint) MonoTime.currTime.ticks; } - rand.popFront(); - return cast(uint) (MonoTime.currTime.ticks ^ rand.front); } ///