/* 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 #include "fork_detect.h" #if defined(OPENSSL_LINUX) #include #include #include #include #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 int init_fork_detect_madv_wipeonfork(void *addr, long page_size) { // 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) { // The mapping |addr| points to is unmapped by caller. return 0; } return 1; } static void init_fork_detect(void) { int res = 0; void *addr = MAP_FAILED; long page_size = 0; // Check whether we are completely ignoring fork detection. This is only done // during testing. if (*g_ignore_madv_wipeonfork_bss_get() == 1) { goto cleanup; } page_size = sysconf(_SC_PAGESIZE); if (page_size <= 0) { goto cleanup; } addr = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (addr == MAP_FAILED) { goto cleanup; } if (init_fork_detect_madv_wipeonfork(addr, page_size) == 0) { goto cleanup; } *((volatile char *) addr) = 1; *g_fork_detect_addr_bss_get() = addr; *g_fork_generation_bss_get() = 1; res = 1; cleanup: if (res == 0 && addr != MAP_FAILED) { munmap(addr, (size_t)page_size); addr = NULL; } } 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; } #elif defined(OPENSSL_WINDOWS) || defined(OPENSSL_TRUSTY) // 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