|
|
|
/* 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_LINUX) && !defined(OPENSSL_TRUSTY)
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include <openssl/type_check.h>
|
|
|
|
|
|
|
|
#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 void init_fork_detect(void) {
|
|
|
|
if (*g_ignore_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;
|
|
|
|
}
|
|
|
|
|
|
|
|
*((volatile char *) addr) = 1;
|
|
|
|
*g_fork_detect_addr_bss_get() = addr;
|
|
|
|
*g_fork_generation_bss_get() = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else // !OPENSSL_LINUX
|
|
|
|
|
|
|
|
uint64_t CRYPTO_get_fork_generation(void) { return 0; }
|
|
|
|
|
|
|
|
#endif // OPENSSL_LINUX
|