Fork detection: add rfork() and similar support for FreeBSD/OpenBSD.

On BSD systems, `pthread_atfork()` is insufficient, as e.g. `rfork()`
(similar to Linux's `clone()`) can create processes too, and will not
call pthread's handlers. Use BSD's `minherit` mechanism to detect this
kind of process duplication the same way as it works on Linux.

There would be similar problems if one were to use `vfork()` on some
systems. However, do note that according to POSIX.1, doing virtually
anything in the `vfork()` child is undefined behavior, and we can't
reasonably be expected to guard against undefined behavior in callers.
macOS even deprecated the function and shows a warning for any use of
it.

I believe this will not actually hit any real-world software, and am
merely adding this to be sure.

Manually tested on OpenBSD:

```
grawp$ uname -a
OpenBSD grawp.my.domain 7.1 GENERIC.MP#465 amd64
grawp$ ./crypto_test --gtest_filter='*Fork*'
Note: Google Test filter = *Fork*
[==========] Running 3 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 2 tests from ForkDetect
[ RUN      ] ForkDetect.Test
[       OK ] ForkDetect.Test (7 ms)
[ RUN      ] ForkDetect.TestAlternate
[       OK ] ForkDetect.TestAlternate (0 ms)
[----------] 2 tests from ForkDetect (8 ms total)

[----------] 1 test from RandTest
[ RUN      ] RandTest.Fork
[       OK ] RandTest.Fork (4 ms)
[----------] 1 test from RandTest (4 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 2 test suites ran. (13 ms total)
[  PASSED  ] 3 tests.
```

and on FreeBSD:

```
[rpolzer@grawp build (git)-[d03c3135d]-]$ uname -a
FreeBSD grawp 15.0-RELEASE-p6 FreeBSD 15.0-RELEASE-p6 releng/15.0-n281024-6b6bc9afa0b0 GENERIC amd64
[rpolzer@grawp build (git)-[d03c3135d]-]$ ./crypto_test --gtest_filter='*Fork*'
Note: Google Test filter = *Fork*
[==========] Running 3 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 2 tests from ForkDetect
[ RUN      ] ForkDetect.Test
[       OK ] ForkDetect.Test (2 ms)
[ RUN      ] ForkDetect.TestAlternate
[       OK ] ForkDetect.TestAlternate (0 ms)
[----------] 2 tests from ForkDetect (3 ms total)

[----------] 1 test from RandTest
[ RUN      ] RandTest.Fork
[       OK ] RandTest.Fork (1 ms)
[----------] 1 test from RandTest (1 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 2 test suites ran. (5 ms total)
[  PASSED  ] 3 tests.
```

Tested on both that without this change, the added test fails.

Update-Note: if you seriously sidestep the OS-level proper channels of
creating a new process duplicating address space, stop doing that, or at
least do not call BoringSSL from the so-forked process. In general, any
address space duplication shall be done using `fork()` and `fork()`
only.

Change-Id: I11f0c3e868a36ca5a1ab8b605c40f9856a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/94247
Commit-Queue: Rudolf Polzer <rpolzer@google.com>
Presubmit-BoringSSL-Verified: boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/rand/fork_detect.cc b/crypto/rand/fork_detect.cc
index c12464a..ecee436 100644
--- a/crypto/rand/fork_detect.cc
+++ b/crypto/rand/fork_detect.cc
@@ -20,9 +20,10 @@
 #include "../internal.h"
 #include "internal.h"
 
-using namespace bssl;
+#if defined(OPENSSL_FORK_DETECTION_WIPEONFORK)
 
-#if defined(OPENSSL_FORK_DETECTION_MADVISE)
+#if defined(OPENSSL_LINUX)
+
 #include <assert.h>
 #include <stdlib.h>
 #include <sys/mman.h>
@@ -32,13 +33,48 @@
 #else
 #define MADV_WIPEONFORK 18
 #endif
+
+#else
+
+// Otherwise assume a BSD style API.
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#endif
+
 #elif defined(OPENSSL_FORK_DETECTION_PTHREAD_ATFORK)
+
 #include <pthread.h>
 #include <stdlib.h>
 #include <unistd.h>
+
 #endif  // OPENSSL_FORK_DETECTION_PTHREAD_ATFORK
 
-#if defined(OPENSSL_FORK_DETECTION_MADVISE)
+
+using namespace bssl;
+
+#if defined(OPENSSL_FORK_DETECTION_WIPEONFORK)
+
+static bool wipeonfork(void *addr, size_t page_size) {
+#if defined(OPENSSL_LINUX)
+  // Linux flavor, >=4.14.
+  // Some versions of qemu (up to at least 5.0.0-rc4, see linux-user/syscall.c)
+  // ignore |madvise| calls and just return zero (i.e. success). But we need to
+  // know whether MADV_WIPEONFORK actually took effect. Therefore try an invalid
+  // call to check that the implementation of |madvise| is actually rejecting
+  // unknown |advice| values.
+  return madvise(addr, page_size, -1) != 0 &&
+         madvise(addr, page_size, MADV_WIPEONFORK) == 0;
+#elif defined(MAP_INHERIT_ZERO)
+  // OpenBSD flavor, >=5.6.
+  return minherit(addr, page_size, MAP_INHERIT_ZERO) == 0;
+#else
+  // FreeBSD flavor, >=12.0.
+  return minherit(addr, page_size, INHERIT_ZERO) == 0;
+#endif
+}
+
 static int g_force_madv_wipeonfork;
 static int g_force_madv_wipeonfork_enabled;
 static CRYPTO_once_t g_fork_detect_once = CRYPTO_ONCE_INIT;
@@ -56,20 +92,14 @@
     return;
   }
 
