diff mbox

[libgfortran] Improve PRNG seed scrambling

Message ID CAO9iq9GRRH2DwrtZdXT4rMR5viXVeM-atOziKf-1sZp=as=TXg@mail.gmail.com
State New
Headers show

Commit Message

Janne Blomqvist Aug. 16, 2016, 9:30 a.m. UTC
Hi,

the attached patch improves the scrambling of the seed given by the
RANDOM_SEED intrinsic. Previously we shuffled the bytes in the seed
back and forth, the new implementation replaces this with a "XOR
cipher" (https://en.wikipedia.org/wiki/XOR_cipher). Also, now the
scrambling is also done for the random_seed_i8 variant, previously
only the i4 variant was scrambled.

Similarly to the previous implementation, this is of course not a
defence against somebody who is determined to generate a poor seed.
But it's a protection from naive users setting all seed values to 0 or
to some small constant value, or similar low entropy seeds.

Since this is bordering on obvious, I'm planning to commit this in a
few days unless somebody objects. Regtested on x86_64-pc-linux-gnu.

libgfortran:
2016-08-16  Janne Blomqvist  <jb@gcc.gnu.org>

    * intrinsics/random.c (xor_keys): New array with "secret" keys.
    (scramble_seed): XOR given seed with xor_keys array rather than
    shuffling bytes.
    (unscramble_seed): Remove function.
    (random_seed_i4): Use new scramble_seed.
    (random_seed_i8): Likewise.

frontend:
2016-08-16  Janne Blomqvist  <jb@gcc.gnu.org>

    * intrinsics.texi (RANDOM_NUMBER): Remove reference to
    init_random_seed in example.
    (RANDOM_SEED): Remove warning to not set all seed values to 0.
diff mbox

Patch

diff --git a/gcc/fortran/intrinsic.texi b/gcc/fortran/intrinsic.texi
index 67b0231..8cca9b1 100644
--- a/gcc/fortran/intrinsic.texi
+++ b/gcc/fortran/intrinsic.texi
@@ -11155,7 +11155,6 @@  Subroutine
 @smallexample
 program test_random_number
   REAL :: r(5,5)
-  CALL init_random_seed()         ! see example of RANDOM_SEED
   CALL RANDOM_NUMBER(r)
 end program
 @end smallexample
@@ -11192,9 +11191,6 @@  alias any other stream in the system, where @var{N} is the number of
 threads that have used @code{RANDOM_NUMBER} so far during the program
 execution.
 
-Note that setting any of the seed values to zero should be avoided as
-it can result in poor quality random numbers being generated.
-
 @item @emph{Standard}:
 Fortran 95 and later
 
diff --git a/libgfortran/intrinsics/random.c b/libgfortran/intrinsics/random.c
index 3b91389..ddfbbd4 100644
--- a/libgfortran/intrinsics/random.c
+++ b/libgfortran/intrinsics/random.c
@@ -715,28 +715,33 @@  arandom_r16 (gfc_array_r16 *x)
 #endif
 
 
+/* Number of elements in master_state array.  */
+#define SZU64 (sizeof (master_state) / sizeof (uint64_t))
 
-static void
-scramble_seed (unsigned char *dest, unsigned char *src, int size)
-{
-  int i;
 
-  for (i = 0; i < size; i++)
-    dest[(i % 2) * (size / 2) + i / 2] = src[i];
-}
+/* Keys for scrambling the seed in order to avoid poor seeds.  */
 
+static const uint64_t xor_keys[] = {
+  0xbd0c5b6e50c2df49ULL, 0xd46061cd46e1df38ULL, 0xbb4f4d4ed6103544ULL,
+  0x114a583d0756ad39ULL, 0x4b5ad8623d0aaab6ULL, 0x3f2ed7afbe0c0f21ULL,
+  0xdec83fd65f113445ULL, 0x3824f8fbc4f10d24ULL, 0x5d9025af05878911ULL,
+  0x500bc46b540340e9ULL, 0x8bd53298e0d00530ULL, 0x57886e40a952e06aULL,
+  0x926e76c88e31cdb6ULL, 0xbd0724dac0a3a5f9ULL, 0xc5c8981b858ab796ULL,
+  0xbb12ab2694c2b32cULL
+};
+
+
+/* Since a XOR cipher is symmetric, we need only one routine, and we
+   can use it both for encryption and decryption.  */
 
 static void
-unscramble_seed (unsigned char *dest, unsigned char *src, int size)
+scramble_seed (uint64_t *dest, const uint64_t *src)
 {
-  int i;
-
-  for (i = 0; i < size; i++)
-    dest[i] = src[(i % 2) * (size / 2) + i / 2];
+  for (int i = 0; i < (int) SZU64; i++)
+    dest[i] = src[i] ^ xor_keys[i];
 }
 
 
