-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FreeBSD and OpenBSD fork detection #2089
Changes from all commits
45cc105
c152f24
9371ab6
eae288b
3f8bae8
0b92726
a97f645
b549c4a
7f44571
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,29 +12,47 @@ | |
* 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. | ||
#include <openssl/target.h> | ||
|
||
// Methods to detect fork events aren't generally portable over our supported | ||
// platforms. Fork detection is therefore an opt-in. Capture the opt-in logic | ||
// below that categorizes a platform targets as either having | ||
// 1) fork detection support, | ||
// 2) forking doesn't exist, and | ||
// 3) no fork detection support. | ||
// For (1) we implement sufficient methods for detecting fork events. For (2), | ||
// since forking is not a semantic that exists for the platform, we do not need | ||
// to do anything. Except fake that fork detection is supported. For (3), we | ||
// can't do anything. In this case randomness generation falls back to | ||
// randomizing the state per-request. | ||
#if defined(OPENSSL_LINUX) | ||
#define AWSLC_FORK_DETECTION_SUPPORTED | ||
#if !defined(_GNU_SOURCE) | ||
#define _GNU_SOURCE // Needed for madvise() and MAP_ANONYMOUS. | ||
#endif | ||
#elif defined(OPENSSL_FREEBSD) || defined(OPENSSL_OPENBSD) | ||
#define AWSLC_FORK_DETECTION_SUPPORTED | ||
// FreeBSD requires POSIX compatibility off for its syscalls | ||
// (enables __BSD_VISIBLE). Without the below line, <sys/mman.h> cannot be | ||
// imported (it requires __BSD_VISIBLE). | ||
#undef _POSIX_C_SOURCE | ||
#elif defined(OPENSSL_WINDOWS) || defined(OPENSSL_TRUSTY) | ||
#define AWSLC_PLATFORM_DOES_NOT_FORK | ||
#endif | ||
|
||
#include <openssl/base.h> | ||
|
||
#include "fork_detect.h" | ||
|
||
#if defined(OPENSSL_LINUX) | ||
#include <sys/mman.h> | ||
#include <unistd.h> | ||
#include <stdlib.h> | ||
static int ignore_wipeonfork = 0; | ||
static int ignore_inheritzero = 0; | ||
|
||
#if defined(AWSLC_FORK_DETECTION_SUPPORTED) | ||
|
||
#include <openssl/base.h> | ||
#include <openssl/type_check.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 | ||
#include <stdlib.h> | ||
|
||
static CRYPTO_once_t fork_detect_once = CRYPTO_ONCE_INIT; | ||
static struct CRYPTO_STATIC_MUTEX fork_detect_lock = CRYPTO_STATIC_MUTEX_INIT; | ||
|
@@ -44,25 +62,29 @@ static struct CRYPTO_STATIC_MUTEX fork_detect_lock = CRYPTO_STATIC_MUTEX_INIT; | |
// assume that it has exclusive access to it. | ||
static volatile char *fork_detect_addr = NULL; | ||
static uint64_t fork_generation = 0; | ||
static int ignore_madv_wipeonfork = 0; | ||
|
||
static int init_fork_detect_madv_wipeonfork(void **addr_out) { | ||
static int ignore_all_fork_detection(void) { | ||
if (ignore_wipeonfork == 1 && | ||
ignore_inheritzero == 1) { | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
|
||
void *addr = MAP_FAILED; | ||
long page_size = 0; | ||
#if defined(OPENSSL_LINUX) | ||
|
||
GUARD_PTR(addr_out); | ||
#include <sys/mman.h> | ||
#include <unistd.h> | ||
|
||
page_size = sysconf(_SC_PAGESIZE); | ||
if (page_size <= 0) { | ||
return 0; | ||
} | ||
#if defined(MADV_WIPEONFORK) | ||
OPENSSL_STATIC_ASSERT(MADV_WIPEONFORK == 18, MADV_WIPEONFORK_is_not_18) | ||
#else | ||
#define MADV_WIPEONFORK 18 | ||
#endif | ||
|
||
addr = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE, | ||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | ||
if (addr == MAP_FAILED) { | ||
return 0; | ||
} | ||
static int init_fork_detect_wipeonfork(void *addr, long page_size) { | ||
|
||
GUARD_PTR(addr); | ||
|
||
// 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 | ||
|
@@ -71,29 +93,112 @@ static int init_fork_detect_madv_wipeonfork(void **addr_out) { | |
// 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. | ||
munmap(addr, (size_t)page_size); | ||
return 0; | ||
} | ||
|
||
*addr_out = addr; | ||
return 1; | ||
} | ||
|
||
#else // defined(OPENSSL_LINUX) | ||
|
||
static int init_fork_detect_wipeonfork(void *addr, long page_size) { | ||
return 0; | ||
} | ||
|
||
#endif // defined(OPENSSL_LINUX) | ||
|
||
|
||
#if defined(OPENSSL_FREEBSD) || defined(OPENSSL_OPENBSD) | ||
|
||
#include <sys/mman.h> | ||
#include <unistd.h> | ||
|
||
// Sometimes (for example, on FreeBSD) MAP_INHERIT_ZERO is called INHERIT_ZERO | ||
#if !defined(MAP_INHERIT_ZERO) && defined(INHERIT_ZERO) | ||
#define MAP_INHERIT_ZERO INHERIT_ZERO | ||
#endif | ||
Comment on lines
+116
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NP: Would it be safer to define our own macro with a value specific the target OS:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current form is a bit shorter. It has worked flawlessly in s2n-tls where I first used this pattern. |
||
|
||
static int init_fork_detect_inheritzero(void *addr, long page_size) { | ||
|
||
GUARD_PTR(addr); | ||
|
||
if (minherit(addr, page_size, MAP_INHERIT_ZERO) != 0) { | ||
return 0; | ||
} | ||
|
||
return 1; | ||
} | ||
|
||
#else // defined(OPENSSL_FREEBSD) || defined(OPENSSL_OPENBSD) | ||
|
||
static int init_fork_detect_inheritzero(void *addr, long page_size) { | ||
return 0; | ||
} | ||
|
||
#endif // defined(OPENSSL_FREEBSD) || defined(OPENSSL_OPENBSD) | ||
|
||
// We assume that a method in this function is sufficient to detect fork events. | ||
// Returns 1 if a method sufficient for fork detection successfully | ||
// initializes. Otherwise returns 0. | ||
static int init_fork_detect_methods_sufficient(void *addr, long page_size) { | ||
|
||
if (ignore_wipeonfork != 1 && | ||
torben-hansen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
init_fork_detect_wipeonfork(addr, page_size) == 1) { | ||
return 1; | ||
} | ||
|
||
if (ignore_inheritzero != 1 && | ||
init_fork_detect_inheritzero(addr, page_size) == 1) { | ||
return 1; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
// Best-effort attempt to initialize fork detection methods that provides | ||
// defense-in-depth. These methods should not be relied on for fork detection. | ||
// If initialization fails for one of these methods, just ignore it. | ||
static void init_fork_detect_methods_best_effort(void *addr, long page_size) { | ||
// No methods yet. pthread_at_fork() would be a "best-effort" choice. It can | ||
// detect fork events through e.g. fork(). But miss fork events through | ||
// clone(). | ||
} | ||
|
||
static void init_fork_detect(void) { | ||
|
||
void *addr = NULL; | ||
void *addr = MAP_FAILED; | ||
long page_size = 0; | ||
|
||
if (ignore_all_fork_detection() == 1) { | ||
return; | ||
} | ||
|
||
// Check whether we are completely ignoring fork detection. This is only done | ||
// during testing. | ||
if (ignore_madv_wipeonfork == 1) { | ||
page_size = sysconf(_SC_PAGESIZE); | ||
if (page_size <= 0) { | ||
return; | ||
} | ||
|
||
if (init_fork_detect_madv_wipeonfork(&addr) != 1) { | ||
addr = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE, | ||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | ||
if (addr == MAP_FAILED) { | ||
torben-hansen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
|
||
// First attempt to initialize a method that is sufficient to detect fork | ||
// events. If we can't initialize one such method, return without setting | ||
// |fork_detect_addr|. This means that fork detection is not available. | ||
if (init_fork_detect_methods_sufficient(addr, page_size) != 1) { | ||
// No reason to verify return value of munmap() since we can't use that | ||
// information for anything anyway. | ||
munmap(addr, (size_t) page_size); | ||
addr = NULL; | ||
return; | ||
} | ||
|
||
// Next, try to initialize any defense-in-depth fork detection methods that | ||
// might be available. Fail-open. | ||
init_fork_detect_methods_best_effort(addr, page_size); | ||
|
||
*((volatile char *) addr) = 1; | ||
fork_detect_addr = addr; | ||
fork_generation = 1; | ||
|
@@ -107,15 +212,18 @@ uint64_t CRYPTO_get_fork_generation(void) { | |
// 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. | ||
// In the limit, the kernel may clear e.g. WIPEONFORK pages while a | ||
// multi-threaded process is running. (For example, because a VM was cloned.) | ||
// Therefore a lock is used below to synchronize the potentially multiple | ||
// threads that may concurrently observe the cleared flag. | ||
// | ||
// One cannot convert this to thread-local values to avoid locking. See e.g. | ||
// https://github.com/aws/s2n-tls/issues/3107. | ||
CRYPTO_once(&fork_detect_once, init_fork_detect); | ||
|
||
volatile char *const flag_ptr = fork_detect_addr; | ||
if (flag_ptr == NULL) { | ||
// Our kernel is too old to support |MADV_WIPEONFORK|. | ||
// Our kernel does not support fork detection. | ||
return 0; | ||
} | ||
|
||
|
@@ -146,11 +254,7 @@ uint64_t CRYPTO_get_fork_generation(void) { | |
return current_generation; | ||
} | ||
|
||
void CRYPTO_fork_detect_ignore_madv_wipeonfork_FOR_TESTING(void) { | ||
ignore_madv_wipeonfork = 1; | ||
} | ||
|
||
#elif defined(OPENSSL_WINDOWS) || defined(OPENSSL_TRUSTY) | ||
#elif defined(AWSLC_PLATFORM_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 | ||
|
@@ -166,4 +270,12 @@ uint64_t CRYPTO_get_fork_generation(void) { return 0xc0ffee; } | |
// every RAND_bytes call. | ||
uint64_t CRYPTO_get_fork_generation(void) { return 0; } | ||
torben-hansen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#endif | ||
#endif // defined(AWSLC_FORK_DETECTION_SUPPORTED) | ||
|
||
void CRYPTO_fork_detect_ignore_wipeonfork_FOR_TESTING(void) { | ||
ignore_wipeonfork = 1; | ||
} | ||
|
||
void CRYPTO_fork_detect_ignore_inheritzero_FOR_TESTING(void) { | ||
ignore_inheritzero = 1; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MADV_WIPEONFORK
were not already defined in the headers provided by the system, why do we think it's safe to define the value here?MADV_WIPEONFORK
should be stable for a given target, but how do we know that the value is 18 across all Linux targets?MADV_WIPEONFORK
is not supported by linux kernels prior to v4.14. There are older kernels (like v4.4) that are still actively supported (till January 2027).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the existing behaviour and it has worked flawlessly.
It's just to increase support for the feature that we attempt to use a hard-coded value in case the define doesn't exist.
will fail if the feature is not supported.