-  void *addr = mmap(nullptr, (size_t)page_size, PROT_READ | PROT_WRITE,
-                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  void *addr = mmap(nullptr, static_cast<size_t>(page_size),
+                    PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
   if (addr == MAP_FAILED) {
     return;
   }
 
-  // Some versions of qemu (up to at least 5.0.0-rc4, see linux-user/syscall.c)
-  // ignore |madvise| calls and just return zero (i.e. success). But we need to
-  // know whether MADV_WIPEONFORK actually took effect. Therefore try an invalid
-  // call to check that the implementation of |madvise| is actually rejecting
-  // unknown |advice| values.
-  if (madvise(addr, (size_t)page_size, -1) == 0 ||
-      madvise(addr, (size_t)page_size, MADV_WIPEONFORK) != 0) {
-    munmap(addr, (size_t)page_size);
+  if (!wipeonfork(addr, static_cast<size_t>(page_size))) {
+    munmap(addr, static_cast<size_t>(page_size));
     return;
   }
 
diff --git a/crypto/rand/fork_detect_test.cc b/crypto/rand/fork_detect_test.cc
index 565519b..81bcb0d 100644
--- a/crypto/rand/fork_detect_test.cc
+++ b/crypto/rand/fork_detect_test.cc
@@ -15,11 +15,13 @@
 #include <openssl/base.h>
 
 #include "../bcm_support.h"
+#include "internal.h"
 
 // TSAN cannot cope with this test and complains that "starting new threads
 // after multi-threaded fork is not supported".
 #if defined(OPENSSL_FORK_DETECTION) && !defined(OPENSSL_TSAN) && \
     !defined(OPENSSL_IOS)
+
 #include <errno.h>
 #include <inttypes.h>
 #include <stdio.h>
@@ -36,6 +38,8 @@
 
 #include <gtest/gtest.h>
 
+#include <openssl/mem.h>
+
 
 BSSL_NAMESPACE_BEGIN
 namespace {
@@ -70,20 +74,7 @@
   }
 }
 
-// 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);
-  }
-
+static void WaitForChildOrDie(pid_t pid) {
   // Wait for the child and pass its exit code up.
   int status;
   if (WaitpidEINTR(pid, &status, 0) < 0) {
@@ -100,6 +91,68 @@
   }
 }
 
+// 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);
+  }
+  if (pid == 0) {
+    f();
+    _exit(0);
+  }
+  WaitForChildOrDie(pid);
+}
+
+// Many systems provide an alternate API to create a child process and run code
+// in it.
+static bool AlternateForkInChild(std::function<void()> f) {
+  fflush(stderr);  // Avoid duplicating any buffered output.
+#if defined(OPENSSL_LINUX)
+  // On Linux, clone() can make new processes, too (even by default).
+  void *temp_stack = OPENSSL_malloc(65536);
+  void *stack_top = static_cast<char *>(temp_stack) + 65536;
+  pid_t pid = clone(
+      +[](void *fp) {
+        (*static_cast<std::function<void()> *>(fp))();
+        return 0;
+      },
+      stack_top, SIGCHLD, &f);
+  OPENSSL_free(temp_stack);
+  WaitForChildOrDie(pid);
+  return true;
+#elif defined(OPENSSL_FREEBSD)
+  // On FreeBSD, rfork() can be used to simulate fork().
+  const pid_t pid = rfork(RFFDG | RFPROC);
+  if (pid < 0) {
+    perror("fork");
+    _exit(1);
+  }
+  if (pid == 0) {
+    f();
+    _exit(0);
+  }
+  WaitForChildOrDie(pid);
+  return true;
+#else
+  // No alternate methods known.
+  return false;
+#endif
+}
+
+
+TEST(ForkDetect, OSSupport) {
+  uint64_t start = CRYPTO_get_fork_generation();
+  EXPECT_NE(start, uint64_t{0})
+      << "Fork detection not supported, but support is configured to be "
+         "expected on this platform in crypto/rand/internal.h. Typically this "
+         "means your OS or kernel is too old. Expect reduced performance, but "
+         "no security impact.";
+}
+
 TEST(ForkDetect, Test) {
   uint64_t start = CRYPTO_get_fork_generation();
   if (start == 0) {
@@ -177,6 +230,24 @@
   EXPECT_EQ(start, CRYPTO_get_fork_generation());
 }
 
+TEST(ForkDetect, TestAlternateFork) {
+  uint64_t start = CRYPTO_get_fork_generation();
+  if (start == 0) {
+    GTEST_SKIP() << "Fork detection not supported. Skipping test.\n";
+  }
+
+  // The fork generation should be stable.
+  EXPECT_EQ(start, CRYPTO_get_fork_generation());
+
+  if (!AlternateForkInChild(
+          [&] { CheckGenerationAtLeastInChild("Child", start + 1); })) {
+    GTEST_SKIP() << "No alternate fork method detected. Skipping test.\n";
+  }
+
+  // We still observe |start|.
+  EXPECT_EQ(start, CRYPTO_get_fork_generation());
+}
+
 }  // namespace
 BSSL_NAMESPACE_END
 
