diff --git a/CMakeLists.txt b/CMakeLists.txt index a3c1388..872ca6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ CHECK_CXX_SOURCE_COMPILES(" # include #endif #if __has_include() -#include +# include #endif int main() { int entropy = 0; @@ -97,7 +97,6 @@ int main() { return res; } " SNMALLOC_PLATFORM_HAS_GETENTROPY) - # Provide as function so other projects can reuse # FIXME: This modifies some variables that may or may not be the ones that # provide flags and so is broken by design. It should be removed once Verona @@ -123,7 +122,7 @@ endfunction() function(clangformat_targets) # The clang-format tool is installed under a variety of different names. Try # to find a sensible one. Only look for versions 9 explicitly - we don't - # know whether our clang-format file will work with newer versions of the + # know whether our clang-format file will work with newer versions of the # tool. It does not work with older versions as AfterCaseLabel is not supported # in earlier versions. find_program(CLANG_FORMAT NAMES @@ -196,6 +195,7 @@ add_as_define(USE_SNMALLOC_STATS) add_as_define(SNMALLOC_QEMU_WORKAROUND) add_as_define(SNMALLOC_CI_BUILD) add_as_define(SNMALLOC_PLATFORM_HAS_GETENTROPY) + target_compile_definitions(snmalloc INTERFACE $<$:MALLOC_USABLE_SIZE_QUALIFIER=const>) # In debug and CI builds, link the backtrace library so that we can get stack diff --git a/src/pal/pal_linux.h b/src/pal/pal_linux.h index f597ff1..438a746 100644 --- a/src/pal/pal_linux.h +++ b/src/pal/pal_linux.h @@ -4,8 +4,10 @@ # include "../ds/bits.h" # include "pal_posix.h" +# include # include # include +# include extern "C" int puts(const char* str); @@ -18,12 +20,9 @@ namespace snmalloc * Bitmap of PalFeatures flags indicating the optional features that this * PAL supports. * - * Linux does not support any features other than those in a generic POSIX - * platform. This field is declared explicitly to remind anyone who - * extends this PAL that they may need to extend the set of advertised - * features. + * We always make sure that linux has entropy support. */ - static constexpr uint64_t pal_features = PALPOSIX::pal_features; + static constexpr uint64_t pal_features = PALPOSIX::pal_features | Entropy; static constexpr size_t page_size = Aal::aal_name == PowerPC ? 0x10000 : PALPOSIX::page_size; @@ -85,6 +84,122 @@ namespace snmalloc madvise(p, size, MADV_FREE); } } + + static uint64_t get_entropy64() + { + // TODO: If the system call fails then the POSIX PAL calls libc + // functions that can require malloc, which may result in deadlock. + + // SYS_getrandom API stablized since 3.17. + // This fallback implementation is to aid some environments + // where SYS_getrandom is provided in kernel but the libc + // is not providing getentropy interface. + + union + { + uint64_t result; + char buffer[sizeof(uint64_t)]; + }; + ssize_t ret; + + // give a try to SYS_getrandom +# ifdef SYS_getrandom + static std::atomic_bool syscall_not_working = false; + // Relaxed ordering should be fine here. This function will be called + // during early initialisation, which will examine the availability in a + // protected routine. + if (false == syscall_not_working.load(std::memory_order_relaxed)) + { + auto current = std::begin(buffer); + auto target = std::end(buffer); + while (auto length = target - current) + { + // Reading data via syscall from system entropy pool. + // According to both MUSL and GLIBC implementation, getentropy uses + // /dev/urandom (blocking API). + // + // The third argument here indicates: + // 1. `GRND_RANDOM` bit is not set, so the source of entropy will be + // `urandom`. + // 2. `GRND_NONBLOCK` bit is set. Since we are reading from + // `urandom`, this means if the entropy pool is + // not initialised, we will get a EAGAIN. + ret = syscall(SYS_getrandom, current, length, GRND_NONBLOCK); + // check whether are interrupt by a signal + if (SNMALLOC_UNLIKELY(ret < 0)) + { + if (SNMALLOC_UNLIKELY(errno == EAGAIN)) + { + // the system is going through early initialisation: at this stage + // it is very likely that snmalloc is being used in some system + // programs and we do not want to block it. + return reinterpret_cast(&result) ^ + reinterpret_cast(&error); + } + if (errno != EINTR) + { + break; + } + } + else + { + current += ret; + } + } + if (SNMALLOC_UNLIKELY(target != current)) + { + // in this routine, the only possible situations should be ENOSYS + // or EPERM (forbidden by seccomp, for example). + SNMALLOC_ASSERT(errno == ENOSYS || errno == EPERM); + syscall_not_working.store(true, std::memory_order_relaxed); + } + else + { + return result; + } + } +# endif + + // Syscall is not working. + // In this case, it is not a good idea to fallback to std::random_device: + // 1. it may want to use malloc to create a buffer, which causes + // reentrancy problem during initialisation routine. + // 2. some implementations also require libstdc++ to be linked since + // its APIs are not exception-free. + int flags = O_RDONLY; +# if defined(O_CLOEXEC) + flags |= O_CLOEXEC; +# endif + auto fd = open("/dev/urandom", flags, 0); + if (fd > 0) + { + auto current = std::begin(buffer); + auto target = std::end(buffer); + while (auto length = static_cast(target - current)) + { + ret = read(fd, current, length); + if (ret <= 0) + { + if (errno != EAGAIN && errno != EINTR) + { + break; + } + } + else + { + current += ret; + } + } + ret = close(fd); + SNMALLOC_ASSERT(0 == ret); + if (SNMALLOC_LIKELY(target == current)) + { + return result; + } + } + + error("Failed to get system randomness"); + } }; } // namespace snmalloc #endif diff --git a/src/pal/pal_posix.h b/src/pal/pal_posix.h index e75020d..4c8dff2 100644 --- a/src/pal/pal_posix.h +++ b/src/pal/pal_posix.h @@ -19,9 +19,6 @@ #if __has_include() # include #endif -#if __has_include() -# include -#endif extern "C" int puts(const char* str);