| // Copyright 2020 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #if !defined(_GNU_SOURCE) | |
| #define _GNU_SOURCE // needed for madvise() and MAP_ANONYMOUS on Linux. | |
| #endif | |
| #include "../bcm_support.h" | |
| #include "../internal.h" | |
| #include "internal.h" | |
| #if defined(OPENSSL_FORK_DETECTION_WIPEONFORK) | |
| #if defined(OPENSSL_LINUX) | |
| #include <assert.h> | |
| #include <stdlib.h> | |
| #include <sys/mman.h> | |
| #include <unistd.h> | |
| #if defined(MADV_WIPEONFORK) | |
| static_assert(MADV_WIPEONFORK == 18); | |
| #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 | |
| 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; | |
| static StaticMutex g_fork_detect_lock; | |
| static Atomic<uint32_t> *g_fork_detect_addr; | |
| static uint64_t g_fork_generation; | |
| static void init_fork_detect() { | |
| if (g_force_madv_wipeonfork) { | |
| return; | |
| } | |
| long page_size = sysconf(_SC_PAGESIZE); | |
| if (page_size <= 0) { | |
| return; | |
| } | |
| 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; | |
| } | |
| if (!wipeonfork(addr, static_cast<size_t>(page_size))) { | |
| munmap(addr, static_cast<size_t>(page_size)); | |
| return; | |
| } | |
| g_fork_detect_addr = new (addr) Atomic<uint32_t>(1); | |
| g_fork_generation = 1; | |
| } | |
| uint64_t bssl::CRYPTO_get_fork_generation() { | |
| CRYPTO_once(&g_fork_detect_once, 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. | |
| Atomic<uint32_t> *const flag_ptr = g_fork_detect_addr; | |
| if (flag_ptr == nullptr) { | |
| // Our kernel is too old to support `MADV_WIPEONFORK` or | |
| // `g_force_madv_wipeonfork` is set. | |
| if (g_force_madv_wipeonfork && g_force_madv_wipeonfork_enabled) { | |
| // 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; | |
| if (flag_ptr->load() != 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. | |
| MutexWriteLock lock(&g_fork_detect_lock); | |
| uint64_t current_generation = *generation_ptr; | |
| if (flag_ptr->load() == 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; | |
| flag_ptr->store(1); | |
| } | |
| return current_generation; | |
| } | |
| void bssl::CRYPTO_fork_detect_force_madv_wipeonfork_for_testing(int on) { | |
| g_force_madv_wipeonfork = 1; | |
| g_force_madv_wipeonfork_enabled = on; | |
| } | |
| #elif defined(OPENSSL_FORK_DETECTION_PTHREAD_ATFORK) | |
| static CRYPTO_once_t g_pthread_fork_detection_once = CRYPTO_ONCE_INIT; | |
| static uint64_t g_atfork_fork_generation; | |
| static void we_are_forked() { | |
| // Immediately after a fork, the process must be single-threaded. | |
| uint64_t value = g_atfork_fork_generation + 1; | |
| if (value == 0) { | |
| value = 1; | |
| } | |
| g_atfork_fork_generation = value; | |
| } | |
| static void init_pthread_fork_detection() { | |
| if (pthread_atfork(nullptr, nullptr, we_are_forked) != 0) { | |
| abort(); | |
| } | |
| g_atfork_fork_generation = 1; | |
| } | |
| uint64_t bssl::CRYPTO_get_fork_generation() { | |
| CRYPTO_once(&g_pthread_fork_detection_once, init_pthread_fork_detection); | |
| return g_atfork_fork_generation; | |
| } | |
| #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 bssl::CRYPTO_get_fork_generation() { 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 occurred on any call entropy must be added to | |
| // every RAND_bytes call. | |
| uint64_t bssl::CRYPTO_get_fork_generation() { return 0; } | |
| #endif |