Bug 1402282 - Move CSPRNG logic to common area r=froydnj
authorChris Martin <cmartin@mozilla.com>
Thu, 25 Oct 2018 18:00:15 +0000
changeset 502295 f3d23a7bcbb6e30a5d7368882fdf1b04498d9bdf
parent 502284 d0b577458d53f59ecabc3556ce04a27a8e8eb6d0
child 502296 11819394462eb9695db2fb3021c1dec220e52c8f
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1402282
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1402282 - Move CSPRNG logic to common area r=froydnj The logic in JSMath for generating cryptographically-secure pseudorandom numbers without NSS is independently useful, and so it's been moved to a common area. It will eventually be used for generated random arena ids. Differential Revision: https://phabricator.services.mozilla.com/D8597
js/src/jsmath.cpp
mfbt/RandomNum.cpp
mfbt/RandomNum.h
mfbt/moz.build
mfbt/tests/TestRandomNum.cpp
mfbt/tests/moz.build
testing/cppunittest.ini
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -8,117 +8,51 @@
  * JS math package.
  */
 
 #include "jsmath.h"
 
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/RandomNum.h"
 #include "mozilla/Unused.h"
 #include "mozilla/WrappingOperations.h"
 
 #include <cmath>
-#include <fcntl.h>
-#ifdef XP_UNIX
-# include <unistd.h>
-#endif
 
 #include "fdlibm.h"
 #include "jsapi.h"
 #include "jstypes.h"
 
 #include "jit/InlinableNatives.h"
 #include "js/Class.h"
 #include "util/Windows.h"
 #include "vm/JSAtom.h"
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 #include "vm/Time.h"
 
 #include "vm/JSObject-inl.h"
 
-#if defined(XP_WIN)
-// #define needed to link in RtlGenRandom(), a.k.a. SystemFunction036.  See the
-// "Community Additions" comment on MSDN here:
-// https://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx
-# define SystemFunction036 NTAPI SystemFunction036
-# include <ntsecapi.h>
-# undef SystemFunction036
-#endif
-
-#if defined(ANDROID) || defined(XP_DARWIN) || defined(__DragonFly__) || \
-    defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
-# include <stdlib.h>
-# define HAVE_ARC4RANDOM
-#endif
-
-#if defined(__linux__)
-# include <linux/random.h> // For GRND_NONBLOCK.
-# include <sys/syscall.h> // For SYS_getrandom.
-
-// Older glibc versions don't define SYS_getrandom, so we define it here if
-// it's not available. See bug 995069.
-# if defined(__x86_64__)
-#  define GETRANDOM_NR 318
-# elif defined(__i386__)
-#  define GETRANDOM_NR 355
-# elif defined(__aarch64__)
-#  define GETRANDOM_NR 278
-# elif defined(__arm__)
-#  define GETRANDOM_NR 384
-# elif defined(__powerpc__)
-#  define GETRANDOM_NR 359
-# elif defined(__s390__)
-#  define GETRANDOM_NR 349
-# elif defined(__mips__)
-#  include <sgidefs.h>
-#  if _MIPS_SIM == _MIPS_SIM_ABI32
-#    define GETRANDOM_NR 4353
-#  elif _MIPS_SIM == _MIPS_SIM_ABI64
-#    define GETRANDOM_NR 5313
-#  elif _MIPS_SIM == _MIPS_SIM_NABI32
-#    define GETRANDOM_NR 6317
-#  endif
-# endif
-
-# if defined(SYS_getrandom)
-// We have SYS_getrandom. Use it to check GETRANDOM_NR. Only do this if we set
-// GETRANDOM_NR so tier 3 platforms with recent glibc are not forced to define
-// it for no good reason.
-#  if defined(GETRANDOM_NR)
-static_assert(GETRANDOM_NR == SYS_getrandom,
-              "GETRANDOM_NR should match the actual SYS_getrandom value");
-#  endif
-# else
-#  define SYS_getrandom GETRANDOM_NR
-# endif
-
-# if defined(GRND_NONBLOCK)
-static_assert(GRND_NONBLOCK == 1, "If GRND_NONBLOCK is not 1 the #define below is wrong");
-# else
-#  define GRND_NONBLOCK 1
-# endif
-
-#endif // defined(__linux__)
-
 using namespace js;
 
 using mozilla::Abs;
