[Support,Windows] Tolerate failure of CryptGenRandom
authorSimon Tatham <simon.tatham@arm.com>
Tue, 7 Apr 2020 08:18:09 +0000 (09:18 +0100)
committerSimon Tatham <simon.tatham@arm.com>
Tue, 7 Apr 2020 08:18:12 +0000 (09:18 +0100)
Summary:
In `Unix/Process.inc`, we seed a random number generator from
`/dev/urandom` if possible, but if not, we're happy to fall back to
ordinary pseudorandom strategies, like the current time and PID.

The corresponding function on Windows calls `CryptGenRandom`, but it
//doesn't// have a fallback if that strategy fails. But `CryptGenRandom`
//can// fail, if a cryptography provider isn't properly initialized, or
occasionally (by our observation) simply intermittently.

If it's reasonable on Unix to implement traditional pseudorandom-number
seeding as a fallback, then it's surely reasonable to do the same on
Windows. So this patch adds a last-ditch use of ordinary rand(), using
much the same strategy as the Unix fallback code.

Reviewers: hans, sammccall

Reviewed By: hans

Subscribers: hiraditya, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D77553

llvm/lib/Support/Windows/Process.inc

index 518ecdb..6eb4a5e 100644 (file)
@@ -439,18 +439,38 @@ const char *Process::ResetColor() {
   return 0;
 }
 
+static unsigned GetRandomNumberSeed() {
+  // Generate a random number seed from the millisecond-resolution Windows
+  // system clock and the current process id.
+  FILETIME Time;
+  GetSystemTimeAsFileTime(&Time);
+  DWORD Pid = GetCurrentProcessId();
+  return hash_combine(Time.dwHighDateTime, Time.dwLowDateTime, Pid);
+}
+
+static unsigned GetPseudoRandomNumber() {
+  // Arrange to call srand once when this function is first used, and
+  // otherwise (if GetRandomNumber always succeeds in using
+  // CryptGenRandom) don't bother at all.
+  static int x = (static_cast<void>(::srand(GetRandomNumberSeed())), 0);
+  (void)x;
+  return ::rand();
+}
+
 unsigned Process::GetRandomNumber() {
+  // Try to use CryptGenRandom.
   HCRYPTPROV HCPC;
-  if (!::CryptAcquireContextW(&HCPC, NULL, NULL, PROV_RSA_FULL,
-                              CRYPT_VERIFYCONTEXT))
-    ReportLastErrorFatal("Could not acquire a cryptographic context");
-
-  ScopedCryptContext CryptoProvider(HCPC);
-  unsigned Ret;
-  if (!::CryptGenRandom(CryptoProvider, sizeof(Ret),
-                        reinterpret_cast<BYTE *>(&Ret)))
-    ReportLastErrorFatal("Could not generate a random number");
-  return Ret;
+  if (::CryptAcquireContextW(&HCPC, NULL, NULL, PROV_RSA_FULL,
+                             CRYPT_VERIFYCONTEXT)) {
+    ScopedCryptContext CryptoProvider(HCPC);
+    unsigned Ret;
+    if (::CryptGenRandom(CryptoProvider, sizeof(Ret),
+                         reinterpret_cast<BYTE *>(&Ret)))
+      return Ret;
+  }
+
+  // If that fails, fall back to pseudo-random numbers.
+  return GetPseudoRandomNumber();
 }
 
 typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);