#include "utils/s2n_fork_detection_features.h"
#include "utils/s2n_fork_detection.h"
#include "error/s2n_errno.h"
#include "utils/s2n_safety.h"
#if defined(S2N_MADVISE_SUPPORTED) && defined(MADV_WIPEONFORK)
#if (MADV_WIPEONFORK != 18)
#error "MADV_WIPEONFORK is not 18"
#endif
#else
#define MADV_WIPEONFORK 18
#endif
#if !defined(MAP_INHERIT_ZERO) && defined(INHERIT_ZERO)
#define MAP_INHERIT_ZERO INHERIT_ZERO
#endif
static bool ignore_wipeonfork_or_inherit_zero_method_for_testing = false;
static bool ignore_pthread_atfork_method_for_testing = false;
static bool ignore_fork_detection_for_testing = false;
#define S2N_FORK_EVENT 0
#define S2N_NO_FORK_EVENT 1
struct FGN_STATE {
uint64_t current_fork_generation_number;
bool is_fork_detection_enabled;
volatile char *zero_on_fork_addr;
pthread_once_t fork_detection_once;
pthread_rwlock_t fork_detection_rw_lock;
};
static struct FGN_STATE fgn_state = {
.current_fork_generation_number = 0,
.is_fork_detection_enabled = false,
.zero_on_fork_addr = NULL,
.fork_detection_once = PTHREAD_ONCE_INIT,
.fork_detection_rw_lock = PTHREAD_RWLOCK_INITIALIZER,
};
static inline S2N_RESULT s2n_initialise_wipeonfork_best_effort(void *addr, long page_size)
{
#if defined(S2N_MADVISE_SUPPORTED)
madvise(addr, (size_t) page_size, MADV_WIPEONFORK);
#endif
return S2N_RESULT_OK;
}
static inline S2N_RESULT s2n_initialise_inherit_zero(void *addr, long page_size)
{
#if defined(S2N_MINHERIT_SUPPORTED) && defined(MAP_INHERIT_ZERO)
RESULT_ENSURE(minherit(addr, page_size, MAP_INHERIT_ZERO) == 0, S2N_ERR_FORK_DETECTION_INIT);
#endif
return S2N_RESULT_OK;
}
static void s2n_pthread_atfork_on_fork(void)
{
if (pthread_rwlock_wrlock(&fgn_state.fork_detection_rw_lock) != 0) {
printf("pthread_rwlock_wrlock() failed. Aborting.\n");
abort();
}
if (fgn_state.zero_on_fork_addr == NULL) {
printf("fgn_state.zero_on_fork_addr is NULL. Aborting.\n");
abort();
}
*fgn_state.zero_on_fork_addr = 0;
if (pthread_rwlock_unlock(&fgn_state.fork_detection_rw_lock) != 0) {
printf("pthread_rwlock_unlock() failed. Aborting.\n");
abort();
}
}
static S2N_RESULT s2n_inititalise_pthread_atfork(void)
{
if (s2n_is_pthread_atfork_supported() == true) {
RESULT_ENSURE(pthread_atfork(NULL, NULL, s2n_pthread_atfork_on_fork) == 0, S2N_ERR_FORK_DETECTION_INIT);
}
return S2N_RESULT_OK;
}
static S2N_RESULT s2n_initialise_fork_detection_methods_try(void *addr, long page_size)
{
RESULT_GUARD_PTR(addr);
if (ignore_wipeonfork_or_inherit_zero_method_for_testing == false) {
RESULT_GUARD(s2n_initialise_wipeonfork_best_effort(addr, page_size));
}
if (ignore_wipeonfork_or_inherit_zero_method_for_testing == false) {
RESULT_GUARD(s2n_initialise_inherit_zero(addr, page_size));
}
if (ignore_pthread_atfork_method_for_testing == false) {
RESULT_GUARD(s2n_inititalise_pthread_atfork());
}
fgn_state.zero_on_fork_addr = addr;
*fgn_state.zero_on_fork_addr = S2N_NO_FORK_EVENT;
fgn_state.is_fork_detection_enabled = true;
return S2N_RESULT_OK;
}
static S2N_RESULT s2n_setup_mapping(void **addr, long *page_size)
{
*page_size = sysconf(_SC_PAGESIZE);
RESULT_ENSURE_GT(*page_size, 0);
*addr = mmap(NULL, (size_t) *page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RESULT_ENSURE_NE(*addr, MAP_FAILED);
return S2N_RESULT_OK;
}
static void s2n_initialise_fork_detection_methods(void)
{
void *addr = MAP_FAILED;
long page_size = 0;
if (ignore_wipeonfork_or_inherit_zero_method_for_testing == true && ignore_pthread_atfork_method_for_testing == true) {
ignore_fork_detection_for_testing = true;
return;
}
if (s2n_result_is_error(s2n_setup_mapping(&addr, &page_size)) == true) {
return;
}
if (s2n_result_is_error(s2n_initialise_fork_detection_methods_try(addr, page_size)) == true) {
munmap(addr, (size_t) page_size);
addr = NULL;
fgn_state.zero_on_fork_addr = NULL;
fgn_state.is_fork_detection_enabled = false;
}
}
S2N_RESULT s2n_get_fork_generation_number(uint64_t *return_fork_generation_number)
{
RESULT_ENSURE(pthread_once(&fgn_state.fork_detection_once, s2n_initialise_fork_detection_methods) == 0, S2N_ERR_FORK_DETECTION_INIT);
if (ignore_fork_detection_for_testing == true) {
RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
return S2N_RESULT_OK;
}
RESULT_ENSURE(fgn_state.is_fork_detection_enabled == true, S2N_ERR_FORK_DETECTION_INIT);
RESULT_ENSURE(pthread_rwlock_rdlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
*return_fork_generation_number = fgn_state.current_fork_generation_number;
if (*fgn_state.zero_on_fork_addr != S2N_FORK_EVENT) {
RESULT_ENSURE(pthread_rwlock_unlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
return S2N_RESULT_OK;
}
RESULT_ENSURE(pthread_rwlock_unlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
RESULT_ENSURE(pthread_rwlock_wrlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
*return_fork_generation_number = fgn_state.current_fork_generation_number;
if (*fgn_state.zero_on_fork_addr == S2N_FORK_EVENT) {
*fgn_state.zero_on_fork_addr = S2N_NO_FORK_EVENT;
fgn_state.current_fork_generation_number = fgn_state.current_fork_generation_number + 1;
*return_fork_generation_number = fgn_state.current_fork_generation_number;
}
RESULT_ENSURE(pthread_rwlock_unlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
return S2N_RESULT_OK;
}
static void s2n_cleanup_cb_munmap(void **probe_addr)
{
munmap(*probe_addr, (size_t) sysconf(_SC_PAGESIZE));
}
static S2N_RESULT s2n_probe_madv_wipeonfork_support(void)
{
bool result = false;
DEFER_CLEANUP(void *probe_addr = MAP_FAILED, s2n_cleanup_cb_munmap);
long page_size = 0;
RESULT_GUARD(s2n_setup_mapping(&probe_addr, &page_size));
#if defined(S2N_MADVISE_SUPPORTED)
RESULT_ENSURE_NE(madvise(probe_addr, (size_t) page_size, -1), 0);
RESULT_ENSURE_EQ(madvise(probe_addr, (size_t) page_size, MADV_WIPEONFORK), 0);
result = true;
#endif
RESULT_ENSURE_EQ(result, true);
return S2N_RESULT_OK;
}
bool s2n_is_madv_wipeonfork_supported(void)
{
return s2n_result_is_ok(s2n_probe_madv_wipeonfork_support());
}
bool s2n_is_map_inherit_zero_supported(void)
{
#if defined(S2N_MINHERIT_SUPPORTED) && defined(MAP_INHERIT_ZERO)
return true;
#else
return false;
#endif
}
bool s2n_is_pthread_atfork_supported(void)
{
#if defined(__OpenBSD__)
return false;
#else
return true;
#endif
}
S2N_RESULT s2n_ignore_wipeonfork_and_inherit_zero_for_testing(void)
{
RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
ignore_wipeonfork_or_inherit_zero_method_for_testing = true;
return S2N_RESULT_OK;
}
S2N_RESULT s2n_ignore_pthread_atfork_for_testing(void)
{
RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
ignore_pthread_atfork_method_for_testing = true;
return S2N_RESULT_OK;
}