Harden against fork via MADV_WIPEONFORK.

Linux 4.14 and up support MADV_WIPEONFORK, which can be used to reliably
and efficiently detect forks. Use it to harden the PRNG and RSA blinding
caches.

On the RSA side, we currently cache blinding values. (Alas, removing the cache
gives a *very* noticeable perf hit. There is some low-hanging fruit to trim a
few Montgomery reductions, but it didn't offset much last I toyed with it.)
Now, on Linux 4.14 and up, this cache is fork-safe.

Since not all platforms that support fork also support fork detection,
this should only be used as a hardening measure. Now, when detection is
present, BoringSSL will skip doing per-call entropy draws from the
kernel. (This might regress protection against VM cloning when no fast
RDRAND is available. However, we need to do something for AMD machines.
Hypervisors that clone VMs are going to need to signal the kernel to
wipe WIPEONFORK pages.)

Upgrade-Note: BoringSSL now calls some more syscalls on Linux. If this offends
sandboxes, let us know. We can loosen the sandbox or add a mechanism to prime
the MADV_WIPEONFORK page before entering it.

Change-Id: I6ba43951aeaa2b9b81f74f9e5a7a0ce2de0438a4
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/34745
Reviewed-by: Adam Langley <alangley@gmail.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt
index 20af6e4..68e3496 100644
--- a/crypto/CMakeLists.txt
+++ b/crypto/CMakeLists.txt
@@ -513,6 +513,7 @@
   fipsmodule/ec/ec_test.cc
   fipsmodule/ec/p256-x86_64_test.cc
   fipsmodule/ecdsa/ecdsa_test.cc
+  fipsmodule/fork_detect_test.cc
   fipsmodule/md5/md5_test.cc
   fipsmodule/modes/gcm_test.cc
   fipsmodule/rand/ctrdrbg_test.cc
diff --git a/crypto/fipsmodule/bcm.c b/crypto/fipsmodule/bcm.c
index 4a53c32..bc3d6d1 100644
--- a/crypto/fipsmodule/bcm.c
+++ b/crypto/fipsmodule/bcm.c
@@ -77,6 +77,7 @@
 #include "ec/simple_mul.c"
 #include "ec/util.c"
 #include "ec/wnaf.c"
+#include "fork_detect.c"
 #include "hmac/hmac.c"
 #include "md4/md4.c"
 #include "md5/md5.c"
diff --git a/crypto/fipsmodule/fork_detect.c b/crypto/fipsmodule/fork_detect.c
new file mode 100644
index 0000000..baa6bcf
--- /dev/null
+++ b/crypto/fipsmodule/fork_detect.c
@@ -0,0 +1,131 @@
+/* Copyright (c) 2020, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#if !defined(_GNU_SOURCE)
+#define _GNU_SOURCE  // needed for madvise() and MAP_ANONYMOUS on Linux.
+#endif
+
+#include <openssl/base.h>
+
+#include "fork_detect.h"
+
+#if defined(OPENSSL_LINUX)
+#include <sys/mman.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <openssl/type_check.h>
+
+#include "delocate.h"
+#include "../internal.h"
+
+
+#if defined(MADV_WIPEONFORK)
+OPENSSL_STATIC_ASSERT(MADV_WIPEONFORK == 18, "MADV_WIPEONFORK is not 18");
+#else
+#define MADV_WIPEONFORK 18
+#endif
+
+DEFINE_STATIC_ONCE(g_fork_detect_once);
+DEFINE_STATIC_MUTEX(g_fork_detect_lock);
+DEFINE_BSS_GET(volatile char *, g_fork_detect_addr);
+DEFINE_BSS_GET(uint64_t, g_fork_generation);
+DEFINE_BSS_GET(int, g_ignore_madv_wipeonfork);
+
+static void init_fork_detect(void) {
+  if (*g_ignore_madv_wipeonfork_bss_get()) {
+    return;
+  }
+
+  long page_size = sysconf(_SC_PAGESIZE);
+  if (page_size <= 0) {
+    return;
+  }
+
+  void *addr = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE,
+                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (addr == MAP_FAILED) {
+    return;
+  }
+
+  if (madvise(addr, (size_t)page_size, MADV_WIPEONFORK) != 0) {
+    munmap(addr, (size_t)page_size);
+    return;
+  }
+
+  *((volatile char *) addr) = 1;
+  *g_fork_detect_addr_bss_get() = addr;
+  *g_fork_generation_bss_get() = 1;
+}
+
+uint64_t CRYPTO_get_fork_generation(void) {
+  // In a single-threaded process, there are obviously no races because there's
+  // only a single mutator in the address space.
+  //
+  // In a multi-threaded environment, |CRYPTO_once| ensures that the flag byte
+  // is initialised atomically, even if multiple threads enter this function
+  // concurrently.
+  //
+  // In the limit, the kernel may clear WIPEONFORK pages while a multi-threaded
+  // process is running. (For example, because a VM was cloned.) Therefore a
+  // lock is used below to synchronise the potentially multiple threads that may
+  // concurrently observe the cleared flag.
+
+  CRYPTO_once(g_fork_detect_once_bss_get(), init_fork_detect);
+  // This pointer is |volatile| because the value pointed to may be changed by
+  // external forces (i.e. the kernel wiping the page) thus the compiler must
+  // not assume that it has exclusive access to it.
+  volatile char *const flag_ptr = *g_fork_detect_addr_bss_get();
+  if (flag_ptr == NULL) {
+    // Our kernel is too old to support |MADV_WIPEONFORK|.
+    return 0;
+  }
+
+  struct CRYPTO_STATIC_MUTEX *const lock = g_fork_detect_lock_bss_get();
+  uint64_t *const generation_ptr = g_fork_generation_bss_get();
+
+  CRYPTO_STATIC_MUTEX_lock_read(lock);
+  uint64_t current_generation = *generation_ptr;
+  if (*flag_ptr) {
+    CRYPTO_STATIC_MUTEX_unlock_read(lock);
+    return current_generation;
+  }
+
+  CRYPTO_STATIC_MUTEX_unlock_read(lock);
+  CRYPTO_STATIC_MUTEX_lock_write(lock);
+  current_generation = *generation_ptr;
+  if (*flag_ptr == 0) {
+    // A fork has occurred.
+    *flag_ptr = 1;
+
+    current_generation++;
+    if (current_generation == 0) {
+      current_generation = 1;
+    }
+    *generation_ptr = current_generation;
+  }
+  CRYPTO_STATIC_MUTEX_unlock_write(lock);
+
+  return current_generation;
+}
+
+void CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing(void) {
+  *g_ignore_madv_wipeonfork_bss_get() = 1;
+}
+
+#else   // !OPENSSL_LINUX
+
+uint64_t CRYPTO_get_fork_generation(void) { return 0; }
+
+#endif  // OPENSSL_LINUX
diff --git a/crypto/fipsmodule/fork_detect.h b/crypto/fipsmodule/fork_detect.h
new file mode 100644
index 0000000..8518830
--- /dev/null
+++ b/crypto/fipsmodule/fork_detect.h
@@ -0,0 +1,49 @@
+/* Copyright (c) 2020, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#ifndef OPENSSL_HEADER_CRYPTO_FORK_DETECT_H
+#define OPENSSL_HEADER_CRYPTO_FORK_DETECT_H
+
+#include <openssl/base.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+// crypto_get_fork_generation returns the fork generation number for the current
+// process, or zero if not supported on the platform. The fork generation number
+// is a non-zero, strictly-monotonic counter with the property that, if queried
+// in an address space and then again in a subsequently forked copy, the forked
+// address space will observe a greater value.
+//
+// This function may be used to clear cached values across a fork. When
+// initializing a cache, record the fork generation. Before using the cache,
+// check if the fork generation has changed. If so, drop the cache and update
+// the save fork generation. Note this logic transparently handles platforms
+// which always return zero.
+//
+// This is not reliably supported on all platforms which implement |fork|, so it
+// should only be used as a hardening measure.
+OPENSSL_EXPORT uint64_t CRYPTO_get_fork_generation(void);
+
+// CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing is an internal detail
+// used for testing purposes.
+OPENSSL_EXPORT void CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing(void);
+
+#if defined(__cplusplus)
+}  // extern C
+#endif
+
+#endif  // OPENSSL_HEADER_CRYPTO_FORK_DETECT_H
diff --git a/crypto/fipsmodule/fork_detect_test.cc b/crypto/fipsmodule/fork_detect_test.cc
new file mode 100644
index 0000000..12eed4d
--- /dev/null
+++ b/crypto/fipsmodule/fork_detect_test.cc
@@ -0,0 +1,158 @@
+/* Copyright (c) 2020, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/base.h>
+
+#if defined(OPENSSL_LINUX)
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <functional>
+
+#if defined(OPENSSL_THREADS)
+#include <thread>
+#include <vector>
+#endif
+
+#include <gtest/gtest.h>
+
+#include "fork_detect.h"
+
+
+static pid_t WaitpidEINTR(pid_t pid, int *out_status, int options) {
+  pid_t ret;
+  do {
+    ret = waitpid(pid, out_status, options);
+  } while (ret < 0 && errno == EINTR);
+
+  return ret;
+}
+
+// The *InChild functions run inside a child process and must report errors via
+// |stderr| and |_exit| rather than GTest.
+
+static void CheckGenerationInChild(const char *name, uint64_t expected) {
+  uint64_t generation = CRYPTO_get_fork_generation();
+  if (generation != expected) {
+    fprintf(stderr, "%s generation (#1) was %" PRIu64 ", wanted %" PRIu64 ".\n",
+            name, generation, expected);
+    _exit(1);
+  }
+
+  // The generation should be stable.
+  generation = CRYPTO_get_fork_generation();
+  if (generation != expected) {
+    fprintf(stderr, "%s generation (#2) was %" PRIu64 ", wanted %" PRIu64 ".\n",
+            name, generation, expected);
+    _exit(1);
+  }
+}
+
+// ForkInChild forks a child which runs |f|. If the child exits unsuccessfully,
+// this function will also exit unsuccessfully.
+static void ForkInChild(std::function<void()> f) {
+  fflush(stderr);  // Avoid duplicating any buffered output.
+
+  const pid_t pid = fork();
+  if (pid < 0) {
+    perror("fork");
+    _exit(1);
+  } else if (pid == 0) {
+    f();
+    _exit(0);
+  }
+
+  // Wait for the child and pass its exit code up.
+  int status;
+  if (WaitpidEINTR(pid, &status, 0) < 0) {
+    perror("waitpid");
+    _exit(1);
+  }
+  if (!WIFEXITED(status)) {
+    fprintf(stderr, "Child did not exit cleanly.\n");
+    _exit(1);
+  }
+  if (WEXITSTATUS(status) != 0) {
+    // Pass the failure up.
+    _exit(WEXITSTATUS(status));
+  }
+}
+
+TEST(ForkDetect, Test) {
+  const uint64_t start = CRYPTO_get_fork_generation();
+  if (start == 0) {
+    fprintf(stderr, "Fork detection not supported. Skipping test.\n");
+    return;
+  }
+
+  // The fork generation should be stable.
+  EXPECT_EQ(start, CRYPTO_get_fork_generation());
+
+  fflush(stderr);
+  const pid_t child = fork();
+
+  if (child == 0) {
+    // Fork grandchildren before observing the fork generation. The
+    // grandchildren will observe |start| + 1.
+    for (int i = 0; i < 2; i++) {
+      ForkInChild([&] { CheckGenerationInChild("Grandchild", start + 1); });
+    }
+
+    // Now the child also observes |start| + 1. This is fine because it has
+    // already diverged from the grandchild at this point.
+    CheckGenerationInChild("Child", start + 1);
+
+    // Forked grandchildren will now observe |start| + 2.
+    for (int i = 0; i < 2; i++) {
+      ForkInChild([&] { CheckGenerationInChild("Grandchild", start + 2); });
+    }
+
+#if defined(OPENSSL_THREADS)
+    // The fork generation logic itself must be thread-safe. We test this in a
+    // child process to capture the actual fork detection. This segment is meant
+    // to be tested in TSan.
+    ForkInChild([&] {
+      std::vector<std::thread> threads(4);
+      for (int i = 0; i < 2; i++) {
+        for (auto &t : threads) {
+          t = std::thread(
+              [&] { CheckGenerationInChild("Grandchild thread", start + 2); });
+        }
+        for (auto &t : threads) {
+          t.join();
+        }
+      }
+    });
+#endif  // OPENSSL_THREADS
+
+    // The child still observes |start| + 1.
+    CheckGenerationInChild("Child", start + 1);
+    _exit(0);
+  }
+
+  ASSERT_GT(child, 0) << "Error in fork: " << strerror(errno);
+  int status;
+  ASSERT_EQ(child, WaitpidEINTR(child, &status, 0))
+      << "Error in waitpid: " << strerror(errno);
+  ASSERT_TRUE(WIFEXITED(status));
+  EXPECT_EQ(0, WEXITSTATUS(status)) << "Error in child process";
+
+  // We still observe |start|.
+  EXPECT_EQ(start, CRYPTO_get_fork_generation());
+}
+
+#endif  // OPENSSL_LINUX
diff --git a/crypto/fipsmodule/rand/rand.c b/crypto/fipsmodule/rand/rand.c
index 7bed579..b9b5184 100644
--- a/crypto/fipsmodule/rand/rand.c
+++ b/crypto/fipsmodule/rand/rand.c
@@ -29,6 +29,7 @@
 #include "internal.h"
 #include "../../internal.h"
 #include "../delocate.h"
+#include "../fork_detect.h"
 
 
 // It's assumed that the operating system always has an unfailing source of
@@ -57,6 +58,7 @@
 // rand_thread_state contains the per-thread state for the RNG.
 struct rand_thread_state {
   CTR_DRBG_STATE drbg;
+  uint64_t fork_generation;
   // calls is the number of generate calls made on |drbg| since it was last
   // (re)seeded. This is bound by |kReseedInterval|.
   unsigned calls;
@@ -240,6 +242,8 @@
     return;
   }
 
+  const uint64_t fork_generation = CRYPTO_get_fork_generation();
+
   // Additional data is mixed into every CTR-DRBG call to protect, as best we
   // can, against forks & VM clones. We do not over-read this information and
   // don't reseed with it so, from the point of view of FIPS, this doesn't
@@ -251,9 +255,9 @@
       !rdrand(additional_data, sizeof(additional_data))) {
     // Without a hardware RNG to save us from address-space duplication, the OS
     // entropy is used. This can be expensive (one read per |RAND_bytes| call)
-    // and so can be disabled by applications that we have ensured don't fork
-    // and aren't at risk of VM cloning.
-    if (rand_fork_unsafe_buffering_enabled()) {
+    // and so is disabled when we have fork detection, or if the application has
+    // promised not to fork.
+    if (fork_generation != 0 || rand_fork_unsafe_buffering_enabled()) {
       OPENSSL_memset(additional_data, 0, sizeof(additional_data));
     } else if (!have_rdrand()) {
       // No alternative so block for OS entropy.
@@ -291,6 +295,7 @@
       abort();
     }
     state->calls = 0;
+    state->fork_generation = fork_generation;
 
 #if defined(BORINGSSL_FIPS)
     if (state != &stack_state) {
@@ -307,7 +312,8 @@
 #endif
   }
 
-  if (state->calls >= kReseedInterval) {
+  if (state->calls >= kReseedInterval ||
+      state->fork_generation != fork_generation) {
     uint8_t seed[CTR_DRBG_ENTROPY_LEN];
     rand_get_seed(state, seed);
 #if defined(BORINGSSL_FIPS)
@@ -325,6 +331,7 @@
       abort();
     }
     state->calls = 0;
+    state->fork_generation = fork_generation;
   } else {
 #if defined(BORINGSSL_FIPS)
     CRYPTO_STATIC_MUTEX_lock_read(thread_states_list_lock_bss_get());
diff --git a/crypto/fipsmodule/rand/urandom_test.cc b/crypto/fipsmodule/rand/urandom_test.cc
index 266a603..cd2e16e 100644
--- a/crypto/fipsmodule/rand/urandom_test.cc
+++ b/crypto/fipsmodule/rand/urandom_test.cc
@@ -28,6 +28,8 @@
 #include <sys/syscall.h>
 #include <sys/user.h>
 
+#include "../fork_detect.h"
+
 #if !defined(PTRACE_O_EXITKILL)
 #define PTRACE_O_EXITKILL (1 << 20)
 #endif
@@ -329,6 +331,10 @@
   RAND_bytes(&byte, sizeof(byte));
 }
 
+static bool have_fork_detection() {
+  return CRYPTO_get_fork_generation() != 0;
+}
+
 // TestFunctionPRNGModel is a model of how the urandom.c code will behave when
 // |TestFunction| is run. It should return the same trace of events that
 // |GetTrace| will observe the real code making.
@@ -411,24 +417,26 @@
   const size_t kAdditionalDataLength = 32;
 
   if (!have_rdrand()) {
-    if (!sysrand(true, kAdditionalDataLength) ||
+    if ((!have_fork_detection() && !sysrand(true, kAdditionalDataLength)) ||
         // Initialise CRNGT.
         (is_fips && !sysrand(true, 16)) ||
         !sysrand(true, kSeedLength) ||
         // Second entropy draw.
-        !sysrand(true, kAdditionalDataLength)) {
+        (!have_fork_detection() && !sysrand(true, kAdditionalDataLength))) {
       return ret;
     }
   } else if (
       // First additional data. If fast RDRAND isn't available then a
       // non-blocking OS entropy draw will be tried.
-      (!have_fast_rdrand() && !sysrand(false, kAdditionalDataLength)) ||
+      (!have_fast_rdrand() && !have_fork_detection() &&
+       !sysrand(false, kAdditionalDataLength)) ||
       // Opportuntistic entropy draw in FIPS mode because RDRAND was used.
       // In non-FIPS mode it's just drawn from |CRYPTO_sysrand| in a blocking
       // way.
       !sysrand(!is_fips, CTR_DRBG_ENTROPY_LEN) ||
       // Second entropy draw's additional data.
-      (!have_fast_rdrand() && !sysrand(false, kAdditionalDataLength))) {
+      (!have_fast_rdrand() && !have_fork_detection() &&
+       !sysrand(false, kAdditionalDataLength))) {
     return ret;
   }
 
@@ -493,6 +501,11 @@
 
 int main(int argc, char **argv) {
   ::testing::InitGoogleTest(&argc, argv);
+
+  if (getenv("BORINGSSL_IGNORE_MADV_WIPEONFORK")) {
+    CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing();
+  }
+
   return RUN_ALL_TESTS();
 }
 
diff --git a/crypto/fipsmodule/rsa/blinding.c b/crypto/fipsmodule/rsa/blinding.c
index b05f9c9..29477bd 100644
--- a/crypto/fipsmodule/rsa/blinding.c
+++ b/crypto/fipsmodule/rsa/blinding.c
@@ -167,6 +167,10 @@
   OPENSSL_free(r);
 }
 
+void BN_BLINDING_invalidate(BN_BLINDING *b) {
+  b->counter = BN_BLINDING_COUNTER - 1;
+}
+
 static int bn_blinding_update(BN_BLINDING *b, const BIGNUM *e,
                               const BN_MONT_CTX *mont, BN_CTX *ctx) {
   if (++b->counter == BN_BLINDING_COUNTER) {
diff --git a/crypto/fipsmodule/rsa/internal.h b/crypto/fipsmodule/rsa/internal.h
index f913058..faa6fb7 100644
--- a/crypto/fipsmodule/rsa/internal.h
+++ b/crypto/fipsmodule/rsa/internal.h
@@ -83,6 +83,7 @@
 
 BN_BLINDING *BN_BLINDING_new(void);
 void BN_BLINDING_free(BN_BLINDING *b);
+void BN_BLINDING_invalidate(BN_BLINDING *b);
 int BN_BLINDING_convert(BIGNUM *n, BN_BLINDING *b, const BIGNUM *e,
                         const BN_MONT_CTX *mont_ctx, BN_CTX *ctx);
 int BN_BLINDING_invert(BIGNUM *n, const BN_BLINDING *b, BN_MONT_CTX *mont_ctx,
diff --git a/crypto/fipsmodule/rsa/rsa_impl.c b/crypto/fipsmodule/rsa/rsa_impl.c
index 94fb75c..280c81a 100644
--- a/crypto/fipsmodule/rsa/rsa_impl.c
+++ b/crypto/fipsmodule/rsa/rsa_impl.c
@@ -70,6 +70,7 @@
 #include "../bn/internal.h"
 #include "../../internal.h"
 #include "../delocate.h"
+#include "../fork_detect.h"
 
 
 static int check_modulus_and_exponent_sizes(const RSA *rsa) {
@@ -365,8 +366,21 @@
   assert(rsa->mont_n != NULL);
 
   BN_BLINDING *ret = NULL;
+  const uint64_t fork_generation = CRYPTO_get_fork_generation();
   CRYPTO_MUTEX_lock_write(&rsa->lock);
 
+  // Wipe the blinding cache on |fork|.
+  if (rsa->blinding_fork_generation != fork_generation) {
+    for (unsigned i = 0; i < rsa->num_blindings; i++) {
+      // The inuse flag must be zero unless we were forked from a
+      // multi-threaded process, in which case calling back into BoringSSL is
+      // forbidden.
+      assert(rsa->blindings_inuse[i] == 0);
+      BN_BLINDING_invalidate(rsa->blindings[i]);
+    }
+    rsa->blinding_fork_generation = fork_generation;
+  }
+
   uint8_t *const free_inuse_flag =
       OPENSSL_memchr(rsa->blindings_inuse, 0, rsa->num_blindings);
   if (free_inuse_flag != NULL) {
diff --git a/crypto/rand_extra/rand_test.cc b/crypto/rand_extra/rand_test.cc
index 9c69b8f..8e608bf 100644
--- a/crypto/rand_extra/rand_test.cc
+++ b/crypto/rand_extra/rand_test.cc
@@ -21,6 +21,7 @@
 #include <openssl/cpu.h>
 #include <openssl/span.h>
 
+#include "../fipsmodule/fork_detect.h"
 #include "../fipsmodule/rand/internal.h"
 #include "../test/abi_test.h"
 #include "../test/test_util.h"
diff --git a/include/openssl/rsa.h b/include/openssl/rsa.h
index 51600c6..ed6df69 100644
--- a/include/openssl/rsa.h
+++ b/include/openssl/rsa.h
@@ -741,6 +741,7 @@
   // |blindings_inuse| from 0 to 1.
   BN_BLINDING **blindings;
   unsigned char *blindings_inuse;
+  uint64_t blinding_fork_generation;
 
   // private_key_frozen is one if the key has been used for a private key
   // operation and may no longer be mutated.
diff --git a/util/all_tests.json b/util/all_tests.json
index a251e5c..b899202 100644
--- a/util/all_tests.json
+++ b/util/all_tests.json
@@ -28,6 +28,16 @@
     "env": ["OPENSSL_ia32cap=|0x0000000040000000"]
   },
   {
+    "comment": "No RDRAND and without WIPEONFORK",
+    "cmd": ["crypto/urandom_test"],
+    "env": ["OPENSSL_ia32cap=~0x4000000000000000", "BORINGSSL_IGNORE_MADV_WIPEONFORK=1"]
+  },
+  {
+    "comment": "Potentially with RDRAND, but not Intel, and no WIPEONFORK",
+    "cmd": ["crypto/urandom_test"],
+    "env": ["OPENSSL_ia32cap=~0x0000000040000000", "BORINGSSL_IGNORE_MADV_WIPEONFORK=1"]
+  },
+  {
     "cmd": ["crypto/crypto_test", "--fork_unsafe_buffering", "--gtest_filter=RandTest.*:-RandTest.Fork"]
   },
   {