-using mozilla::NumberEqualsInt32;
-using mozilla::NumberIsInt32;
 using mozilla::ExponentComponent;
 using mozilla::FloatingPoint;
 using mozilla::IsFinite;
 using mozilla::IsInfinite;
 using mozilla::IsNaN;
 using mozilla::IsNegative;
 using mozilla::IsNegativeZero;
+using mozilla::Maybe;
+using mozilla::NegativeInfinity;
+using mozilla::NumberEqualsInt32;
+using mozilla::NumberIsInt32;
 using mozilla::PositiveInfinity;
-using mozilla::NegativeInfinity;
 using mozilla::WrappingMultiply;
 using JS::ToNumber;
 using JS::GenericNaN;
 
 static const JSConstDoubleSpec math_constants[] = {
     // clang-format off
     {"E"      ,  M_E       },
     {"LOG2E"  ,  M_LOG2E   },
@@ -629,45 +563,23 @@ js::math_pow(JSContext* cx, unsigned arg
     double z = ecmaPow(x, y);
     args.rval().setNumber(z);
     return true;
 }
 
 uint64_t
 js::GenerateRandomSeed()
 {
-    uint64_t seed = 0;
-
-#if defined(XP_WIN)
-    MOZ_ALWAYS_TRUE(RtlGenRandom(&seed, sizeof(seed)));
-#elif defined(HAVE_ARC4RANDOM)
-    seed = (static_cast<uint64_t>(arc4random()) << 32) | arc4random();
-#elif defined(XP_UNIX)
-    bool done = false;
-# if defined(__linux__)
-    // Try the relatively new getrandom syscall first. It's the preferred way
-    // on Linux as /dev/urandom may not work inside chroots and is harder to
-    // sandbox (see bug 995069).
-    int ret = syscall(SYS_getrandom, &seed, sizeof(seed), GRND_NONBLOCK);
-    done = (ret == sizeof(seed));
-# endif
-    if (!done) {
-        int fd = open("/dev/urandom", O_RDONLY);
-        if (fd >= 0) {
-            mozilla::Unused << read(fd, static_cast<void*>(&seed), sizeof(seed));
-            close(fd);
-        }
-    }
-#else
-# error "Platform needs to implement GenerateRandomSeed()"
-#endif
-
-    // Also mix in PRMJ_Now() in case we couldn't read random bits from the OS.
-    uint64_t timestamp = PRMJ_Now();
-    return seed ^ timestamp ^ (timestamp << 32);
+    Maybe<uint64_t> maybeSeed = mozilla::RandomUint64();
+    
+    return maybeSeed.valueOrFrom([] {
+        // Use PRMJ_Now() in case we couldn't read random bits from the OS.
+        uint64_t timestamp = PRMJ_Now();
+        return timestamp ^ (timestamp << 32);
+    });
 }
 
 void
 js::GenerateXorShift128PlusSeed(mozilla::Array<uint64_t, 2>& seed)
 {
     // XorShift128PlusRNG must be initialized with a non-zero seed.
     do {
         seed[0] = GenerateRandomSeed();
new file mode 100644
--- /dev/null
+++ b/mfbt/RandomNum.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/RandomNum.h"
+
+#include <fcntl.h>
+#ifdef XP_UNIX
+# include <unistd.h>
+#endif
+
+#if defined(XP_WIN)
+
+// Microsoft doesn't "officially" support using RtlGenRandom() directly 
+// anymore, and the Windows headers assume that __stdcall is 
+// the default calling convention (which is true when Microsoft uses this
+// function to build their own CRT libraries).
+
+// We will explicitly declare it with the proper calling convention.
+
+#include "minwindef.h"
+#define RtlGenRandom SystemFunction036
+extern "C" BOOLEAN NTAPI
+RtlGenRandom(
+  PVOID RandomBuffer,
+  ULONG RandomBufferLength
+  );
+
+#endif
+
+#if defined(ANDROID) || defined(XP_DARWIN) || defined(__DragonFly__) || \
+    defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# include <stdlib.h>
+# define USE_ARC4RANDOM
+#endif
+
+#if defined(__linux__)
+# include <linux/random.h> // For GRND_NONBLOCK.
+# include <sys/syscall.h> // For SYS_getrandom.
+
+// Older glibc versions don't define SYS_getrandom, so we define it here if
+// it's not available. See bug 995069.
+# if defined(__x86_64__)
+#  define GETRANDOM_NR 318
+# elif defined(__i386__)
+#  define GETRANDOM_NR 355
+# elif defined(__aarch64__)
+#  define GETRANDOM_NR 278
+# elif defined(__arm__)
+#  define GETRANDOM_NR 384
+# elif defined(__powerpc__)
+#  define GETRANDOM_NR 359
+# elif defined(__s390__)
+#  define GETRANDOM_NR 349
+# elif defined(__mips__)
+#  include <sgidefs.h>
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    define GETRANDOM_NR 4353
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define GETRANDOM_NR 5313
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define GETRANDOM_NR 6317
+#  endif
+# endif
+
+# if defined(SYS_getrandom)
+// We have SYS_getrandom. Use it to check GETRANDOM_NR. Only do this if we set
+// GETRANDOM_NR so tier 3 platforms with recent glibc are not forced to define
+// it for no good reason.
+#  if defined(GETRANDOM_NR)
+static_assert(GETRANDOM_NR == SYS_getrandom,
+              "GETRANDOM_NR should match the actual SYS_getrandom value");
+#  endif
+# else
+#  define SYS_getrandom GETRANDOM_NR
+# endif
+
+# if defined(GRND_NONBLOCK)
+static_assert(GRND_NONBLOCK == 1, 
+  "If GRND_NONBLOCK is not 1 the #define below is wrong");
+# else
+#  define GRND_NONBLOCK 1
+# endif
+
+#endif // defined(__linux__)
+
+namespace mozilla {
+
+/* 
+ * Note - Bug 1500115 has been opened to discuss simplifying or improving
+ * this function in the future; however, the function is secure as-is right
+ * now. Further improvements may be made to reduce complexity, improve
+ * robustness, or take advantage of OS-specific API improvements as they
+ * become available.
+ * 
+ */
+
+MFBT_API Maybe<uint64_t>
+RandomUint64()
+{  
+#if defined(XP_WIN)
+  
+  uint64_t result = 0;
+  if (!RtlGenRandom(&result, sizeof(result))) {
+    return Nothing();
+  }
+  
+  return Some(result);
+
+#elif defined(USE_ARC4RANDOM) // defined(XP_WIN)
+  
+  return Some((static_cast<uint64_t>(arc4random()) << 32) | arc4random());
+    
+#elif defined(XP_UNIX) // defined(USE_ARC4RANDOM)
+  
+  uint64_t result = 0;
+    
+# if defined(__linux__)
+
+  long bytesGenerated = syscall(
+    SYS_getrandom, &result, sizeof(result), GRND_NONBLOCK);
+  
+  if ((bytesGenerated > 0) &&
+      (static_cast<unsigned long>(bytesGenerated) == sizeof(result))) 
+  {  
+    return Some(result);
+  }
+  
+  // Fall-through to UNIX behavior if failed
+      
+# endif // defined(__linux__)
+  
+  int fd = open("/dev/urandom", O_RDONLY);
+  if (fd < 0) {
+    return Nothing();
+  }
+  
+  ssize_t bytesRead = read(fd, &result, sizeof(result));
+  
+  close(fd);
+  
+  if (bytesRead < 0) {
+    return Nothing();
+  }
+  
+  if (static_cast<size_t>(bytesRead) != sizeof(result)) {
+    return Nothing();
+  }
+  
+  return Some(result);
+  
+#else // defined(XP_UNIX)
+# error "Platform needs to implement RandomUint64()"
+#endif
+}
+
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/mfbt/RandomNum.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/* Routines for generating random numbers */
+
+#ifndef mozilla_RandomNum_h_
+#define mozilla_RandomNum_h_
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+
+/**
+ *  Generate a cryptographically secure random 64-bit unsigned number using the
+ *  best facilities available on the current OS.
+ *  
+ *  Useful whenever a secure random number is needed and NSS isn't available.
+ *  (Perhaps because it hasn't been initialized yet)
+ *
+ *  Current mechanisms:
+ *    Windows: RtlGenRandom()
+ *    Android, Darwin, DragonFly, FreeBSD, OpenBSD, NetBSD: arc4random()
+ *    Linux: getrandom() if available, "/dev/urandom" otherwise
+ *    Other Unix: "/dev/urandom"
+ *
+ */
+MFBT_API Maybe<uint64_t>
+RandomUint64();
+
+} // namespace mozilla
+
+#endif // mozilla_RandomNum_h_
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -64,16 +64,17 @@ EXPORTS.mozilla = [
     'NotNull.h',
     'NullPtr.h',
     'Opaque.h',
     'OperatorNewExtensions.h',
     'Pair.h',
     'Path.h',
     'PodOperations.h',
     'Poison.h',
+    'RandomNum.h',
     'Range.h',
     'RangedArray.h',
     'RangedPtr.h',
     'RecordReplay.h',
     'ReentrancyGuard.h',
     'RefCounted.h',
     'RefCountType.h',
     'RefPtr.h',
@@ -142,16 +143,17 @@ UNIFIED_SOURCES += [
     'double-conversion/double-conversion/double-conversion.cc',
     'double-conversion/double-conversion/fast-dtoa.cc',
     'double-conversion/double-conversion/fixed-dtoa.cc',
     'double-conversion/double-conversion/strtod.cc',
     'FloatingPoint.cpp',
     'HashFunctions.cpp',
     'JSONWriter.cpp',
     'Poison.cpp',
+    'RandomNum.cpp',
     'RecordReplay.cpp',
     'SHA1.cpp',
     'TaggedAnonymousMemory.cpp',
     'Unused.cpp',
     'Utf8.cpp',
 ]
 
 DEFINES['IMPL_MFBT'] = True
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestRandomNum.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/RandomNum.h"
+
+/*
+ 
+ * We're going to check that random number generation is sane on a basic
+ * level - That is, we want to check that the function returns success
+ * and doesn't just keep returning the same number.
+ *
+ * Note that there are many more tests that could be done, but to really test
+ * a PRNG we'd probably need to generate a large set of randoms and
+ * perform statistical analysis on them. Maybe that's worth doing eventually?
+ *
+ * For now we should be fine just performing a dumb test of generating 5 
+ * numbers and making sure they're all unique. In theory, it is possible for 
+ * this test to report a false negative, but with 5 numbers the probability 
+ * is less than one-in-a-trillion.
+ *
+ */
+
+#define NUM_RANDOMS_TO_GENERATE 5
+
+using mozilla::Maybe;
+using mozilla::RandomUint64;
+
+static uint64_t getRandomUint64OrDie()
+{
+  Maybe<uint64_t> maybeRandomNum = RandomUint64();
+    
+  MOZ_RELEASE_ASSERT(maybeRandomNum.isSome());
+  
+  return maybeRandomNum.value();
+}
+
+static void TestRandomUint64()
+{ 
+  uint64_t randomsList[NUM_RANDOMS_TO_GENERATE];
+  
+  for(uint8_t i = 0; i < NUM_RANDOMS_TO_GENERATE; ++i) {
+    uint64_t randomNum = getRandomUint64OrDie();
+    
+    for(uint8_t j = 0; j < i; ++j) {
+      MOZ_RELEASE_ASSERT(randomNum != randomsList[j]);
+    }
+    
+    randomsList[i] = randomNum;
+  }
+}
+
+int
+main()
+{
+  TestRandomUint64();
+  return 0;
+}
\ No newline at end of file
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -39,16 +39,17 @@ CppUnitTests([
     'TestLinkedList',
     'TestMacroArgs',
     'TestMacroForEach',
     'TestMathAlgorithms',
     'TestMaybe',
     'TestNonDereferenceable',
     'TestNotNull',
     'TestPair',
+    'TestRandomNum',
     'TestRange',
     'TestRefPtr',
     'TestResult',
     'TestRollingMean',
     'TestSaturate',
     'TestScopeExit',
     'TestSegmentedVector',
     'TestSHA1',
--- a/testing/cppunittest.ini
+++ b/testing/cppunittest.ini
@@ -35,16 +35,17 @@ skip-if = os != 'win'
 [TestNonDereferenceable]
 [TestNotNull]
 [TestParseFTPList]
 [TestPLDHash]
 [TestPair]
 [TestPoisonArea]
 skip-if = os == 'android' # Bug 1147630
 [TestRange]
+[TestRandomNum]
 [TestRefPtr]
 [TestResult]
 [TestRollingMean]
 [TestScopeExit]
 [TestSegmentedVector]
 [TestSHA1]
 [TestSmallPointerArray]
 [TestSaturate]