diff --git a/crypto/rand/internal.h b/crypto/rand/internal.h
index 9ed080f..50dbf0b 100644
--- a/crypto/rand/internal.h
+++ b/crypto/rand/internal.h
@@ -17,6 +17,7 @@
 
 #include <openssl/base.h>
 
+
 #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
 #define OPENSSL_RAND_DETERMINISTIC
 #elif defined(OPENSSL_TRUSTY)
@@ -34,22 +35,33 @@
 #define OPENSSL_RAND_GETENTROPY
 #endif
 
-#if defined(OPENSSL_LINUX)
+#if defined(OPENSSL_LINUX) || defined(OPENSSL_FREEBSD) || \
+    defined(OPENSSL_OPENBSD)
+
 // On linux we use MADVISE instead of pthread_atfork(), due
 // to concerns about clone() being used for address space
 // duplication.
 #define OPENSSL_FORK_DETECTION
-#define OPENSSL_FORK_DETECTION_MADVISE
-#elif defined(OPENSSL_MACOS) || defined(OPENSSL_IOS) || \
-    defined(OPENSSL_OPENBSD) || defined(OPENSSL_FREEBSD)
+#define OPENSSL_FORK_DETECTION_WIPEONFORK
+
+#elif defined(OPENSSL_MACOS) || defined(OPENSSL_IOS)
+
 // These platforms may detect address space duplication with pthread_atfork.
 // iOS doesn't normally allow fork in apps, but it's there.
 #define OPENSSL_FORK_DETECTION
 #define OPENSSL_FORK_DETECTION_PTHREAD_ATFORK
+
 #elif defined(OPENSSL_WINDOWS) || defined(OPENSSL_TRUSTY) || \
     defined(__ZEPHYR__) || defined(CROS_EC)
+
 // These platforms do not fork.
 #define OPENSSL_DOES_NOT_FORK
+
+#else
+
+// Other platforms may fork, but BoringSSL cannot reliably detect it happening.
+// So instead, new entropy will be drawn on every RNG call.
+
 #endif
 
 #endif  // OPENSSL_HEADER_CRYPTO_RAND_INTERNAL_H