blob: a2cf3a05a1344df9d9af2f1bb0ff9e4a8255e3aa [file] [log] [blame]
/* 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_FORK_DETECTION_MADVISE)
#include <unistd.h>
#include <stdlib.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);
static void init_fork_detect(void) {
if (*g_force_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;
}
// 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);
return;
}
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.
//
// In a multi-threaded environment, |CRYPTO_once| ensures that the flag byte
// is initialised atomically, even if multiple threads enter this function
// concurrently.
//
// Additionally, while the kernel will only clear WIPEONFORK at a point when a
// child process is single-threaded, the child may become multi-threaded
// before it observes this. Therefore, we must synchronize the logic below.
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
// |g_force_madv_wipeonfork| is set.
if (*g_force_madv_wipeonfork_bss_get() &&
*g_force_madv_wipeonfork_enabled_bss_get()) {
// A constant generation number to simulate support, even if the kernel
// 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;
}
// In the common case, try to observe the flag without taking a lock. This
// avoids cacheline contention in the PRNG.
uint64_t *const generation_ptr = g_fork_generation_bss_get();
if (CRYPTO_atomic_load_u32(flag_ptr) != 0) {
// If we observe a non-zero flag, it is safe to read |generation_ptr|
// without a lock. The flag and generation number are fixed for this copy of
// the address space.
return *generation_ptr;
}
// The flag was zero. The generation number must be incremented, but other
// threads may have concurrently observed the zero, so take a lock before
// incrementing.
CRYPTO_MUTEX *const lock = g_fork_detect_lock_bss_get();
CRYPTO_MUTEX_lock_write(lock);
uint64_t current_generation = *generation_ptr;
if (CRYPTO_atomic_load_u32(flag_ptr) == 0) {
// A fork has occurred.
current_generation++;
if (current_generation == 0) {
// Zero means fork detection isn't supported, so skip that value.
current_generation = 1;
}
// We must update |generation_ptr| before |flag_ptr|. Other threads may
// observe |flag_ptr| without taking a lock.
*generation_ptr = current_generation;
CRYPTO_atomic_store_u32(flag_ptr, 1);
}
CRYPTO_MUTEX_unlock_write(lock);
return current_generation;
}
void CRYPTO_fork_detect_force_madv_wipeonfork_for_testing(int on) {
*g_force_madv_wipeonfork_bss_get() = 1;
*g_force_madv_wipeonfork_enabled_bss_get() = on;
}
#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
// assume address space duplication is not a concern and adding entropy to
// every RAND_bytes call is not needed.
uint64_t CRYPTO_get_fork_generation(void) { return 0xc0ffee; }
#else
// These platforms may fork, but we do not have a mitigation mechanism in
// place. Returning a constant zero value makes BoringSSL assume that address
// space duplication could have occured on any call entropy must be added to
// every RAND_bytes call.
uint64_t CRYPTO_get_fork_generation(void) { return 0; }
#endif