Skip to content

Commit

Permalink
Add support for fork detection via pthread_atfork()
Browse files Browse the repository at this point in the history
This provides an alternative way for fork detection that can
be used if we are certain the platform does not have other ways
of cloning an address space in use that are not noticed by
pthread_atfork.

This supports using pthread_atfork for fork detection on
macOS, iOS, OpenBSD and FreeBSD. Linux continues to use MADVISE
for the generation number and fork detection.

Change-Id: I79fd7769477dc90bfe37229d2ff2e8c16898dff7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/59906
Reviewed-by: David Benjamin <davidben@google.com>
Auto-Submit: Bob Beck <bbe@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
  • Loading branch information
Bob Beck authored and Boringssl LUCI CQ committed Oct 20, 2023
1 parent 39d7ee9 commit bfa8369
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 33 deletions.
64 changes: 51 additions & 13 deletions crypto/fipsmodule/rand/fork_detect.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,34 @@
#endif

#include <openssl/base.h>

#include "fork_detect.h"

#if defined(OPENSSL_LINUX)
#include <assert.h>
#include <sys/mman.h>
#if defined(OPENSSL_FORK_DETECTION_MADVISE)
#include <unistd.h>
#include <stdlib.h>

#include "../delocate.h"
#include "../../internal.h"


#include <assert.h>
#include <sys/mman.h>
#if defined(MADV_WIPEONFORK)
static_assert(MADV_WIPEONFORK == 18, "MADV_WIPEONFORK is not 18");
#else
#define MADV_WIPEONFORK 18
#endif
#elif defined(OPENSSL_FORK_DETECTION_PTHREAD_ATFORK)
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#endif // OPENSSL_FORK_DETECTION_MADVISE

#include "../delocate.h"
#include "../../internal.h"

#if defined(OPENSSL_FORK_DETECTION_MADVISE)
DEFINE_BSS_GET(int, g_force_madv_wipeonfork);
DEFINE_BSS_GET(int, g_force_madv_wipeonfork_enabled);
DEFINE_STATIC_ONCE(g_fork_detect_once);
DEFINE_STATIC_MUTEX(g_fork_detect_lock);
DEFINE_BSS_GET(CRYPTO_atomic_u32 *, g_fork_detect_addr);
DEFINE_BSS_GET(uint64_t, g_fork_generation);
DEFINE_BSS_GET(int, g_force_madv_wipeonfork);
DEFINE_BSS_GET(int, g_force_madv_wipeonfork_enabled);