-
 /* random_seed is used to seed the PRNG with either a default
    set of seeds or user specified set of seeds.  random_seed
    must be called with no argument or exactly one argument.  */
@@ -744,7 +749,7 @@  unscramble_seed (unsigned char *dest, unsigned char *src, int size)
 void
 random_seed_i4 (GFC_INTEGER_4 *size, gfc_array_i4 *put, gfc_array_i4 *get)
 {
-  unsigned char seed[sizeof (master_state)];
+  uint64_t seed[SZU64];
 #define SZ (sizeof (master_state) / sizeof (GFC_INTEGER_4))
 
   /* Check that we only have one argument present.  */
@@ -771,12 +776,12 @@  random_seed_i4 (GFC_INTEGER_4 *size, gfc_array_i4 *put, gfc_array_i4 *get)
 	init_rand_state (rs, false);
 
       /* Unscramble the seed.  */
-      unscramble_seed (seed, (unsigned char *) rs->s, sizeof seed);
+      scramble_seed (seed, rs->s);
 
       /*  Then copy it back to the user variable.  */
       for (size_t i = 0; i < SZ ; i++)
 	memcpy (&(get->base_addr[(SZ - 1 - i) * GFC_DESCRIPTOR_STRIDE(get,0)]),
-               seed + i * sizeof(GFC_UINTEGER_4),
+		(unsigned char*) seed + i * sizeof(GFC_UINTEGER_4),
                sizeof(GFC_UINTEGER_4));
 
       /* Finally copy the value of p after the seed.  */
@@ -808,21 +813,19 @@  random_seed_i4 (GFC_INTEGER_4 *size, gfc_array_i4 *put, gfc_array_i4 *get)
 
       /*  We copy the seed given by the user.  */
       for (size_t i = 0; i < SZ; i++)
-	memcpy (seed + i * sizeof(GFC_UINTEGER_4),
+	memcpy ((unsigned char*) seed + i * sizeof(GFC_UINTEGER_4),
 		&(put->base_addr[(SZ - 1 - i) * GFC_DESCRIPTOR_STRIDE(put,0)]),
 		sizeof(GFC_UINTEGER_4));
 
       /* We put it after scrambling the bytes, to paper around users who
 	 provide seeds with quality only in the lower or upper part.  */
-      scramble_seed ((unsigned char *) master_state, seed, sizeof seed);
+      scramble_seed (master_state, seed);
       njumps = 0;
       init_rand_state (rs, true);
 
       rs->p = put->base_addr[SZ * GFC_DESCRIPTOR_STRIDE(put, 0)] & 15;
     }
 
-
-
   __gthread_mutex_unlock (&random_lock);
     }
 #undef SZ
@@ -833,6 +836,8 @@  iexport(random_seed_i4);
 void
 random_seed_i8 (GFC_INTEGER_8 *size, gfc_array_i8 *put, gfc_array_i8 *get)
 {
+  uint64_t seed[SZU64];
+
   /* Check that we only have one argument present.  */
   if ((size ? 1 : 0) + (put ? 1 : 0) + (get ? 1 : 0) > 1)
     runtime_error ("RANDOM_SEED should have at most one argument present.");
@@ -857,9 +862,12 @@  random_seed_i8 (GFC_INTEGER_8 *size, gfc_array_i8 *put, gfc_array_i8 *get)
       if (!rs->init)
 	init_rand_state (rs, false);
 
+      /* Unscramble the seed.  */
+      scramble_seed (seed, rs->s);
+
       /*  This code now should do correct strides.  */
       for (size_t i = 0; i < SZ; i++)
-	memcpy (&(get->base_addr[i * GFC_DESCRIPTOR_STRIDE(get,0)]), &rs->s[i],
+	memcpy (&(get->base_addr[i * GFC_DESCRIPTOR_STRIDE(get,0)]), &seed[i],
 		sizeof (GFC_UINTEGER_8));
 
       get->base_addr[SZ * GFC_DESCRIPTOR_STRIDE(get, 0)] = rs->p;
@@ -890,9 +898,10 @@  random_seed_i8 (GFC_INTEGER_8 *size, gfc_array_i8 *put, gfc_array_i8 *get)
 
       /*  This code now should do correct strides.  */
       for (size_t i = 0; i < SZ; i++)
-	memcpy (&master_state[i], &(put->base_addr[i * GFC_DESCRIPTOR_STRIDE(put,0)]),
+	memcpy (&seed[i], &(put->base_addr[i * GFC_DESCRIPTOR_STRIDE(put,0)]),
 		sizeof (GFC_UINTEGER_8));
 
+      scramble_seed (master_state, seed);
       njumps = 0;
       init_rand_state (rs, true);
       rs->p = put->base_addr[SZ * GFC_DESCRIPTOR_STRIDE(put, 0)] & 15;