From 553925e8ed93fdf035cb1094c8018eb7ea36026f Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Tue, 24 Sep 2024 18:01:46 -0400 Subject: [PATCH 01/12] [libc] implement secure random buffer filling with vDSO --- .../src/__support/OSUtil/linux/CMakeLists.txt | 22 ++ .../src/__support/OSUtil/linux/aarch64/vdso.h | 2 + libc/src/__support/OSUtil/linux/random.cpp | 323 ++++++++++++++++++ libc/src/__support/OSUtil/linux/random.h | 20 ++ libc/src/__support/OSUtil/linux/vdso_sym.h | 9 +- libc/src/__support/OSUtil/linux/x86_64/vdso.h | 2 + libc/src/__support/threads/thread.cpp | 2 +- .../integration/src/__support/CMakeLists.txt | 1 + .../src/__support/OSUtil/CMakeLists.txt | 5 + .../src/__support/OSUtil/linux/CMakeLists.txt | 10 + .../OSUtil/linux/random_fill_test.cpp | 22 ++ 11 files changed, 414 insertions(+), 4 deletions(-) create mode 100644 libc/src/__support/OSUtil/linux/random.cpp create mode 100644 libc/src/__support/OSUtil/linux/random.h create mode 100644 libc/test/integration/src/__support/OSUtil/CMakeLists.txt create mode 100644 libc/test/integration/src/__support/OSUtil/linux/CMakeLists.txt create mode 100644 libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt index 6c7014940407d..4752e326cc153 100644 --- a/libc/src/__support/OSUtil/linux/CMakeLists.txt +++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt @@ -53,3 +53,25 @@ add_object_library( libc.src.errno.errno libc.src.sys.auxv.getauxval ) + +add_object_library( + random + HDRS + random.h + SRCS + random.cpp + DEPENDS + libc.src.sys.random.getrandom + libc.src.sys.mman.mmap + libc.src.sys.mman.munmap + libc.src.unistd.sysconf + libc.src.errno.errno + libc.src.__support.common + libc.src.__support.OSUtil.linux.vdso + libc.src.__support.threads.callonce + libc.src.__support.threads.linux.raw_mutex + libc.src.__support.threads.thread + libc.src.sched.sched_getaffinity + libc.src.sched.__sched_getcpucount +) + diff --git a/libc/src/__support/OSUtil/linux/aarch64/vdso.h b/libc/src/__support/OSUtil/linux/aarch64/vdso.h index 3c4c6205071da..ee5777ad67f6d 100644 --- a/libc/src/__support/OSUtil/linux/aarch64/vdso.h +++ b/libc/src/__support/OSUtil/linux/aarch64/vdso.h @@ -23,6 +23,8 @@ LIBC_INLINE constexpr cpp::string_view symbol_name(VDSOSym sym) { return "__kernel_clock_gettime"; case VDSOSym::ClockGetRes: return "__kernel_clock_getres"; + case VDSOSym::GetRandom: + return "__kernel_getrandom"; default: return ""; } diff --git a/libc/src/__support/OSUtil/linux/random.cpp b/libc/src/__support/OSUtil/linux/random.cpp new file mode 100644 index 0000000000000..612a184bda845 --- /dev/null +++ b/libc/src/__support/OSUtil/linux/random.cpp @@ -0,0 +1,323 @@ +#include "src/__support/OSUtil/linux/random.h" +#include "src/__support/CPP/mutex.h" +#include "src/__support/CPP/new.h" +#include "src/__support/OSUtil/linux/syscall.h" +#include "src/__support/OSUtil/linux/vdso.h" +#include "src/__support/OSUtil/linux/x86_64/vdso.h" +#include "src/__support/libc_assert.h" +#include "src/__support/memory_size.h" +#include "src/__support/threads/callonce.h" +#include "src/__support/threads/linux/callonce.h" +#include "src/__support/threads/linux/raw_mutex.h" +#include "src/errno/libc_errno.h" +#include "src/sched/sched_getaffinity.h" +#include "src/sched/sched_getcpucount.h" +#include "src/stdlib/atexit.h" +#include "src/sys/mman/mmap.h" +#include "src/sys/mman/munmap.h" +#include "src/sys/random/getrandom.h" +#include "src/unistd/sysconf.h" +#include + +namespace LIBC_NAMESPACE_DECL { +namespace { +// errno protection +struct ErrnoProtect { + int backup; + ErrnoProtect() : backup(libc_errno) { libc_errno = 0; } + ~ErrnoProtect() { libc_errno = backup; } +}; + +// parameters for allocating per-thread random state +struct Params { + unsigned size_of_opaque_state; + unsigned mmap_prot; + unsigned mmap_flags; + unsigned reserved[13]; +}; + +// for registering thread-specific atexit callbacks +using Destructor = void(void *); +extern "C" int __cxa_thread_atexit_impl(Destructor *, void *, void *); +extern "C" [[gnu::weak, gnu::visibility("hidden")]] void *__dso_handle = + nullptr; + +class MMapContainer { + void **ptr = nullptr; + void **usage = nullptr; + void **boundary = nullptr; + + internal::SafeMemSize capacity() const { + return internal::SafeMemSize{ + static_cast(reinterpret_cast(boundary) - + reinterpret_cast(ptr))}; + } + + internal::SafeMemSize bytes() const { + return capacity() * internal::SafeMemSize{sizeof(void *)}; + } + + bool initialize() { + internal::SafeMemSize page_size{static_cast(sysconf(_SC_PAGESIZE))}; + if (!page_size.valid()) + return false; + ptr = reinterpret_cast(mmap(nullptr, page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + if (ptr == MAP_FAILED) + return false; + usage = ptr; + boundary = ptr + page_size / sizeof(void *); + return true; + } + + bool grow(size_t additional) { + if (ptr == nullptr) + return initialize(); + + size_t old_capacity = capacity(); + + internal::SafeMemSize target_bytes{additional}; + internal::SafeMemSize new_bytes = bytes(); + target_bytes = target_bytes + size(); + target_bytes = target_bytes * internal::SafeMemSize{sizeof(void *)}; + + if (!target_bytes.valid()) + return false; + while (new_bytes < target_bytes) { + new_bytes = new_bytes * internal::SafeMemSize{static_cast(2)}; + if (!new_bytes.valid()) + return false; + } + + // TODO: migrate to syscall wrapper once it's available + auto result = syscall_impl( + SYS_mremap, bytes(), static_cast(new_bytes), MREMAP_MAYMOVE); + + if (result < 0 && result > -EXEC_PAGESIZE) + return false; + ptr = reinterpret_cast(result); + usage = ptr + old_capacity; + boundary = ptr + new_bytes / sizeof(void *); + return true; + } + +public: + MMapContainer() = default; + ~MMapContainer() { + if (!ptr) + return; + munmap(ptr, bytes()); + } + + bool ensure_space(size_t additional) { + if (usage + additional >= boundary && !grow(additional)) + return false; + return true; + } + + void push_unchecked(void *value) { + LIBC_ASSERT(usage != boundary && "pushing into full container"); + *usage++ = value; + } + + using iterator = void **; + using value_type = void *; + iterator begin() const { return ptr; } + iterator end() const { return usage; } + + bool empty() const { return begin() == end(); } + void *pop() { + LIBC_ASSERT(!empty() && "popping from empty container"); + return *--usage; + } + internal::SafeMemSize size() const { + return internal::SafeMemSize{static_cast( + reinterpret_cast(usage) - reinterpret_cast(ptr))}; + } +}; + +class StateFactory { + RawMutex mutex{}; + MMapContainer allocations{}; + MMapContainer freelist{}; + Params params{}; + size_t states_per_page = 0; + size_t pages_per_allocation = 0; + size_t page_size = 0; + + bool prepare() { + vdso::TypedSymbol vgetrandom; + + if (!vgetrandom) + return false; + + // get the allocation configuration suggested by the kernel + if (vgetrandom(nullptr, 0, 0, ¶ms, ~0UL)) + return false; + + cpu_set_t cs{}; + + if (LIBC_NAMESPACE::sched_getaffinity(0, sizeof(cs), &cs)) + return false; + + internal::SafeMemSize count{static_cast( + LIBC_NAMESPACE::__sched_getcpucount(sizeof(cs), &cs))}; + + internal::SafeMemSize allocation_size = + internal::SafeMemSize{ + static_cast(params.size_of_opaque_state)} * + count; + + page_size = static_cast(sysconf(_SC_PAGESIZE)); + allocation_size = allocation_size.align_up(page_size); + if (!allocation_size.valid()) + return false; + + states_per_page = page_size / params.size_of_opaque_state; + pages_per_allocation = allocation_size / page_size; + + return true; + } + + bool allocate_new_states() { + if (!allocations.ensure_space(1)) + return false; + + // we always ensure the freelist can contain all the allocated states + internal::SafeMemSize total_size = + internal::SafeMemSize{page_size} * + internal::SafeMemSize{pages_per_allocation} * + (internal::SafeMemSize{static_cast(1)} + allocations.size()); + + if (!total_size.valid() || + !freelist.ensure_space(total_size - freelist.size())) + return false; + + auto *new_allocation = + static_cast(mmap(nullptr, page_size * pages_per_allocation, + params.mmap_prot, params.mmap_flags, -1, 0)); + if (new_allocation == MAP_FAILED) + return false; + + for (size_t i = 0; i < pages_per_allocation; ++i) { + auto *page = new_allocation + i * page_size; + for (size_t j = 0; j < states_per_page; ++j) + freelist.push_unchecked(page + j * params.size_of_opaque_state); + } + return true; + } + + static StateFactory *instance() { + alignas(StateFactory) static char storage[sizeof(StateFactory)]{}; + static CallOnceFlag flag = callonce_impl::NOT_CALLED; + static bool valid = false; + callonce(&flag, []() { + auto *factory = new (storage) StateFactory(); + valid = factory->prepare(); + if (valid) + atexit([]() { + auto factory = reinterpret_cast(storage); + factory->~StateFactory(); + valid = false; + }); + }); + return valid ? reinterpret_cast(storage) : nullptr; + } + + void *acquire() { + cpp::lock_guard guard{mutex}; + if (freelist.empty() && !allocate_new_states()) + return nullptr; + return freelist.pop(); + } + void release(void *state) { + cpp::lock_guard guard{mutex}; + // there should be no need to check this pushing + freelist.push_unchecked(state); + } + ~StateFactory() { + for (auto *allocation : allocations) + munmap(allocation, page_size * pages_per_allocation); + } + +public: + static void *acquire_global() { + auto *factory = instance(); + if (!factory) + return nullptr; + return factory->acquire(); + } + static void release_global(void *state) { + auto *factory = instance(); + if (!factory) + return; + factory->release(state); + } + static size_t size_of_opaque_state() { + return instance()->params.size_of_opaque_state; + } +}; + +void *acquire_tls() { + static thread_local void *state = nullptr; + // previous acquire failed, do not try again + if (state == MAP_FAILED) + return nullptr; + // first acquirement + if (state == nullptr) { + state = StateFactory::acquire_global(); + // if still fails, remember the failure + if (state == nullptr) { + state = MAP_FAILED; + return nullptr; + } else { + // register the release callback. + if (__cxa_thread_atexit_impl( + [](void *s) { StateFactory::release_global(s); }, state, + __dso_handle)) { + StateFactory::release_global(state); + state = MAP_FAILED; + return nullptr; + } + } + } + return state; +} + +template void random_fill_impl(F gen, void *buf, size_t size) { + auto *buffer = reinterpret_cast(buf); + while (size > 0) { + ssize_t len = gen(buffer, size); + if (len == -1) { + if (libc_errno == EINTR) + continue; + break; + } + size -= len; + buffer += len; + } +} +} // namespace + +void random_fill(void *buf, size_t size) { + ErrnoProtect protect; + void *state = acquire_tls(); + if (state) { + random_fill_impl( + [state](void *buf, size_t size) { + vdso::TypedSymbol vgetrandom; + return vgetrandom(buf, size, 0, state, + StateFactory::size_of_opaque_state()); + }, + buf, size); + } else { + random_fill_impl( + [](void *buf, size_t size) { + return LIBC_NAMESPACE::getrandom(buf, size, 0); + }, + buf, size); + } +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/__support/OSUtil/linux/random.h b/libc/src/__support/OSUtil/linux/random.h new file mode 100644 index 0000000000000..0e2d51391ec31 --- /dev/null +++ b/libc/src/__support/OSUtil/linux/random.h @@ -0,0 +1,20 @@ +//===-- Utilities for getting secure randomness -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC___SUPPORT_RANDOMNESS_H +#define LLVM_LIBC_SRC___SUPPORT_RANDOMNESS_H + +#include "src/__support/common.h" + +#define __need_size_t +#include + +namespace LIBC_NAMESPACE_DECL { +void random_fill(void *buf, unsigned long size); +} // namespace LIBC_NAMESPACE_DECL +#endif // LLVM_LIBC_SRC___SUPPORT_RANDOMNESS_H diff --git a/libc/src/__support/OSUtil/linux/vdso_sym.h b/libc/src/__support/OSUtil/linux/vdso_sym.h index 968e1536c4d27..2b1cf398369de 100644 --- a/libc/src/__support/OSUtil/linux/vdso_sym.h +++ b/libc/src/__support/OSUtil/linux/vdso_sym.h @@ -19,7 +19,6 @@ struct __kernel_timespec; struct timezone; struct riscv_hwprobe; struct getcpu_cache; -struct cpu_set_t; // NOLINTEND(llvmlibc-implementation-in-namespace) namespace LIBC_NAMESPACE_DECL { @@ -35,7 +34,8 @@ enum class VDSOSym { RTSigReturn, FlushICache, RiscvHwProbe, - VDSOSymCount + GetRandom, + VDSOSymCount, }; template LIBC_INLINE constexpr auto dispatcher() { @@ -58,8 +58,11 @@ template LIBC_INLINE constexpr auto dispatcher() { else if constexpr (sym == VDSOSym::FlushICache) return static_cast(nullptr); else if constexpr (sym == VDSOSym::RiscvHwProbe) - return static_cast(nullptr); + else if constexpr (sym == VDSOSym::GetRandom) + return static_cast( + nullptr); else return static_cast(nullptr); } diff --git a/libc/src/__support/OSUtil/linux/x86_64/vdso.h b/libc/src/__support/OSUtil/linux/x86_64/vdso.h index abe7c33e07cfa..f46fcb038f2e6 100644 --- a/libc/src/__support/OSUtil/linux/x86_64/vdso.h +++ b/libc/src/__support/OSUtil/linux/x86_64/vdso.h @@ -29,6 +29,8 @@ LIBC_INLINE constexpr cpp::string_view symbol_name(VDSOSym sym) { return "__vdso_time"; case VDSOSym::ClockGetRes: return "__vdso_clock_getres"; + case VDSOSym::GetRandom: + return "__vdso_getrandom"; default: return ""; } diff --git a/libc/src/__support/threads/thread.cpp b/libc/src/__support/threads/thread.cpp index dad4f75f092ed..04668dbfcbb63 100644 --- a/libc/src/__support/threads/thread.cpp +++ b/libc/src/__support/threads/thread.cpp @@ -117,7 +117,7 @@ class ThreadAtExitCallbackMgr { int add_callback(AtExitCallback *callback, void *obj) { cpp::lock_guard lock(mtx); - return callback_list.push_back({callback, obj}); + return callback_list.push_back({callback, obj}) ? 0 : -1; } void call() { diff --git a/libc/test/integration/src/__support/CMakeLists.txt b/libc/test/integration/src/__support/CMakeLists.txt index b5b6557e8d689..d2dae7e02a9c5 100644 --- a/libc/test/integration/src/__support/CMakeLists.txt +++ b/libc/test/integration/src/__support/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(threads) +add_subdirectory(OSUtil) if(LIBC_TARGET_OS_IS_GPU) add_subdirectory(GPU) endif() diff --git a/libc/test/integration/src/__support/OSUtil/CMakeLists.txt b/libc/test/integration/src/__support/OSUtil/CMakeLists.txt new file mode 100644 index 0000000000000..5ff1a11aff5c9 --- /dev/null +++ b/libc/test/integration/src/__support/OSUtil/CMakeLists.txt @@ -0,0 +1,5 @@ +add_custom_target(libc-osutil-integration-tests) + +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) + add_subdirectory(${LIBC_TARGET_OS}) +endif() diff --git a/libc/test/integration/src/__support/OSUtil/linux/CMakeLists.txt b/libc/test/integration/src/__support/OSUtil/linux/CMakeLists.txt new file mode 100644 index 0000000000000..a4f15ad837035 --- /dev/null +++ b/libc/test/integration/src/__support/OSUtil/linux/CMakeLists.txt @@ -0,0 +1,10 @@ +add_integration_test( + random_fill_test + SUITE + libc-osutil-integration-tests + SRCS + random_fill_test.cpp + DEPENDS + libc.include.pthread + libc.src.__support.OSUtil.linux.random +) diff --git a/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp b/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp new file mode 100644 index 0000000000000..bde24a1e0d552 --- /dev/null +++ b/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp @@ -0,0 +1,22 @@ +//===-- Tests for pthread_equal -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/OSUtil/linux/random.h" + +#include "test/IntegrationTest/test.h" + +void smoke_test() { + using namespace LIBC_NAMESPACE; + uint32_t buffer; + random_fill(&buffer, sizeof(buffer)); +} + +TEST_MAIN() { + smoke_test(); + return 0; +} From a09f3f3c79da87107166ac3247d7454242d3cbbc Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Tue, 24 Sep 2024 22:38:49 -0400 Subject: [PATCH 02/12] some fix --- libc/src/__support/OSUtil/linux/random.cpp | 17 ++++++++++++++--- .../__support/OSUtil/linux/random_fill_test.cpp | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/libc/src/__support/OSUtil/linux/random.cpp b/libc/src/__support/OSUtil/linux/random.cpp index 612a184bda845..553b481200c29 100644 --- a/libc/src/__support/OSUtil/linux/random.cpp +++ b/libc/src/__support/OSUtil/linux/random.cpp @@ -1,9 +1,15 @@ +//===- Linux implementation of secure random buffer generation --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// #include "src/__support/OSUtil/linux/random.h" #include "src/__support/CPP/mutex.h" #include "src/__support/CPP/new.h" #include "src/__support/OSUtil/linux/syscall.h" #include "src/__support/OSUtil/linux/vdso.h" -#include "src/__support/OSUtil/linux/x86_64/vdso.h" #include "src/__support/libc_assert.h" #include "src/__support/memory_size.h" #include "src/__support/threads/callonce.h" @@ -307,8 +313,13 @@ void random_fill(void *buf, size_t size) { random_fill_impl( [state](void *buf, size_t size) { vdso::TypedSymbol vgetrandom; - return vgetrandom(buf, size, 0, state, - StateFactory::size_of_opaque_state()); + int res = vgetrandom(buf, size, 0, state, + StateFactory::size_of_opaque_state()); + if (res < 0) { + libc_errno = -res; + return -1; + } + return res; }, buf, size); } else { diff --git a/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp b/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp index bde24a1e0d552..4e029e484742e 100644 --- a/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp +++ b/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp @@ -1,4 +1,4 @@ -//===-- Tests for pthread_equal -------------------------------------------===// +//===-- Tests for random_fill ---------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. From d8b722a6427991e73ecc23c635d8ec515b439026 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Tue, 24 Sep 2024 23:11:46 -0400 Subject: [PATCH 03/12] add fork hooks --- libc/src/__support/OSUtil/linux/random.cpp | 75 ++++++++++++++-------- libc/src/__support/OSUtil/linux/random.h | 3 + 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/libc/src/__support/OSUtil/linux/random.cpp b/libc/src/__support/OSUtil/linux/random.cpp index 553b481200c29..13a6c40ce965a 100644 --- a/libc/src/__support/OSUtil/linux/random.cpp +++ b/libc/src/__support/OSUtil/linux/random.cpp @@ -214,22 +214,7 @@ class StateFactory { return true; } - static StateFactory *instance() { - alignas(StateFactory) static char storage[sizeof(StateFactory)]{}; - static CallOnceFlag flag = callonce_impl::NOT_CALLED; - static bool valid = false; - callonce(&flag, []() { - auto *factory = new (storage) StateFactory(); - valid = factory->prepare(); - if (valid) - atexit([]() { - auto factory = reinterpret_cast(storage); - factory->~StateFactory(); - valid = false; - }); - }); - return valid ? reinterpret_cast(storage) : nullptr; - } + static StateFactory *instance(); void *acquire() { cpp::lock_guard guard{mutex}; @@ -263,32 +248,62 @@ class StateFactory { static size_t size_of_opaque_state() { return instance()->params.size_of_opaque_state; } + static void postfork_cleanup(); }; +thread_local bool fork_inflight = false; +thread_local void *tls_state = nullptr; +alignas(StateFactory) static char factory_storage[sizeof(StateFactory)]{}; +static CallOnceFlag factory_onceflag = callonce_impl::NOT_CALLED; +static bool factory_valid = false; + +StateFactory *StateFactory::instance() { + callonce(&factory_onceflag, []() { + auto *factory = new (factory_storage) StateFactory(); + factory_valid = factory->prepare(); + if (factory_valid) + atexit([]() { + auto factory = reinterpret_cast(factory_storage); + factory->~StateFactory(); + factory_valid = false; + }); + }); + return factory_valid ? reinterpret_cast(factory_storage) + : nullptr; +} + +void StateFactory::postfork_cleanup() { + if (factory_valid) + reinterpret_cast(factory_storage)->~StateFactory(); + factory_onceflag = callonce_impl::NOT_CALLED; + factory_valid = false; +} + void *acquire_tls() { - static thread_local void *state = nullptr; + if (fork_inflight) + return nullptr; // previous acquire failed, do not try again - if (state == MAP_FAILED) + if (tls_state == MAP_FAILED) return nullptr; // first acquirement - if (state == nullptr) { - state = StateFactory::acquire_global(); + if (tls_state == nullptr) { + tls_state = StateFactory::acquire_global(); // if still fails, remember the failure - if (state == nullptr) { - state = MAP_FAILED; + if (tls_state == nullptr) { + tls_state = MAP_FAILED; return nullptr; } else { // register the release callback. if (__cxa_thread_atexit_impl( - [](void *s) { StateFactory::release_global(s); }, state, + [](void *s) { StateFactory::release_global(s); }, tls_state, __dso_handle)) { - StateFactory::release_global(state); - state = MAP_FAILED; + StateFactory::release_global(tls_state); + tls_state = MAP_FAILED; return nullptr; } } } - return state; + return tls_state; } template void random_fill_impl(F gen, void *buf, size_t size) { @@ -331,4 +346,12 @@ void random_fill(void *buf, size_t size) { } } +void random_prefork() { fork_inflight = true; } +void random_postfork_parent() { fork_inflight = false; } +void random_postfork_child() { + tls_state = nullptr; + StateFactory::postfork_cleanup(); + fork_inflight = false; +} + } // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/__support/OSUtil/linux/random.h b/libc/src/__support/OSUtil/linux/random.h index 0e2d51391ec31..567d5f3f412f0 100644 --- a/libc/src/__support/OSUtil/linux/random.h +++ b/libc/src/__support/OSUtil/linux/random.h @@ -16,5 +16,8 @@ namespace LIBC_NAMESPACE_DECL { void random_fill(void *buf, unsigned long size); +void random_prefork(); +void random_postfork_parent(); +void random_postfork_child(); } // namespace LIBC_NAMESPACE_DECL #endif // LLVM_LIBC_SRC___SUPPORT_RANDOMNESS_H From 6e95fe9f6d634b774c0671f23defeebb0b24e6f6 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Tue, 24 Sep 2024 23:18:48 -0400 Subject: [PATCH 04/12] make names more meaningful --- libc/src/__support/OSUtil/linux/random.cpp | 38 ++++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/libc/src/__support/OSUtil/linux/random.cpp b/libc/src/__support/OSUtil/linux/random.cpp index 13a6c40ce965a..ab63c4e27e352 100644 --- a/libc/src/__support/OSUtil/linux/random.cpp +++ b/libc/src/__support/OSUtil/linux/random.cpp @@ -35,7 +35,7 @@ struct ErrnoProtect { }; // parameters for allocating per-thread random state -struct Params { +struct RandomStateMMapParams { unsigned size_of_opaque_state; unsigned mmap_prot; unsigned mmap_flags; @@ -143,11 +143,11 @@ class MMapContainer { } }; -class StateFactory { +class RandomStateFactory { RawMutex mutex{}; MMapContainer allocations{}; MMapContainer freelist{}; - Params params{}; + RandomStateMMapParams params{}; size_t states_per_page = 0; size_t pages_per_allocation = 0; size_t page_size = 0; @@ -214,7 +214,7 @@ class StateFactory { return true; } - static StateFactory *instance(); + static RandomStateFactory *instance(); void *acquire() { cpp::lock_guard guard{mutex}; @@ -227,7 +227,7 @@ class StateFactory { // there should be no need to check this pushing freelist.push_unchecked(state); } - ~StateFactory() { + ~RandomStateFactory() { for (auto *allocation : allocations) munmap(allocation, page_size * pages_per_allocation); } @@ -253,28 +253,30 @@ class StateFactory { thread_local bool fork_inflight = false; thread_local void *tls_state = nullptr; -alignas(StateFactory) static char factory_storage[sizeof(StateFactory)]{}; +alignas(RandomStateFactory) static char factory_storage[sizeof( + RandomStateFactory)]{}; static CallOnceFlag factory_onceflag = callonce_impl::NOT_CALLED; static bool factory_valid = false; -StateFactory *StateFactory::instance() { +RandomStateFactory *RandomStateFactory::instance() { callonce(&factory_onceflag, []() { - auto *factory = new (factory_storage) StateFactory(); + auto *factory = new (factory_storage) RandomStateFactory(); factory_valid = factory->prepare(); if (factory_valid) atexit([]() { - auto factory = reinterpret_cast(factory_storage); - factory->~StateFactory(); + auto factory = reinterpret_cast(factory_storage); + factory->~RandomStateFactory(); factory_valid = false; }); }); - return factory_valid ? reinterpret_cast(factory_storage) + return factory_valid ? reinterpret_cast(factory_storage) : nullptr; } -void StateFactory::postfork_cleanup() { +void RandomStateFactory::postfork_cleanup() { if (factory_valid) - reinterpret_cast(factory_storage)->~StateFactory(); + reinterpret_cast(factory_storage) + ->~RandomStateFactory(); factory_onceflag = callonce_impl::NOT_CALLED; factory_valid = false; } @@ -287,7 +289,7 @@ void *acquire_tls() { return nullptr; // first acquirement if (tls_state == nullptr) { - tls_state = StateFactory::acquire_global(); + tls_state = RandomStateFactory::acquire_global(); // if still fails, remember the failure if (tls_state == nullptr) { tls_state = MAP_FAILED; @@ -295,9 +297,9 @@ void *acquire_tls() { } else { // register the release callback. if (__cxa_thread_atexit_impl( - [](void *s) { StateFactory::release_global(s); }, tls_state, + [](void *s) { RandomStateFactory::release_global(s); }, tls_state, __dso_handle)) { - StateFactory::release_global(tls_state); + RandomStateFactory::release_global(tls_state); tls_state = MAP_FAILED; return nullptr; } @@ -329,7 +331,7 @@ void random_fill(void *buf, size_t size) { [state](void *buf, size_t size) { vdso::TypedSymbol vgetrandom; int res = vgetrandom(buf, size, 0, state, - StateFactory::size_of_opaque_state()); + RandomStateFactory::size_of_opaque_state()); if (res < 0) { libc_errno = -res; return -1; @@ -350,7 +352,7 @@ void random_prefork() { fork_inflight = true; } void random_postfork_parent() { fork_inflight = false; } void random_postfork_child() { tls_state = nullptr; - StateFactory::postfork_cleanup(); + RandomStateFactory::postfork_cleanup(); fork_inflight = false; } From 2b81034fc59dad594f9de2e5e138cfb82aed49f3 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Tue, 1 Oct 2024 21:04:02 -0400 Subject: [PATCH 05/12] renew --- .../src/__support/OSUtil/linux/CMakeLists.txt | 17 +- libc/src/__support/OSUtil/linux/cprng.cpp | 332 ++++++++++++++++ libc/src/__support/OSUtil/linux/cprng.h | 54 +++ libc/src/__support/OSUtil/linux/random.cpp | 359 ------------------ libc/src/__support/OSUtil/linux/random.h | 23 -- .../integration/src/__support/CMakeLists.txt | 1 - .../src/__support/OSUtil/CMakeLists.txt | 5 - .../src/__support/OSUtil/linux/CMakeLists.txt | 10 - .../OSUtil/linux/random_fill_test.cpp | 22 -- .../src/__support/OSUtil/linux/CMakeLists.txt | 8 + .../src/__support/OSUtil/linux/cprng_test.cpp | 25 ++ 11 files changed, 427 insertions(+), 429 deletions(-) create mode 100644 libc/src/__support/OSUtil/linux/cprng.cpp create mode 100644 libc/src/__support/OSUtil/linux/cprng.h delete mode 100644 libc/src/__support/OSUtil/linux/random.cpp delete mode 100644 libc/src/__support/OSUtil/linux/random.h delete mode 100644 libc/test/integration/src/__support/OSUtil/CMakeLists.txt delete mode 100644 libc/test/integration/src/__support/OSUtil/linux/CMakeLists.txt delete mode 100644 libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp create mode 100644 libc/test/src/__support/OSUtil/linux/cprng_test.cpp diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt index 4752e326cc153..447e4c04211bc 100644 --- a/libc/src/__support/OSUtil/linux/CMakeLists.txt +++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt @@ -55,22 +55,21 @@ add_object_library( ) add_object_library( - random + cprng HDRS - random.h + cprng.h SRCS - random.cpp + cprng.cpp DEPENDS - libc.src.sys.random.getrandom - libc.src.sys.mman.mmap - libc.src.sys.mman.munmap - libc.src.unistd.sysconf - libc.src.errno.errno libc.src.__support.common libc.src.__support.OSUtil.linux.vdso libc.src.__support.threads.callonce libc.src.__support.threads.linux.raw_mutex - libc.src.__support.threads.thread + libc.src.__support.CPP.optional + libc.src.__support.CPP.new + libc.src.sys.mman.mmap + libc.src.sys.mman.munmap + libc.src.unistd.sysconf libc.src.sched.sched_getaffinity libc.src.sched.__sched_getcpucount ) diff --git a/libc/src/__support/OSUtil/linux/cprng.cpp b/libc/src/__support/OSUtil/linux/cprng.cpp new file mode 100644 index 0000000000000..02ee3e12f4781 --- /dev/null +++ b/libc/src/__support/OSUtil/linux/cprng.cpp @@ -0,0 +1,332 @@ +//===- Linux implementation of secure random buffer generation --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "src/__support/OSUtil/linux/cprng.h" +#include "src/__support/CPP/atomic.h" +#include "src/__support/CPP/mutex.h" +#include "src/__support/CPP/new.h" +#include "src/__support/OSUtil/linux/syscall.h" +#include "src/__support/OSUtil/linux/vdso.h" +#include "src/__support/block.h" +#include "src/__support/libc_assert.h" +#include "src/__support/threads/callonce.h" +#include "src/__support/threads/linux/raw_mutex.h" +#include "src/sched/sched_getaffinity.h" +#include "src/sched/sched_getcpucount.h" +#include "src/sys/mman/mmap.h" +#include "src/sys/mman/munmap.h" +#include "src/unistd/sysconf.h" + +extern "C" int __cxa_thread_atexit_impl(void (*)(void *), void *, void *); +extern "C" int __cxa_atexit(void (*)(void *), void *, void *); +extern "C" [[gnu::weak, gnu::visibility("hidden")]] void *__dso_handle = + nullptr; + +namespace LIBC_NAMESPACE_DECL { +namespace cprng { +namespace { + +using namespace vdso; +// A block of random state together with enough space to hold all its freelist. +struct StateBlock { + StateBlock *prev; + StateBlock *next; + void *appendix[0]; + void *&pages() { return appendix[0]; } + void *&freelist(size_t index) { return appendix[index + 1]; } +}; + +struct vgetrandom_opaque_params { + unsigned size_of_opaque_state = 0; + unsigned mmap_prot = 0; + unsigned mmap_flags = 0; + unsigned reserved[13]; +}; + +class GlobalConfig { +public: + const size_t page_size = 0; + const size_t pages_per_block = 0; + const size_t states_per_page = 0; + const vgetrandom_opaque_params params = {}; + +private: + static size_t guess_cpu_count() { + cpu_set_t cpuset{}; + if (LIBC_NAMESPACE::sched_getaffinity(0, sizeof(cpuset), &cpuset)) + return 1u; + int count = LIBC_NAMESPACE::__sched_getcpucount(sizeof(cpu_set_t), &cpuset); + return static_cast(count > 1 ? count : 1); + } + +private: + constexpr GlobalConfig(size_t page_size, size_t pages_per_block, + size_t states_per_page, + vgetrandom_opaque_params params) + : page_size(page_size), pages_per_block(pages_per_block), + states_per_page(states_per_page), params(params) {} + +public: + static cpp::optional get() { + size_t page_size = 0; + size_t states_per_page = 0; + size_t pages_per_block = 0; + vgetrandom_opaque_params params = {}; + + // check symbol availability + TypedSymbol vgetrandom; + if (!vgetrandom) + return cpp::nullopt; + + // get valid page size + long page_size_res = sysconf(_SC_PAGESIZE); + if (page_size_res <= 0) + return cpp::nullopt; + page_size = static_cast(page_size_res); + + // get parameters for state allocation + if (vgetrandom(nullptr, 0, 0, ¶ms, ~0U)) + return cpp::nullopt; + + // Compute the number of states per page. On a valid human-constructable + // computer as year 2024, the following operations shall not overflow given + // the above operations are correctly returned. + size_t guessed_bytes = guess_cpu_count() * params.size_of_opaque_state; + size_t aligned_bytes = align_up(guessed_bytes, page_size); + states_per_page = page_size / params.size_of_opaque_state; + pages_per_block = aligned_bytes / page_size; + return GlobalConfig(page_size, pages_per_block, states_per_page, params); + } +}; + +// Utilities to allocate memory and hold it temporarily. +template struct Allocation { + T *ptr; + size_t raw_size; + Allocation(size_t raw_size) : ptr(nullptr), raw_size(raw_size) { + AllocChecker ac{}; + ptr = static_cast( + /* NOLINT(llvmlibc-callee-namespace) */ ::operator new(raw_size, ac)); + if (!ac) + ptr = nullptr; + } + T *operator->() { return ptr; } + operator bool() const { return ptr != nullptr; } + ~Allocation() { + if (ptr) + /* NOLINT(llvmlibc-callee-namespace )*/ ::operator delete(ptr, raw_size); + } + void release() { ptr = nullptr; } +}; + +// A monotonic state pool. +class MonotonicStatePool { + RawMutex mutex; + GlobalConfig config; + StateBlock sentinel; + StateBlock *free_cursor; + // invariant: at sentinel, free_count is always locally full such that we + // don't need to specially check the condition for sentinel when recycling. + size_t free_count; + + constexpr MonotonicStatePool(GlobalConfig config) + : mutex(), config(config), sentinel{&sentinel, &sentinel, {}}, + free_cursor(&sentinel), + free_count(config.pages_per_block * config.states_per_page) {} + MonotonicStatePool(const MonotonicStatePool &) = delete; + MonotonicStatePool(MonotonicStatePool &&) = delete; + + bool is_empty() const { return free_cursor == &sentinel; } + bool is_locally_full() const { + return free_count == config.pages_per_block * config.states_per_page; + } + size_t state_block_raw_size() const { + return sizeof(StateBlock) + + (config.states_per_page * config.pages_per_block + 1) * + sizeof(void *); + } + bool allocate_new_block() { + LIBC_ASSERT(is_empty()); + // allocate a new block + size_t raw_size = state_block_raw_size(); + Allocation block(raw_size); + if (!block) + return false; + // allocate associated pages + auto pages = static_cast( + mmap(nullptr, config.pages_per_block * config.page_size, + config.params.mmap_prot, config.params.mmap_flags, -1, 0)); + if (pages == MAP_FAILED) + return false; + // populate the block + block->pages() = pages; + size_t state_idx = 0; + for (size_t p = 0; p < config.pages_per_block; ++p) { + char *page = pages + p * config.page_size; + for (size_t s = 0; s < config.states_per_page; ++s) { + block->freelist(state_idx++) = + page + s * config.params.size_of_opaque_state; + } + } + // link the block to the sentinel + block->next = sentinel.next; + block->prev = &sentinel; + block->next->prev = block.ptr; + block->prev->next = block.ptr; + // update the cursor + free_cursor = block.ptr; + free_count = state_idx; + // release the ownership of allocation + block.release(); + return true; + } + +public: + void *get() { + cpp::lock_guard guard{this->mutex}; + // if freelist is empty, allocate a new block + if (is_empty() && !allocate_new_block()) + return nullptr; + LIBC_ASSERT(free_count != 0); + void *page = free_cursor->freelist(--free_count); + // move cursor to the previous block if free_count is exhausted + if (free_count == 0) { + free_cursor = free_cursor->prev; + free_count = config.states_per_page * config.pages_per_block; + } + return page; + } + size_t state_size() const { return config.params.size_of_opaque_state; } + void recycle(void *state) { + cpp::lock_guard guard{this->mutex}; + if (is_locally_full()) { + free_cursor = free_cursor->next; + free_count = 0; + } + free_cursor->freelist(free_count++) = static_cast(state); + } + ~MonotonicStatePool() { + StateBlock *block = sentinel.next; + size_t raw_size = state_block_raw_size(); + while (block != &sentinel) { + munmap(block->pages(), config.pages_per_block * config.page_size); + StateBlock *next = block->next; + /* NOLINT(llvmlibc-callee-namespace) */ ::operator delete( + block, raw_size, std::align_val_t{alignof(StateBlock)}); + block = next; + } + } + static MonotonicStatePool *instance() { + alignas(MonotonicStatePool) static char pool[sizeof(MonotonicStatePool)]; + static bool is_valid = false; + static CallOnceFlag once_flag = callonce_impl::NOT_CALLED; + callonce(&once_flag, []() { + cpp::optional config = GlobalConfig::get(); + if (!config) + return; + new (pool) MonotonicStatePool(*config); + is_valid = /* NOLINT(llvmlibc-callee-namespace) */ !__cxa_atexit( + [](void *) { + reinterpret_cast(pool)->~MonotonicStatePool(); + }, + nullptr, __dso_handle); + }); + if (!is_valid) + return nullptr; + return reinterpret_cast(pool); + } +}; + +// We do not guarantee the correctness of calling the cprng functions in signal +// frames. However, we do want to make sure that an mistaken (maliciously +// induced) call to the cprng will never cause a state to be used twice due to +// reentrancy. +class ThreadLocalState { + void *local_state; + bool in_use; + size_t remaining_trials; + + void destroy() { + in_use = true; + cpp::atomic_signal_fence(cpp::MemoryOrder::SEQ_CST); + if (local_state) + MonotonicStatePool::instance()->recycle(local_state); + } + +public: + constexpr ThreadLocalState() + : local_state(nullptr), in_use(false), remaining_trials(256) {} + void *acquire() { + if (in_use) + return nullptr; + in_use = true; + cpp::atomic_signal_fence(cpp::MemoryOrder::SEQ_CST); + if (local_state) + return local_state; + + MonotonicStatePool *pool = MonotonicStatePool::instance(); + if (pool && remaining_trials) { + remaining_trials--; + local_state = pool->get(); + if (local_state) { + if (/* NOLINT(llvmlibc-callee-namespace) */ __cxa_thread_atexit_impl( + [](void *opaque) { + auto *state = static_cast(opaque); + state->destroy(); + }, + this, __dso_handle) != 0) { + pool->recycle(local_state); + local_state = nullptr; + } + } + } + return local_state; + } + void release() { + in_use = false; + cpp::atomic_signal_fence(cpp::MemoryOrder::SEQ_CST); + } +}; +thread_local ThreadLocalState local_state{}; + +template +size_t fill_buffer_impl(G generator, char *buffer, size_t length) { + size_t filled = 0; + while (filled < length) { + int n = generator(&buffer[filled], length - filled); + if (n < 0) { + if (n == -EAGAIN || n == -EINTR) + continue; + break; + } + filled += static_cast(n); + } + return filled; +} + +} // namespace +size_t fill_buffer(char *buffer, size_t length) { + void *state = local_state.acquire(); + if (state) { + // Given state is valid, state_size shall be valid. + size_t state_size = MonotonicStatePool::instance()->state_size(); + auto impl = [state, state_size](char *cursor, size_t len) { + TypedSymbol vgetrandom; + return vgetrandom(cursor, len, 0, state, state_size); + }; + return fill_buffer_impl(impl, buffer, length); + } else { + auto impl = [](char *cursor, size_t len) { + // use syscall to avoid errno handling + return syscall_impl(SYS_getrandom, cursor, len, 0); + }; + return fill_buffer_impl(impl, buffer, length); + } + local_state.release(); +} +} // namespace cprng +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/__support/OSUtil/linux/cprng.h b/libc/src/__support/OSUtil/linux/cprng.h new file mode 100644 index 0000000000000..e2075805eb81c --- /dev/null +++ b/libc/src/__support/OSUtil/linux/cprng.h @@ -0,0 +1,54 @@ +//===-- Utilities for getting secure randomness -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC___SUPPORT_CPRNG_H +#define LLVM_LIBC_SRC___SUPPORT_CPRNG_H + +#include "src/__support/CPP/optional.h" +#include "src/__support/CPP/type_traits.h" +#include "src/__support/common.h" + +#define __need_size_t +#include + +namespace LIBC_NAMESPACE_DECL { +namespace cprng { +size_t fill_buffer(char *buffer, size_t length); + +template LIBC_INLINE cpp::optional generate() { + static_assert(cpp::is_arithmetic_v, "T must be an arithmetic type."); + union { + T result; + char bytes[sizeof(T)]; + }; + if (fill_buffer(bytes, sizeof(bytes)) == sizeof(bytes)) + return result; + return cpp::nullopt; +} + +// based on https://jacquesheunis.com/post/bounded-random/ +LIBC_INLINE cpp::optional generate_bounded_u32(uint32_t bound) { + auto lifted_bound = static_cast(bound); + cpp::optional seed = generate(); + if (!seed) + return cpp::nullopt; + uint64_t r0 = *seed & 0xFFFFFFFFu; + uint64_t r1 = (*seed >> 32) & 0xFFFFFFFFu; + uint64_t prod0 = static_cast(r0) * lifted_bound; + uint64_t prod0_hi = (prod0 >> 32) & 0xFFFFFFFFu; + uint64_t prod0_lo = prod0 & 0xFFFFFFFFu; + uint64_t prod1 = static_cast(r1) * lifted_bound; + uint64_t prod1_hi = (prod1 >> 32) & 0xFFFFFFFFu; + uint64_t sum = prod0_lo + prod1_hi; + uint64_t sum_hi = (sum >> 32) & 0xFFFFFFFFu; + return static_cast(prod0_hi + sum_hi); +} + +} // namespace cprng +} // namespace LIBC_NAMESPACE_DECL +#endif // LLVM_LIBC_SRC___SUPPORT_CPRNG_H diff --git a/libc/src/__support/OSUtil/linux/random.cpp b/libc/src/__support/OSUtil/linux/random.cpp deleted file mode 100644 index ab63c4e27e352..0000000000000 --- a/libc/src/__support/OSUtil/linux/random.cpp +++ /dev/null @@ -1,359 +0,0 @@ -//===- Linux implementation of secure random buffer generation --*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -#include "src/__support/OSUtil/linux/random.h" -#include "src/__support/CPP/mutex.h" -#include "src/__support/CPP/new.h" -#include "src/__support/OSUtil/linux/syscall.h" -#include "src/__support/OSUtil/linux/vdso.h" -#include "src/__support/libc_assert.h" -#include "src/__support/memory_size.h" -#include "src/__support/threads/callonce.h" -#include "src/__support/threads/linux/callonce.h" -#include "src/__support/threads/linux/raw_mutex.h" -#include "src/errno/libc_errno.h" -#include "src/sched/sched_getaffinity.h" -#include "src/sched/sched_getcpucount.h" -#include "src/stdlib/atexit.h" -#include "src/sys/mman/mmap.h" -#include "src/sys/mman/munmap.h" -#include "src/sys/random/getrandom.h" -#include "src/unistd/sysconf.h" -#include - -namespace LIBC_NAMESPACE_DECL { -namespace { -// errno protection -struct ErrnoProtect { - int backup; - ErrnoProtect() : backup(libc_errno) { libc_errno = 0; } - ~ErrnoProtect() { libc_errno = backup; } -}; - -// parameters for allocating per-thread random state -struct RandomStateMMapParams { - unsigned size_of_opaque_state; - unsigned mmap_prot; - unsigned mmap_flags; - unsigned reserved[13]; -}; - -// for registering thread-specific atexit callbacks -using Destructor = void(void *); -extern "C" int __cxa_thread_atexit_impl(Destructor *, void *, void *); -extern "C" [[gnu::weak, gnu::visibility("hidden")]] void *__dso_handle = - nullptr; - -class MMapContainer { - void **ptr = nullptr; - void **usage = nullptr; - void **boundary = nullptr; - - internal::SafeMemSize capacity() const { - return internal::SafeMemSize{ - static_cast(reinterpret_cast(boundary) - - reinterpret_cast(ptr))}; - } - - internal::SafeMemSize bytes() const { - return capacity() * internal::SafeMemSize{sizeof(void *)}; - } - - bool initialize() { - internal::SafeMemSize page_size{static_cast(sysconf(_SC_PAGESIZE))}; - if (!page_size.valid()) - return false; - ptr = reinterpret_cast(mmap(nullptr, page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - if (ptr == MAP_FAILED) - return false; - usage = ptr; - boundary = ptr + page_size / sizeof(void *); - return true; - } - - bool grow(size_t additional) { - if (ptr == nullptr) - return initialize(); - - size_t old_capacity = capacity(); - - internal::SafeMemSize target_bytes{additional}; - internal::SafeMemSize new_bytes = bytes(); - target_bytes = target_bytes + size(); - target_bytes = target_bytes * internal::SafeMemSize{sizeof(void *)}; - - if (!target_bytes.valid()) - return false; - while (new_bytes < target_bytes) { - new_bytes = new_bytes * internal::SafeMemSize{static_cast(2)}; - if (!new_bytes.valid()) - return false; - } - - // TODO: migrate to syscall wrapper once it's available - auto result = syscall_impl( - SYS_mremap, bytes(), static_cast(new_bytes), MREMAP_MAYMOVE); - - if (result < 0 && result > -EXEC_PAGESIZE) - return false; - ptr = reinterpret_cast(result); - usage = ptr + old_capacity; - boundary = ptr + new_bytes / sizeof(void *); - return true; - } - -public: - MMapContainer() = default; - ~MMapContainer() { - if (!ptr) - return; - munmap(ptr, bytes()); - } - - bool ensure_space(size_t additional) { - if (usage + additional >= boundary && !grow(additional)) - return false; - return true; - } - - void push_unchecked(void *value) { - LIBC_ASSERT(usage != boundary && "pushing into full container"); - *usage++ = value; - } - - using iterator = void **; - using value_type = void *; - iterator begin() const { return ptr; } - iterator end() const { return usage; } - - bool empty() const { return begin() == end(); } - void *pop() { - LIBC_ASSERT(!empty() && "popping from empty container"); - return *--usage; - } - internal::SafeMemSize size() const { - return internal::SafeMemSize{static_cast( - reinterpret_cast(usage) - reinterpret_cast(ptr))}; - } -}; - -class RandomStateFactory { - RawMutex mutex{}; - MMapContainer allocations{}; - MMapContainer freelist{}; - RandomStateMMapParams params{}; - size_t states_per_page = 0; - size_t pages_per_allocation = 0; - size_t page_size = 0; - - bool prepare() { - vdso::TypedSymbol vgetrandom; - - if (!vgetrandom) - return false; - - // get the allocation configuration suggested by the kernel - if (vgetrandom(nullptr, 0, 0, ¶ms, ~0UL)) - return false; - - cpu_set_t cs{}; - - if (LIBC_NAMESPACE::sched_getaffinity(0, sizeof(cs), &cs)) - return false; - - internal::SafeMemSize count{static_cast( - LIBC_NAMESPACE::__sched_getcpucount(sizeof(cs), &cs))}; - - internal::SafeMemSize allocation_size = - internal::SafeMemSize{ - static_cast(params.size_of_opaque_state)} * - count; - - page_size = static_cast(sysconf(_SC_PAGESIZE)); - allocation_size = allocation_size.align_up(page_size); - if (!allocation_size.valid()) - return false; - - states_per_page = page_size / params.size_of_opaque_state; - pages_per_allocation = allocation_size / page_size; - - return true; - } - - bool allocate_new_states() { - if (!allocations.ensure_space(1)) - return false; - - // we always ensure the freelist can contain all the allocated states - internal::SafeMemSize total_size = - internal::SafeMemSize{page_size} * - internal::SafeMemSize{pages_per_allocation} * - (internal::SafeMemSize{static_cast(1)} + allocations.size()); - - if (!total_size.valid() || - !freelist.ensure_space(total_size - freelist.size())) - return false; - - auto *new_allocation = - static_cast(mmap(nullptr, page_size * pages_per_allocation, - params.mmap_prot, params.mmap_flags, -1, 0)); - if (new_allocation == MAP_FAILED) - return false; - - for (size_t i = 0; i < pages_per_allocation; ++i) { - auto *page = new_allocation + i * page_size; - for (size_t j = 0; j < states_per_page; ++j) - freelist.push_unchecked(page + j * params.size_of_opaque_state); - } - return true; - } - - static RandomStateFactory *instance(); - - void *acquire() { - cpp::lock_guard guard{mutex}; - if (freelist.empty() && !allocate_new_states()) - return nullptr; - return freelist.pop(); - } - void release(void *state) { - cpp::lock_guard guard{mutex}; - // there should be no need to check this pushing - freelist.push_unchecked(state); - } - ~RandomStateFactory() { - for (auto *allocation : allocations) - munmap(allocation, page_size * pages_per_allocation); - } - -public: - static void *acquire_global() { - auto *factory = instance(); - if (!factory) - return nullptr; - return factory->acquire(); - } - static void release_global(void *state) { - auto *factory = instance(); - if (!factory) - return; - factory->release(state); - } - static size_t size_of_opaque_state() { - return instance()->params.size_of_opaque_state; - } - static void postfork_cleanup(); -}; - -thread_local bool fork_inflight = false; -thread_local void *tls_state = nullptr; -alignas(RandomStateFactory) static char factory_storage[sizeof( - RandomStateFactory)]{}; -static CallOnceFlag factory_onceflag = callonce_impl::NOT_CALLED; -static bool factory_valid = false; - -RandomStateFactory *RandomStateFactory::instance() { - callonce(&factory_onceflag, []() { - auto *factory = new (factory_storage) RandomStateFactory(); - factory_valid = factory->prepare(); - if (factory_valid) - atexit([]() { - auto factory = reinterpret_cast(factory_storage); - factory->~RandomStateFactory(); - factory_valid = false; - }); - }); - return factory_valid ? reinterpret_cast(factory_storage) - : nullptr; -} - -void RandomStateFactory::postfork_cleanup() { - if (factory_valid) - reinterpret_cast(factory_storage) - ->~RandomStateFactory(); - factory_onceflag = callonce_impl::NOT_CALLED; - factory_valid = false; -} - -void *acquire_tls() { - if (fork_inflight) - return nullptr; - // previous acquire failed, do not try again - if (tls_state == MAP_FAILED) - return nullptr; - // first acquirement - if (tls_state == nullptr) { - tls_state = RandomStateFactory::acquire_global(); - // if still fails, remember the failure - if (tls_state == nullptr) { - tls_state = MAP_FAILED; - return nullptr; - } else { - // register the release callback. - if (__cxa_thread_atexit_impl( - [](void *s) { RandomStateFactory::release_global(s); }, tls_state, - __dso_handle)) { - RandomStateFactory::release_global(tls_state); - tls_state = MAP_FAILED; - return nullptr; - } - } - } - return tls_state; -} - -template void random_fill_impl(F gen, void *buf, size_t size) { - auto *buffer = reinterpret_cast(buf); - while (size > 0) { - ssize_t len = gen(buffer, size); - if (len == -1) { - if (libc_errno == EINTR) - continue; - break; - } - size -= len; - buffer += len; - } -} -} // namespace - -void random_fill(void *buf, size_t size) { - ErrnoProtect protect; - void *state = acquire_tls(); - if (state) { - random_fill_impl( - [state](void *buf, size_t size) { - vdso::TypedSymbol vgetrandom; - int res = vgetrandom(buf, size, 0, state, - RandomStateFactory::size_of_opaque_state()); - if (res < 0) { - libc_errno = -res; - return -1; - } - return res; - }, - buf, size); - } else { - random_fill_impl( - [](void *buf, size_t size) { - return LIBC_NAMESPACE::getrandom(buf, size, 0); - }, - buf, size); - } -} - -void random_prefork() { fork_inflight = true; } -void random_postfork_parent() { fork_inflight = false; } -void random_postfork_child() { - tls_state = nullptr; - RandomStateFactory::postfork_cleanup(); - fork_inflight = false; -} - -} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/__support/OSUtil/linux/random.h b/libc/src/__support/OSUtil/linux/random.h deleted file mode 100644 index 567d5f3f412f0..0000000000000 --- a/libc/src/__support/OSUtil/linux/random.h +++ /dev/null @@ -1,23 +0,0 @@ -//===-- Utilities for getting secure randomness -----------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_LIBC_SRC___SUPPORT_RANDOMNESS_H -#define LLVM_LIBC_SRC___SUPPORT_RANDOMNESS_H - -#include "src/__support/common.h" - -#define __need_size_t -#include - -namespace LIBC_NAMESPACE_DECL { -void random_fill(void *buf, unsigned long size); -void random_prefork(); -void random_postfork_parent(); -void random_postfork_child(); -} // namespace LIBC_NAMESPACE_DECL -#endif // LLVM_LIBC_SRC___SUPPORT_RANDOMNESS_H diff --git a/libc/test/integration/src/__support/CMakeLists.txt b/libc/test/integration/src/__support/CMakeLists.txt index d2dae7e02a9c5..b5b6557e8d689 100644 --- a/libc/test/integration/src/__support/CMakeLists.txt +++ b/libc/test/integration/src/__support/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(threads) -add_subdirectory(OSUtil) if(LIBC_TARGET_OS_IS_GPU) add_subdirectory(GPU) endif() diff --git a/libc/test/integration/src/__support/OSUtil/CMakeLists.txt b/libc/test/integration/src/__support/OSUtil/CMakeLists.txt deleted file mode 100644 index 5ff1a11aff5c9..0000000000000 --- a/libc/test/integration/src/__support/OSUtil/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -add_custom_target(libc-osutil-integration-tests) - -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) - add_subdirectory(${LIBC_TARGET_OS}) -endif() diff --git a/libc/test/integration/src/__support/OSUtil/linux/CMakeLists.txt b/libc/test/integration/src/__support/OSUtil/linux/CMakeLists.txt deleted file mode 100644 index a4f15ad837035..0000000000000 --- a/libc/test/integration/src/__support/OSUtil/linux/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -add_integration_test( - random_fill_test - SUITE - libc-osutil-integration-tests - SRCS - random_fill_test.cpp - DEPENDS - libc.include.pthread - libc.src.__support.OSUtil.linux.random -) diff --git a/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp b/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp deleted file mode 100644 index 4e029e484742e..0000000000000 --- a/libc/test/integration/src/__support/OSUtil/linux/random_fill_test.cpp +++ /dev/null @@ -1,22 +0,0 @@ -//===-- Tests for random_fill ---------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "src/__support/OSUtil/linux/random.h" - -#include "test/IntegrationTest/test.h" - -void smoke_test() { - using namespace LIBC_NAMESPACE; - uint32_t buffer; - random_fill(&buffer, sizeof(buffer)); -} - -TEST_MAIN() { - smoke_test(); - return 0; -} diff --git a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt index ff82616cc4a70..5e75259cd8a16 100644 --- a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt +++ b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt @@ -19,3 +19,11 @@ add_libc_test( libc.src.signal.sigaction libc.src.signal.raise ) + +add_libc_test( + cprng_test + SUITE libc-osutil-tests + SRCS cprng_test.cpp + DEPENDS + libc.src.__support.OSUtil.linux.cprng +) diff --git a/libc/test/src/__support/OSUtil/linux/cprng_test.cpp b/libc/test/src/__support/OSUtil/linux/cprng_test.cpp new file mode 100644 index 0000000000000..e7c20c7c70d9a --- /dev/null +++ b/libc/test/src/__support/OSUtil/linux/cprng_test.cpp @@ -0,0 +1,25 @@ +//===-- Unittests for CPRNG -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "src/__support/OSUtil/linux/cprng.h" +#include "test/UnitTest/Test.h" + +namespace LIBC_NAMESPACE_DECL { +namespace cprng { +TEST(LlvmLibcOSUtilCPRNGTest, Generate) { + auto result = generate(); + ASSERT_TRUE(result.has_value()); +} +TEST(LlvmLibcOSUtilCPRNGTest, GenerateBounded) { + for (uint32_t bound = 1; bound < 5000; ++bound) { + auto result = generate_bounded_u32(bound); + ASSERT_TRUE(result.has_value()); + EXPECT_LT(*result, bound); + } +} +} // namespace cprng +} // namespace LIBC_NAMESPACE_DECL From 290d57136534b8904f67dcc04fceb78782851a0a Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Tue, 1 Oct 2024 23:22:55 -0400 Subject: [PATCH 06/12] add a simple diagram --- libc/src/__support/OSUtil/linux/cprng.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libc/src/__support/OSUtil/linux/cprng.cpp b/libc/src/__support/OSUtil/linux/cprng.cpp index 02ee3e12f4781..8f4c18485788c 100644 --- a/libc/src/__support/OSUtil/linux/cprng.cpp +++ b/libc/src/__support/OSUtil/linux/cprng.cpp @@ -32,6 +32,25 @@ namespace { using namespace vdso; // A block of random state together with enough space to hold all its freelist. +// ┌───────────┬────────────┐ +// │ │ │ +// │ │ Pages ├──────────────┐ +// │ │ │ ▼ +// │ Prev ├────────────┤ ┌──────────────────┐ +// │ │ │ │ │ +// │ │ State0 ├───►│ Opaque Area │ +// │ │ │ │ │ +// │ ├────────────┤ ├──────────────────┤ +// ├───────────┤ │ │ │ +// │ │ State1 ├───►│ Opaque Area │ +// │ │ │ │ │ +// │ ├────────────┤ ├──────────────────┤ +// │ Next │ │ │ │ +// │ │ State2 ├───►│ Opaque Area │ +// │ │ │ │ │ +// │ ├────────────┤ ├──────────────────┤ +// │ │ ...... │ │ .............. │ +// └───────────┴────────────┘ └──────────────────┘ struct StateBlock { StateBlock *prev; StateBlock *next; From 12d06c093e230d49e14d05e68bad314e771b874b Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 2 Oct 2024 11:36:28 -0400 Subject: [PATCH 07/12] add some comments --- libc/src/__support/OSUtil/linux/cprng.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libc/src/__support/OSUtil/linux/cprng.cpp b/libc/src/__support/OSUtil/linux/cprng.cpp index 8f4c18485788c..9c57c9dfc02ba 100644 --- a/libc/src/__support/OSUtil/linux/cprng.cpp +++ b/libc/src/__support/OSUtil/linux/cprng.cpp @@ -51,6 +51,8 @@ using namespace vdso; // │ ├────────────┤ ├──────────────────┤ // │ │ ...... │ │ .............. │ // └───────────┴────────────┘ └──────────────────┘ +// State blocks are doubly linked so that we can iterate bidrectionally as a +// freelist. We'will use a sentinel to simplify the implementation. struct StateBlock { StateBlock *prev; StateBlock *next; @@ -59,6 +61,8 @@ struct StateBlock { void *&freelist(size_t index) { return appendix[index + 1]; } }; +// Parameters from Linux UAPI. Available only for 6.11+. We manually provide it +// to support a mean of compiling libc on old kernels. struct vgetrandom_opaque_params { unsigned size_of_opaque_state = 0; unsigned mmap_prot = 0; @@ -66,6 +70,14 @@ struct vgetrandom_opaque_params { unsigned reserved[13]; }; +// Global configuration for state allocation. +// - System Page Size +// - Number of pages per block (allocation) +// - Number of states per page +// - Parameters for state allocation +// An opaque state cannot go across page boundaries. +// We use the number of processors available to the current process to estimate +// the anticipated number of states. class GlobalConfig { public: const size_t page_size = 0; From 3262b99012ef575c184df5472b3d3ce40deecd2e Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 2 Oct 2024 13:14:09 -0400 Subject: [PATCH 08/12] add some comments --- libc/src/__support/OSUtil/linux/cprng.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libc/src/__support/OSUtil/linux/cprng.h b/libc/src/__support/OSUtil/linux/cprng.h index e2075805eb81c..b418424fd13f1 100644 --- a/libc/src/__support/OSUtil/linux/cprng.h +++ b/libc/src/__support/OSUtil/linux/cprng.h @@ -16,6 +16,20 @@ #define __need_size_t #include +// CPRNG provides LLVM-libc with a cryptographically secure random number. +// arc4random is available in BSD systems and later introduced in glibc. +// However, arc4random family faces performance issues on pre 6.11 Linux +// as each entropy generation would demand a round trip to and from the kernel. +// We still use getrandom syscall as fallback for older kernels. For modern +// kernels, we use the vDSO interface to get random bytes. +// Such vDSO interface differs from the getrandom syscall in that it demands +// the userspace to maintain an opaque state. System library (e.g. libc) is in +// charge of allocating a large bunch of such states and lend them to each +// thread demanding random bytes. +// Our approach should be providing similar guarantees as arc4random family on +// BSD and bionic. It is not async-signal-safe, but we have means to prevent +// information leakage due to reentrancy in signal frames. The vDSO getrandom +// implementation itself provides security across forks (by wiping pages). namespace LIBC_NAMESPACE_DECL { namespace cprng { size_t fill_buffer(char *buffer, size_t length); From d4b71c158d8a1f1e7d56e86349d736aab016e166 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Fri, 4 Oct 2024 17:08:16 -0400 Subject: [PATCH 09/12] [WIP] porting --- .../src/__support/OSUtil/linux/CMakeLists.txt | 20 ----- libc/src/stdlib/linux/CMakeLists.txt | 20 +++++ .../OSUtil => stdlib}/linux/cprng.cpp | 82 ++++++++----------- .../OSUtil => stdlib}/linux/cprng.h | 6 +- .../src/__support/OSUtil/linux/CMakeLists.txt | 14 ++-- 5 files changed, 64 insertions(+), 78 deletions(-) rename libc/src/{__support/OSUtil => stdlib}/linux/cprng.cpp (85%) rename libc/src/{__support/OSUtil => stdlib}/linux/cprng.h (95%) diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt index 447e4c04211bc..21717807d4244 100644 --- a/libc/src/__support/OSUtil/linux/CMakeLists.txt +++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt @@ -54,23 +54,3 @@ add_object_library( libc.src.sys.auxv.getauxval ) -add_object_library( - cprng - HDRS - cprng.h - SRCS - cprng.cpp - DEPENDS - libc.src.__support.common - libc.src.__support.OSUtil.linux.vdso - libc.src.__support.threads.callonce - libc.src.__support.threads.linux.raw_mutex - libc.src.__support.CPP.optional - libc.src.__support.CPP.new - libc.src.sys.mman.mmap - libc.src.sys.mman.munmap - libc.src.unistd.sysconf - libc.src.sched.sched_getaffinity - libc.src.sched.__sched_getcpucount -) - diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt index 1d3c00a5e0ddb..d36fd4b35bcce 100644 --- a/libc/src/stdlib/linux/CMakeLists.txt +++ b/libc/src/stdlib/linux/CMakeLists.txt @@ -9,3 +9,23 @@ add_entrypoint_object( libc.src.signal.raise libc.src.stdlib._Exit ) + +add_object_library( + cprng + HDRS + cprng.h + SRCS + cprng.cpp + DEPENDS + libc.src.__support.common + libc.src.__support.OSUtil.linux.vdso + libc.src.__support.threads.callonce + libc.src.__support.threads.linux.raw_mutex + libc.src.__support.CPP.optional + libc.src.__support.CPP.new + libc.src.sys.mman.mmap + libc.src.sys.mman.munmap + libc.src.unistd.sysconf + libc.src.sched.sched_getaffinity + libc.src.sched.__sched_getcpucount +) diff --git a/libc/src/__support/OSUtil/linux/cprng.cpp b/libc/src/stdlib/linux/cprng.cpp similarity index 85% rename from libc/src/__support/OSUtil/linux/cprng.cpp rename to libc/src/stdlib/linux/cprng.cpp index 9c57c9dfc02ba..790bf8cfc17d3 100644 --- a/libc/src/__support/OSUtil/linux/cprng.cpp +++ b/libc/src/stdlib/linux/cprng.cpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -#include "src/__support/OSUtil/linux/cprng.h" +#include "src/stdlib/linux/cprng.h" #include "src/__support/CPP/atomic.h" #include "src/__support/CPP/mutex.h" #include "src/__support/CPP/new.h" @@ -15,11 +15,7 @@ #include "src/__support/libc_assert.h" #include "src/__support/threads/callonce.h" #include "src/__support/threads/linux/raw_mutex.h" -#include "src/sched/sched_getaffinity.h" -#include "src/sched/sched_getcpucount.h" -#include "src/sys/mman/mmap.h" -#include "src/sys/mman/munmap.h" -#include "src/unistd/sysconf.h" +#include "src/sys/auxv/getauxval.h" extern "C" int __cxa_thread_atexit_impl(void (*)(void *), void *, void *); extern "C" int __cxa_atexit(void (*)(void *), void *, void *); @@ -87,11 +83,13 @@ class GlobalConfig { private: static size_t guess_cpu_count() { - cpu_set_t cpuset{}; - if (LIBC_NAMESPACE::sched_getaffinity(0, sizeof(cpuset), &cpuset)) + unsigned char cpuset[128]{}; + if (syscall_impl(0, sizeof(cpuset), cpuset) <= 0) return 1u; - int count = LIBC_NAMESPACE::__sched_getcpucount(sizeof(cpu_set_t), &cpuset); - return static_cast(count > 1 ? count : 1); + size_t count = 0; + for (auto byte : cpuset) + count += cpp::popcount(byte); + return count > 1u ? count : 1u; } private: @@ -114,7 +112,7 @@ class GlobalConfig { return cpp::nullopt; // get valid page size - long page_size_res = sysconf(_SC_PAGESIZE); + long page_size_res = getauxval(AT_PAGESZ); if (page_size_res <= 0) return cpp::nullopt; page_size = static_cast(page_size_res); @@ -134,26 +132,6 @@ class GlobalConfig { } }; -// Utilities to allocate memory and hold it temporarily. -template struct Allocation { - T *ptr; - size_t raw_size; - Allocation(size_t raw_size) : ptr(nullptr), raw_size(raw_size) { - AllocChecker ac{}; - ptr = static_cast( - /* NOLINT(llvmlibc-callee-namespace) */ ::operator new(raw_size, ac)); - if (!ac) - ptr = nullptr; - } - T *operator->() { return ptr; } - operator bool() const { return ptr != nullptr; } - ~Allocation() { - if (ptr) - /* NOLINT(llvmlibc-callee-namespace )*/ ::operator delete(ptr, raw_size); - } - void release() { ptr = nullptr; } -}; - // A monotonic state pool. class MonotonicStatePool { RawMutex mutex; @@ -183,21 +161,27 @@ class MonotonicStatePool { bool allocate_new_block() { LIBC_ASSERT(is_empty()); // allocate a new block + AllocChecker ac{}; size_t raw_size = state_block_raw_size(); - Allocation block(raw_size); - if (!block) + // NOLINTNEXTLINE(llvmlibc-callee-namespace) + auto *block = static_cast(operator new(raw_size, ac)); + if (!ac) return false; // allocate associated pages - auto pages = static_cast( - mmap(nullptr, config.pages_per_block * config.page_size, - config.params.mmap_prot, config.params.mmap_flags, -1, 0)); - if (pages == MAP_FAILED) + auto pages = syscall_impl( + SYS_mmap, nullptr, config.pages_per_block * config.page_size, + config.params.mmap_prot, config.params.mmap_flags, -1, 0); + + if (pages <= 0) { + // NOLINTNEXTLINE(llvmlibc-callee-namespace) + ::operator delete(block, raw_size); return false; + } // populate the block - block->pages() = pages; + block->pages() = reinterpret_cast(pages); size_t state_idx = 0; for (size_t p = 0; p < config.pages_per_block; ++p) { - char *page = pages + p * config.page_size; + char *page = reinterpret_cast(pages) + p * config.page_size; for (size_t s = 0; s < config.states_per_page; ++s) { block->freelist(state_idx++) = page + s * config.params.size_of_opaque_state; @@ -206,13 +190,12 @@ class MonotonicStatePool { // link the block to the sentinel block->next = sentinel.next; block->prev = &sentinel; - block->next->prev = block.ptr; - block->prev->next = block.ptr; + block->next->prev = block; + block->prev->next = block; // update the cursor - free_cursor = block.ptr; + free_cursor = block; free_count = state_idx; // release the ownership of allocation - block.release(); return true; } @@ -244,10 +227,13 @@ class MonotonicStatePool { StateBlock *block = sentinel.next; size_t raw_size = state_block_raw_size(); while (block != &sentinel) { - munmap(block->pages(), config.pages_per_block * config.page_size); + [[maybe_unused]] + auto res = syscall_impl(SYS_munmap, block->pages(), + config.pages_per_block * config.page_size); + LIBC_ASSERT(res == 0); StateBlock *next = block->next; - /* NOLINT(llvmlibc-callee-namespace) */ ::operator delete( - block, raw_size, std::align_val_t{alignof(StateBlock)}); + // NOLINTNEXTLINE(llvmlibc-callee-namespace) + ::operator delete(block, raw_size); block = next; } } @@ -260,7 +246,7 @@ class MonotonicStatePool { if (!config) return; new (pool) MonotonicStatePool(*config); - is_valid = /* NOLINT(llvmlibc-callee-namespace) */ !__cxa_atexit( + is_valid = !__cxa_atexit( [](void *) { reinterpret_cast(pool)->~MonotonicStatePool(); }, @@ -304,7 +290,7 @@ class ThreadLocalState { remaining_trials--; local_state = pool->get(); if (local_state) { - if (/* NOLINT(llvmlibc-callee-namespace) */ __cxa_thread_atexit_impl( + if (__cxa_thread_atexit_impl( [](void *opaque) { auto *state = static_cast(opaque); state->destroy(); diff --git a/libc/src/__support/OSUtil/linux/cprng.h b/libc/src/stdlib/linux/cprng.h similarity index 95% rename from libc/src/__support/OSUtil/linux/cprng.h rename to libc/src/stdlib/linux/cprng.h index b418424fd13f1..c67c8381f577a 100644 --- a/libc/src/__support/OSUtil/linux/cprng.h +++ b/libc/src/stdlib/linux/cprng.h @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_LIBC_SRC___SUPPORT_CPRNG_H -#define LLVM_LIBC_SRC___SUPPORT_CPRNG_H +#ifndef LLVM_LIBC_SRC_STDLIB_LINUX_CPRNG_H +#define LLVM_LIBC_SRC_STDLIB_LINUX_CPRNG_H #include "src/__support/CPP/optional.h" #include "src/__support/CPP/type_traits.h" @@ -65,4 +65,4 @@ LIBC_INLINE cpp::optional generate_bounded_u32(uint32_t bound) { } // namespace cprng } // namespace LIBC_NAMESPACE_DECL -#endif // LLVM_LIBC_SRC___SUPPORT_CPRNG_H +#endif // LLVM_LIBC_SRC_STDLIB_LINUX_CPRNG_H diff --git a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt index 5e75259cd8a16..0d57513d9c52e 100644 --- a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt +++ b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt @@ -20,10 +20,10 @@ add_libc_test( libc.src.signal.raise ) -add_libc_test( - cprng_test - SUITE libc-osutil-tests - SRCS cprng_test.cpp - DEPENDS - libc.src.__support.OSUtil.linux.cprng -) +# add_libc_test( +# cprng_test +# SUITE libc-osutil-tests +# SRCS cprng_test.cpp +# DEPENDS +# libc.src.__support.OSUtil.linux.cprng +# ) From 8d177f38f0fb721b188901ac5e62bee999af39c3 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Fri, 4 Oct 2024 17:09:38 -0400 Subject: [PATCH 10/12] [WIP] porting --- libc/src/stdlib/linux/cprng.cpp | 41 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/libc/src/stdlib/linux/cprng.cpp b/libc/src/stdlib/linux/cprng.cpp index 790bf8cfc17d3..eea3a5c5566d4 100644 --- a/libc/src/stdlib/linux/cprng.cpp +++ b/libc/src/stdlib/linux/cprng.cpp @@ -237,27 +237,30 @@ class MonotonicStatePool { block = next; } } - static MonotonicStatePool *instance() { - alignas(MonotonicStatePool) static char pool[sizeof(MonotonicStatePool)]; - static bool is_valid = false; - static CallOnceFlag once_flag = callonce_impl::NOT_CALLED; - callonce(&once_flag, []() { - cpp::optional config = GlobalConfig::get(); - if (!config) - return; - new (pool) MonotonicStatePool(*config); - is_valid = !__cxa_atexit( - [](void *) { - reinterpret_cast(pool)->~MonotonicStatePool(); - }, - nullptr, __dso_handle); - }); - if (!is_valid) - return nullptr; - return reinterpret_cast(pool); - } + + static MonotonicStatePool *instance(); }; +alignas(MonotonicStatePool) static char pool[sizeof(MonotonicStatePool)]; +static bool is_valid = false; +static CallOnceFlag once_flag = callonce_impl::NOT_CALLED; +MonotonicStatePool *MonotonicStatePool::instance() { + callonce(&once_flag, []() { + cpp::optional config = GlobalConfig::get(); + if (!config) + return; + new (pool) MonotonicStatePool(*config); + is_valid = !__cxa_atexit( + [](void *) { + reinterpret_cast(pool)->~MonotonicStatePool(); + }, + nullptr, __dso_handle); + }); + if (!is_valid) + return nullptr; + return reinterpret_cast(pool); +} + // We do not guarantee the correctness of calling the cprng functions in signal // frames. However, we do want to make sure that an mistaken (maliciously // induced) call to the cprng will never cause a state to be used twice due to From a20facedc1f69dc620812cc94ccd7cf3d1622b8d Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Sat, 5 Oct 2024 16:02:56 -0400 Subject: [PATCH 11/12] finish up new changes --- libc/src/__support/threads/thread.cpp | 4 ++ libc/src/__support/threads/thread.h | 3 ++ libc/src/stdlib/linux/CMakeLists.txt | 8 ++- libc/src/stdlib/linux/cprng.cpp | 26 +++++----- .../integration/src/stdlib/CMakeLists.txt | 4 ++ .../src/stdlib/linux/CMakeLists.txt | 11 ++++ .../src/stdlib/linux/cprng_test.cpp | 50 +++++++++++++++++++ .../src/__support/OSUtil/linux/CMakeLists.txt | 8 --- .../src/__support/OSUtil/linux/cprng_test.cpp | 25 ---------- 9 files changed, 87 insertions(+), 52 deletions(-) create mode 100644 libc/test/integration/src/stdlib/linux/CMakeLists.txt create mode 100644 libc/test/integration/src/stdlib/linux/cprng_test.cpp delete mode 100644 libc/test/src/__support/OSUtil/linux/cprng_test.cpp diff --git a/libc/src/__support/threads/thread.cpp b/libc/src/__support/threads/thread.cpp index 04668dbfcbb63..12f227a6cd975 100644 --- a/libc/src/__support/threads/thread.cpp +++ b/libc/src/__support/threads/thread.cpp @@ -161,6 +161,10 @@ void call_atexit_callbacks(ThreadAttributes *attrib) { } } +bool add_atexit_callback(void (*callback)(void *), void *obj) { + return atexit_callback_mgr.add_callback(callback, obj) == 0; +} + } // namespace internal cpp::optional new_tss_key(TSSDtor *dtor) { diff --git a/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h index f2b1f6bbb253d..c618b3e826a97 100644 --- a/libc/src/__support/threads/thread.h +++ b/libc/src/__support/threads/thread.h @@ -246,6 +246,9 @@ namespace internal { // returned by this function. ThreadAtExitCallbackMgr *get_thread_atexit_callback_mgr(); +// Interal implementation of the __cxa_thread_atexit_impl function. This +bool add_atexit_callback(void (*callback)(void *), void *obj); + // Call the currently registered thread specific atexit callbacks. Useful for // implementing the thread_exit function. void call_atexit_callbacks(ThreadAttributes *attrib); diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt index d36fd4b35bcce..9dff17f746a99 100644 --- a/libc/src/stdlib/linux/CMakeLists.txt +++ b/libc/src/stdlib/linux/CMakeLists.txt @@ -23,9 +23,7 @@ add_object_library( libc.src.__support.threads.linux.raw_mutex libc.src.__support.CPP.optional libc.src.__support.CPP.new - libc.src.sys.mman.mmap - libc.src.sys.mman.munmap - libc.src.unistd.sysconf - libc.src.sched.sched_getaffinity - libc.src.sched.__sched_getcpucount + libc.src.__support.threads.thread + libc.src.sys.auxv.getauxval + libc.src.stdlib.atexit ) diff --git a/libc/src/stdlib/linux/cprng.cpp b/libc/src/stdlib/linux/cprng.cpp index eea3a5c5566d4..a02eb17d871ec 100644 --- a/libc/src/stdlib/linux/cprng.cpp +++ b/libc/src/stdlib/linux/cprng.cpp @@ -15,13 +15,10 @@ #include "src/__support/libc_assert.h" #include "src/__support/threads/callonce.h" #include "src/__support/threads/linux/raw_mutex.h" +#include "src/__support/threads/thread.h" +#include "src/stdlib/atexit.h" #include "src/sys/auxv/getauxval.h" -extern "C" int __cxa_thread_atexit_impl(void (*)(void *), void *, void *); -extern "C" int __cxa_atexit(void (*)(void *), void *, void *); -extern "C" [[gnu::weak, gnu::visibility("hidden")]] void *__dso_handle = - nullptr; - namespace LIBC_NAMESPACE_DECL { namespace cprng { namespace { @@ -250,11 +247,9 @@ MonotonicStatePool *MonotonicStatePool::instance() { if (!config) return; new (pool) MonotonicStatePool(*config); - is_valid = !__cxa_atexit( - [](void *) { - reinterpret_cast(pool)->~MonotonicStatePool(); - }, - nullptr, __dso_handle); + is_valid = !atexit([]() { + reinterpret_cast(pool)->~MonotonicStatePool(); + }); }); if (!is_valid) return nullptr; @@ -293,14 +288,15 @@ class ThreadLocalState { remaining_trials--; local_state = pool->get(); if (local_state) { - if (__cxa_thread_atexit_impl( + if (!internal::add_atexit_callback( [](void *opaque) { auto *state = static_cast(opaque); state->destroy(); }, - this, __dso_handle) != 0) { + this)) { pool->recycle(local_state); local_state = nullptr; + release(); } } } @@ -332,21 +328,23 @@ size_t fill_buffer_impl(G generator, char *buffer, size_t length) { size_t fill_buffer(char *buffer, size_t length) { void *state = local_state.acquire(); if (state) { - // Given state is valid, state_size shall be valid. + // Given state is valid, state_size shall be valid and the vDSO symbol shall + // be defined. size_t state_size = MonotonicStatePool::instance()->state_size(); auto impl = [state, state_size](char *cursor, size_t len) { TypedSymbol vgetrandom; return vgetrandom(cursor, len, 0, state, state_size); }; + local_state.release(); return fill_buffer_impl(impl, buffer, length); } else { auto impl = [](char *cursor, size_t len) { // use syscall to avoid errno handling return syscall_impl(SYS_getrandom, cursor, len, 0); }; + local_state.release(); return fill_buffer_impl(impl, buffer, length); } - local_state.release(); } } // namespace cprng } // namespace LIBC_NAMESPACE_DECL diff --git a/libc/test/integration/src/stdlib/CMakeLists.txt b/libc/test/integration/src/stdlib/CMakeLists.txt index 1efdf607defe9..26eba5c1071f5 100644 --- a/libc/test/integration/src/stdlib/CMakeLists.txt +++ b/libc/test/integration/src/stdlib/CMakeLists.txt @@ -13,3 +13,7 @@ add_integration_test( FRANCE=Paris GERMANY=Berlin ) + +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) + add_subdirectory(${LIBC_TARGET_OS}) +endif() diff --git a/libc/test/integration/src/stdlib/linux/CMakeLists.txt b/libc/test/integration/src/stdlib/linux/CMakeLists.txt new file mode 100644 index 0000000000000..66ec786d3b501 --- /dev/null +++ b/libc/test/integration/src/stdlib/linux/CMakeLists.txt @@ -0,0 +1,11 @@ +add_integration_test( + cprng_test + SUITE + stdlib-integration-tests + SRCS + cprng_test.cpp + DEPENDS + libc.src.stdlib.linux.cprng + libc.src.pthread.pthread_create + libc.src.pthread.pthread_join +) diff --git a/libc/test/integration/src/stdlib/linux/cprng_test.cpp b/libc/test/integration/src/stdlib/linux/cprng_test.cpp new file mode 100644 index 0000000000000..7b84dbad2685b --- /dev/null +++ b/libc/test/integration/src/stdlib/linux/cprng_test.cpp @@ -0,0 +1,50 @@ +//===-- Unittests for CPRNG -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "src/pthread/pthread_create.h" +#include "src/pthread/pthread_join.h" +#include "src/stdlib/linux/cprng.h" +#include "test/IntegrationTest/test.h" + +namespace LIBC_NAMESPACE_DECL { +namespace cprng { +void smoke_test() { + auto result = generate(); + ASSERT_TRUE(result.has_value()); +} +void bounded_test() { + for (uint32_t bound = 1; bound < 5000; ++bound) { + auto result = generate_bounded_u32(bound); + ASSERT_TRUE(result.has_value()); + ASSERT_TRUE(*result < bound); + } +} +void threaded_bounded_test() { + pthread_t threads[10]; + for (auto &thread : threads) { + ASSERT_EQ(LIBC_NAMESPACE::pthread_create( + &thread, nullptr, + [](void *) -> void * { + bounded_test(); + return nullptr; + }, + nullptr), + 0); + } + for (auto &thread : threads) { + ASSERT_EQ(LIBC_NAMESPACE::pthread_join(thread, nullptr), 0); + } +} +} // namespace cprng +} // namespace LIBC_NAMESPACE_DECL + +TEST_MAIN() { + LIBC_NAMESPACE::cprng::smoke_test(); + LIBC_NAMESPACE::cprng::bounded_test(); + LIBC_NAMESPACE::cprng::threaded_bounded_test(); + return 0; +} diff --git a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt index 0d57513d9c52e..ff82616cc4a70 100644 --- a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt +++ b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt @@ -19,11 +19,3 @@ add_libc_test( libc.src.signal.sigaction libc.src.signal.raise ) - -# add_libc_test( -# cprng_test -# SUITE libc-osutil-tests -# SRCS cprng_test.cpp -# DEPENDS -# libc.src.__support.OSUtil.linux.cprng -# ) diff --git a/libc/test/src/__support/OSUtil/linux/cprng_test.cpp b/libc/test/src/__support/OSUtil/linux/cprng_test.cpp deleted file mode 100644 index e7c20c7c70d9a..0000000000000 --- a/libc/test/src/__support/OSUtil/linux/cprng_test.cpp +++ /dev/null @@ -1,25 +0,0 @@ -//===-- Unittests for CPRNG -----------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -#include "src/__support/OSUtil/linux/cprng.h" -#include "test/UnitTest/Test.h" - -namespace LIBC_NAMESPACE_DECL { -namespace cprng { -TEST(LlvmLibcOSUtilCPRNGTest, Generate) { - auto result = generate(); - ASSERT_TRUE(result.has_value()); -} -TEST(LlvmLibcOSUtilCPRNGTest, GenerateBounded) { - for (uint32_t bound = 1; bound < 5000; ++bound) { - auto result = generate_bounded_u32(bound); - ASSERT_TRUE(result.has_value()); - EXPECT_LT(*result, bound); - } -} -} // namespace cprng -} // namespace LIBC_NAMESPACE_DECL From fc20508c44bd8724db9ea763b12e9ad40aacff77 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Sat, 5 Oct 2024 16:49:10 -0400 Subject: [PATCH 12/12] finish up new changes --- libc/src/__support/threads/thread.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h index c618b3e826a97..dc1854d088060 100644 --- a/libc/src/__support/threads/thread.h +++ b/libc/src/__support/threads/thread.h @@ -246,7 +246,7 @@ namespace internal { // returned by this function. ThreadAtExitCallbackMgr *get_thread_atexit_callback_mgr(); -// Interal implementation of the __cxa_thread_atexit_impl function. This +// Add internal atexit callbacks. bool add_atexit_callback(void (*callback)(void *), void *obj); // Call the currently registered thread specific atexit callbacks. Useful for