static void init_fork_detect(void) {
if (*g_force_madv_wipeonfork_bss_get()) {
Expand Down Expand Up @@ -73,9 +76,12 @@ static void init_fork_detect(void) {
CRYPTO_atomic_store_u32(addr, 1);
*g_fork_detect_addr_bss_get() = addr;
*g_fork_generation_bss_get() = 1;

}

uint64_t CRYPTO_get_fork_generation(void) {
CRYPTO_once(g_fork_detect_once_bss_get(), init_fork_detect);

// In a single-threaded process, there are obviously no races because there's
// only a single mutator in the address space.
//
Expand All @@ -87,7 +93,6 @@ uint64_t CRYPTO_get_fork_generation(void) {
// child process is single-threaded, the child may become multi-threaded
// before it observes this. Therefore, we must synchronize the logic below.

CRYPTO_once(g_fork_detect_once_bss_get(), init_fork_detect);
CRYPTO_atomic_u32 *const flag_ptr = *g_fork_detect_addr_bss_get();
if (flag_ptr == NULL) {
// Our kernel is too old to support |MADV_WIPEONFORK| or
Expand All @@ -98,6 +103,12 @@ uint64_t CRYPTO_get_fork_generation(void) {
// doesn't support it.
return 42;
}
// With Linux and clone(), we do not believe that pthread_atfork() is
// sufficient for detecting all forms of address space duplication. At this
// point we have a kernel that does not support MADV_WIPEONFORK. We could
// return the generation number from pthread_atfork() here and it would
// probably be safe in almost any situation, but to ensure safety we return
// 0 and force an entropy draw on every call.
return 0;
}

Expand Down Expand Up @@ -140,7 +151,34 @@ void CRYPTO_fork_detect_force_madv_wipeonfork_for_testing(int on) {
*g_force_madv_wipeonfork_enabled_bss_get() = on;
}

#elif defined(OPENSSL_WINDOWS) || defined(OPENSSL_TRUSTY)
#elif defined(OPENSSL_FORK_DETECTION_PTHREAD_ATFORK)

DEFINE_STATIC_ONCE(g_pthread_fork_detection_once);
DEFINE_BSS_GET(uint64_t, g_atfork_fork_generation);

static void we_are_forked(void) {
// Immediately after a fork, the process must be single-threaded.
uint64_t value = *g_atfork_fork_generation_bss_get() + 1;
if (value == 0) {
value = 1;
}
*g_atfork_fork_generation_bss_get() = value;
}

static void init_pthread_fork_detection(void) {
if (pthread_atfork(NULL, NULL, we_are_forked) != 0) {
abort();
}
*g_atfork_fork_generation_bss_get() = 1;
}

uint64_t CRYPTO_get_fork_generation(void) {
CRYPTO_once(g_pthread_fork_detection_once_bss_get(), init_pthread_fork_detection);

return *g_atfork_fork_generation_bss_get();
}

#elif defined(OPENSSL_DOES_NOT_FORK)

// These platforms are guaranteed not to fork, and therefore do not require
// fork detection support. Returning a constant non zero value makes BoringSSL
Expand Down
17 changes: 17 additions & 0 deletions crypto/fipsmodule/rand/fork_detect.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@

#include <openssl/base.h>

#if defined(OPENSSL_LINUX)
// 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)
// 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)
// These platforms do not fork.
#define OPENSSL_DOES_NOT_FORK
#endif

#if defined(__cplusplus)
extern "C" {
#endif
Expand Down
55 changes: 35 additions & 20 deletions crypto/fipsmodule/rand/fork_detect_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@

#include <openssl/base.h>

#include "fork_detect.h"

// TSAN cannot cope with this test and complains that "starting new threads
// after multi-threaded fork is not supported".
#if defined(OPENSSL_LINUX) && !defined(OPENSSL_TSAN)
#if defined(OPENSSL_FORK_DETECTION) && !defined(OPENSSL_TSAN)
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
Expand All @@ -32,8 +34,6 @@

#include <gtest/gtest.h>

#include "fork_detect.h"


static pid_t WaitpidEINTR(pid_t pid, int *out_status, int options) {
pid_t ret;
Expand All @@ -47,19 +47,20 @@ static pid_t WaitpidEINTR(pid_t pid, int *out_status, int options) {
// 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) {
static void CheckGenerationAtLeastInChild(const char *name,
uint64_t minimum_expected) {
uint64_t generation = CRYPTO_get_fork_generation();
if (generation != expected) {
if (generation < minimum_expected) {
fprintf(stderr, "%s generation (#1) was %" PRIu64 ", wanted %" PRIu64 ".\n",
name, generation, expected);
name, generation, minimum_expected);
_exit(1);
}

// The generation should be stable.
generation = CRYPTO_get_fork_generation();
if (generation != expected) {
uint64_t new_generation = CRYPTO_get_fork_generation();
if (new_generation != generation) {
fprintf(stderr, "%s generation (#2) was %" PRIu64 ", wanted %" PRIu64 ".\n",
name, generation, expected);
name, new_generation, generation);
_exit(1);
}
}
Expand Down Expand Up @@ -95,10 +96,9 @@ static void ForkInChild(std::function<void()> f) {
}

TEST(ForkDetect, Test) {
const uint64_t start = CRYPTO_get_fork_generation();
uint64_t start = CRYPTO_get_fork_generation();
if (start == 0) {
fprintf(stderr, "Fork detection not supported. Skipping test.\n");
return;
GTEST_SKIP() << "Fork detection not supported. Skipping test.\n";
}

// The fork generation should be stable.
Expand All @@ -111,16 +111,22 @@ TEST(ForkDetect, Test) {
// 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); });
ForkInChild(
[&] { CheckGenerationAtLeastInChild("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);

CheckGenerationAtLeastInChild("Child", start + 1);

// In the pthread_atfork the value may have changed.
uint64_t child_generation = CRYPTO_get_fork_generation();
// Forked grandchildren will now observe |start| + 2.
for (int i = 0; i < 2; i++) {
ForkInChild([&] { CheckGenerationInChild("Grandchild", start + 2); });
ForkInChild([&] {
CheckGenerationAtLeastInChild("Grandchild", child_generation + 1);
});
}

#if defined(OPENSSL_THREADS)
Expand All @@ -131,8 +137,10 @@ TEST(ForkDetect, Test) {
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); });
t = std::thread([&] {
CheckGenerationAtLeastInChild("Grandchild thread",
child_generation + 1);
});
}
for (auto &t : threads) {
t.join();
Expand All @@ -141,8 +149,15 @@ TEST(ForkDetect, Test) {
});
#endif // OPENSSL_THREADS

// The child still observes |start| + 1.
CheckGenerationInChild("Child", start + 1);
// The child's observed value should be unchanged.
if (child_generation != CRYPTO_get_fork_generation()) {
fprintf(stderr,
"Child generation (final stable check) was %" PRIu64
", wanted %" PRIu64 ".\n",
child_generation, CRYPTO_get_fork_generation());
_exit(1);
}

_exit(0);
}

Expand All @@ -157,4 +172,4 @@ TEST(ForkDetect, Test) {
EXPECT_EQ(start, CRYPTO_get_fork_generation());
}

#endif // OPENSSL_LINUX && !OPENSSL_TSAN
#endif // OPENSSL_FORK_DETECTION && !OPENSSL_TSAN
4 changes: 4 additions & 0 deletions crypto/rand_extra/forkunsafe.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ void RAND_enable_fork_unsafe_buffering(int fd) {

CRYPTO_atomic_store_u32(&g_buffering_enabled, 1);
}

void RAND_disable_fork_unsafe_buffering(void) {
CRYPTO_atomic_store_u32(&g_buffering_enabled, 0);
}
#endif

int rand_fork_unsafe_buffering_enabled(void) {
Expand Down
5 changes: 5 additions & 0 deletions include/openssl/rand.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ OPENSSL_EXPORT int RAND_bytes(uint8_t *buf, size_t len);
// It has an unusual name because the buffer is unsafe across calls to |fork|.
// Hence, this function should never be called by libraries.
OPENSSL_EXPORT void RAND_enable_fork_unsafe_buffering(int fd);

// RAND_disable_fork_unsafe_buffering disables efficient buffered reading of
// /dev/urandom, causing BoringSSL to always draw entropy on every request
// for random bytes.
OPENSSL_EXPORT void RAND_disable_fork_unsafe_buffering(void);
#endif

#if defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE)
Expand Down

0 comments on commit bfa8369

Please sign in to comment.