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