[libc-commits] [libc] [libc] implement secure random buffer filling with vDSO (PR #109870)
Schrodinger ZHU Yifan via libc-commits
libc-commits at lists.llvm.org
Sat Oct 5 13:03:47 PDT 2024
https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/109870
>From 553925e8ed93fdf035cb1094c8018eb7ea36026f Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Tue, 24 Sep 2024 18:01:46 -0400
Subject: [PATCH 01/11] [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 6c7014940407d8..4752e326cc153e 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 3c4c6205071da2..ee5777ad67f6dd 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 00000000000000..612a184bda8455
--- /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 <asm/param.h>
+
+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<size_t>(reinterpret_cast<ptrdiff_t>(boundary) -
+ reinterpret_cast<ptrdiff_t>(ptr))};
+ }
+
+ internal::SafeMemSize bytes() const {
+ return capacity() * internal::SafeMemSize{sizeof(void *)};
+ }
+
+ bool initialize() {
+ internal::SafeMemSize page_size{static_cast<size_t>(sysconf(_SC_PAGESIZE))};
+ if (!page_size.valid())
+ return false;
+ ptr = reinterpret_cast<void **>(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<size_t>(2)};
+ if (!new_bytes.valid())
+ return false;
+ }
+
+ // TODO: migrate to syscall wrapper once it's available
+ auto result = syscall_impl<intptr_t>(
+ SYS_mremap, bytes(), static_cast<size_t>(new_bytes), MREMAP_MAYMOVE);
+
+ if (result < 0 && result > -EXEC_PAGESIZE)
+ return false;
+ ptr = reinterpret_cast<void **>(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<size_t>(
+ reinterpret_cast<ptrdiff_t>(usage) - reinterpret_cast<ptrdiff_t>(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<vdso::VDSOSym::GetRandom> 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<size_t>(
+ LIBC_NAMESPACE::__sched_getcpucount(sizeof(cs), &cs))};
+
+ internal::SafeMemSize allocation_size =
+ internal::SafeMemSize{
+ static_cast<size_t>(params.size_of_opaque_state)} *
+ count;
+
+ page_size = static_cast<size_t>(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<size_t>(1)} + allocations.size());
+
+ if (!total_size.valid() ||
+ !freelist.ensure_space(total_size - freelist.size()))
+ return false;
+
+ auto *new_allocation =
+ static_cast<char *>(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<StateFactory *>(storage);
+ factory->~StateFactory();
+ valid = false;
+ });
+ });
+ return valid ? reinterpret_cast<StateFactory *>(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 <class F> void random_fill_impl(F gen, void *buf, size_t size) {
+ auto *buffer = reinterpret_cast<uint8_t *>(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<vdso::VDSOSym::GetRandom> 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 00000000000000..0e2d51391ec31c
--- /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 <stddef.h>
+
+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 968e1536c4d270..2b1cf398369de2 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 <VDSOSym sym> LIBC_INLINE constexpr auto dispatcher() {
@@ -58,8 +58,11 @@ template <VDSOSym sym> LIBC_INLINE constexpr auto dispatcher() {
else if constexpr (sym == VDSOSym::FlushICache)
return static_cast<void (*)(void *, void *, unsigned int)>(nullptr);
else if constexpr (sym == VDSOSym::RiscvHwProbe)
- return static_cast<int (*)(riscv_hwprobe *, size_t, size_t, cpu_set_t *,
+ return static_cast<int (*)(riscv_hwprobe *, size_t, size_t, void *,
unsigned)>(nullptr);
+ else if constexpr (sym == VDSOSym::GetRandom)
+ return static_cast<int (*)(void *, size_t, unsigned int, void *, size_t)>(
+ nullptr);
else
return static_cast<void *>(nullptr);
}
diff --git a/libc/src/__support/OSUtil/linux/x86_64/vdso.h b/libc/src/__support/OSUtil/linux/x86_64/vdso.h
index abe7c33e07cfab..f46fcb038f2e60 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 dad4f75f092ede..04668dbfcbb63a 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 b5b6557e8d6899..d2dae7e02a9c57 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 00000000000000..5ff1a11aff5c9d
--- /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 00000000000000..a4f15ad8370352
--- /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 00000000000000..bde24a1e0d5521
--- /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 <i at zhuyi.fan>
Date: Tue, 24 Sep 2024 22:38:49 -0400
Subject: [PATCH 02/11] 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 612a184bda8455..553b481200c299 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<vdso::VDSOSym::GetRandom> 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 bde24a1e0d5521..4e029e484742e9 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 <i at zhuyi.fan>
Date: Tue, 24 Sep 2024 23:11:46 -0400
Subject: [PATCH 03/11] 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 553b481200c299..13a6c40ce965a7 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<StateFactory *>(storage);
- factory->~StateFactory();
- valid = false;
- });
- });
- return valid ? reinterpret_cast<StateFactory *>(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<StateFactory *>(factory_storage);
+ factory->~StateFactory();
+ factory_valid = false;
+ });
+ });
+ return factory_valid ? reinterpret_cast<StateFactory *>(factory_storage)
+ : nullptr;
+}
+
+void StateFactory::postfork_cleanup() {
+ if (factory_valid)
+ reinterpret_cast<StateFactory *>(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 <class F> 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 0e2d51391ec31c..567d5f3f412f07 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 <i at zhuyi.fan>
Date: Tue, 24 Sep 2024 23:18:48 -0400
Subject: [PATCH 04/11] 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 13a6c40ce965a7..ab63c4e27e3525 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<StateFactory *>(factory_storage);
- factory->~StateFactory();
+ auto factory = reinterpret_cast<RandomStateFactory *>(factory_storage);
+ factory->~RandomStateFactory();
factory_valid = false;
});
});
- return factory_valid ? reinterpret_cast<StateFactory *>(factory_storage)
+ return factory_valid ? reinterpret_cast<RandomStateFactory *>(factory_storage)
: nullptr;
}
-void StateFactory::postfork_cleanup() {
+void RandomStateFactory::postfork_cleanup() {
if (factory_valid)
- reinterpret_cast<StateFactory *>(factory_storage)->~StateFactory();
+ reinterpret_cast<RandomStateFactory *>(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<vdso::VDSOSym::GetRandom> 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 <i at zhuyi.fan>
Date: Tue, 1 Oct 2024 21:04:02 -0400
Subject: [PATCH 05/11] 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 4752e326cc153e..447e4c04211bc2 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 00000000000000..02ee3e12f47816
--- /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<size_t>(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<GlobalConfig> 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<VDSOSym::GetRandom> 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<size_t>(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 <typename T> struct Allocation {
+ T *ptr;
+ size_t raw_size;
+ Allocation(size_t raw_size) : ptr(nullptr), raw_size(raw_size) {
+ AllocChecker ac{};
+ ptr = static_cast<T *>(
+ /* 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<StateBlock> block(raw_size);
+ if (!block)
+ return false;
+ // allocate associated pages
+ auto pages = static_cast<char *>(
+ 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<char *>(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<GlobalConfig> config = GlobalConfig::get();
+ if (!config)
+ return;
+ new (pool) MonotonicStatePool(*config);
+ is_valid = /* NOLINT(llvmlibc-callee-namespace) */ !__cxa_atexit(
+ [](void *) {
+ reinterpret_cast<MonotonicStatePool *>(pool)->~MonotonicStatePool();
+ },
+ nullptr, __dso_handle);
+ });
+ if (!is_valid)
+ return nullptr;
+ return reinterpret_cast<MonotonicStatePool *>(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<ThreadLocalState *>(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 <typename G>
+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<size_t>(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<VDSOSym::GetRandom> 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<int>(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 00000000000000..e2075805eb81c2
--- /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 <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace cprng {
+size_t fill_buffer(char *buffer, size_t length);
+
+template <typename T> LIBC_INLINE cpp::optional<T> generate() {
+ static_assert(cpp::is_arithmetic_v<T>, "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<uint32_t> generate_bounded_u32(uint32_t bound) {
+ auto lifted_bound = static_cast<uint64_t>(bound);
+ cpp::optional<uint64_t> seed = generate<uint64_t>();
+ if (!seed)
+ return cpp::nullopt;
+ uint64_t r0 = *seed & 0xFFFFFFFFu;
+ uint64_t r1 = (*seed >> 32) & 0xFFFFFFFFu;
+ uint64_t prod0 = static_cast<uint64_t>(r0) * lifted_bound;
+ uint64_t prod0_hi = (prod0 >> 32) & 0xFFFFFFFFu;
+ uint64_t prod0_lo = prod0 & 0xFFFFFFFFu;
+ uint64_t prod1 = static_cast<uint64_t>(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<uint32_t>(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 ab63c4e27e3525..00000000000000
--- 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 <asm/param.h>
-
-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<size_t>(reinterpret_cast<ptrdiff_t>(boundary) -
- reinterpret_cast<ptrdiff_t>(ptr))};
- }
-
- internal::SafeMemSize bytes() const {
- return capacity() * internal::SafeMemSize{sizeof(void *)};
- }
-
- bool initialize() {
- internal::SafeMemSize page_size{static_cast<size_t>(sysconf(_SC_PAGESIZE))};
- if (!page_size.valid())
- return false;
- ptr = reinterpret_cast<void **>(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<size_t>(2)};
- if (!new_bytes.valid())
- return false;
- }
-
- // TODO: migrate to syscall wrapper once it's available
- auto result = syscall_impl<intptr_t>(
- SYS_mremap, bytes(), static_cast<size_t>(new_bytes), MREMAP_MAYMOVE);
-
- if (result < 0 && result > -EXEC_PAGESIZE)
- return false;
- ptr = reinterpret_cast<void **>(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<size_t>(
- reinterpret_cast<ptrdiff_t>(usage) - reinterpret_cast<ptrdiff_t>(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<vdso::VDSOSym::GetRandom> 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<size_t>(
- LIBC_NAMESPACE::__sched_getcpucount(sizeof(cs), &cs))};
-
- internal::SafeMemSize allocation_size =
- internal::SafeMemSize{
- static_cast<size_t>(params.size_of_opaque_state)} *
- count;
-
- page_size = static_cast<size_t>(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<size_t>(1)} + allocations.size());
-
- if (!total_size.valid() ||
- !freelist.ensure_space(total_size - freelist.size()))
- return false;
-
- auto *new_allocation =
- static_cast<char *>(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<RandomStateFactory *>(factory_storage);
- factory->~RandomStateFactory();
- factory_valid = false;
- });
- });
- return factory_valid ? reinterpret_cast<RandomStateFactory *>(factory_storage)
- : nullptr;
-}
-
-void RandomStateFactory::postfork_cleanup() {
- if (factory_valid)
- reinterpret_cast<RandomStateFactory *>(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 <class F> void random_fill_impl(F gen, void *buf, size_t size) {
- auto *buffer = reinterpret_cast<uint8_t *>(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<vdso::VDSOSym::GetRandom> 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 567d5f3f412f07..00000000000000
--- 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 <stddef.h>
-
-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 d2dae7e02a9c57..b5b6557e8d6899 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 5ff1a11aff5c9d..00000000000000
--- 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 a4f15ad8370352..00000000000000
--- 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 4e029e484742e9..00000000000000
--- 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 ff82616cc4a701..5e75259cd8a166 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 00000000000000..e7c20c7c70d9a6
--- /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<uint32_t>();
+ 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 <i at zhuyi.fan>
Date: Tue, 1 Oct 2024 23:22:55 -0400
Subject: [PATCH 06/11] 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 02ee3e12f47816..8f4c18485788c9 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 <i at zhuyi.fan>
Date: Wed, 2 Oct 2024 11:36:28 -0400
Subject: [PATCH 07/11] 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 8f4c18485788c9..9c57c9dfc02ba9 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 <i at zhuyi.fan>
Date: Wed, 2 Oct 2024 13:14:09 -0400
Subject: [PATCH 08/11] 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 e2075805eb81c2..b418424fd13f13 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 <stddef.h>
+// 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 <yifanzhu at rochester.edu>
Date: Fri, 4 Oct 2024 17:08:16 -0400
Subject: [PATCH 09/11] [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 447e4c04211bc2..21717807d42441 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 1d3c00a5e0ddbe..d36fd4b35bcce6 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 9c57c9dfc02ba9..790bf8cfc17d30 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<long>(0, sizeof(cpuset), cpuset) <= 0)
return 1u;
- int count = LIBC_NAMESPACE::__sched_getcpucount(sizeof(cpu_set_t), &cpuset);
- return static_cast<size_t>(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<size_t>(page_size_res);
@@ -134,26 +132,6 @@ class GlobalConfig {
}
};
-// Utilities to allocate memory and hold it temporarily.
-template <typename T> struct Allocation {
- T *ptr;
- size_t raw_size;
- Allocation(size_t raw_size) : ptr(nullptr), raw_size(raw_size) {
- AllocChecker ac{};
- ptr = static_cast<T *>(
- /* 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<StateBlock> block(raw_size);
- if (!block)
+ // NOLINTNEXTLINE(llvmlibc-callee-namespace)
+ auto *block = static_cast<StateBlock *>(operator new(raw_size, ac));
+ if (!ac)
return false;
// allocate associated pages
- auto pages = static_cast<char *>(
- 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<long>(
+ 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<void *>(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<char *>(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<long>(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<MonotonicStatePool *>(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<ThreadLocalState *>(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 b418424fd13f13..c67c8381f577ac 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<uint32_t> 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 5e75259cd8a166..0d57513d9c52eb 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 <yifanzhu at rochester.edu>
Date: Fri, 4 Oct 2024 17:09:38 -0400
Subject: [PATCH 10/11] [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 790bf8cfc17d30..eea3a5c5566d46 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<GlobalConfig> config = GlobalConfig::get();
- if (!config)
- return;
- new (pool) MonotonicStatePool(*config);
- is_valid = !__cxa_atexit(
- [](void *) {
- reinterpret_cast<MonotonicStatePool *>(pool)->~MonotonicStatePool();
- },
- nullptr, __dso_handle);
- });
- if (!is_valid)
- return nullptr;
- return reinterpret_cast<MonotonicStatePool *>(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<GlobalConfig> config = GlobalConfig::get();
+ if (!config)
+ return;
+ new (pool) MonotonicStatePool(*config);
+ is_valid = !__cxa_atexit(
+ [](void *) {
+ reinterpret_cast<MonotonicStatePool *>(pool)->~MonotonicStatePool();
+ },
+ nullptr, __dso_handle);
+ });
+ if (!is_valid)
+ return nullptr;
+ return reinterpret_cast<MonotonicStatePool *>(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 <i at zhuyi.fan>
Date: Sat, 5 Oct 2024 16:02:56 -0400
Subject: [PATCH 11/11] 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 04668dbfcbb63a..12f227a6cd975d 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<unsigned int> new_tss_key(TSSDtor *dtor) {
diff --git a/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h
index f2b1f6bbb253db..c618b3e826a97c 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 d36fd4b35bcce6..9dff17f746a991 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 eea3a5c5566d46..a02eb17d871ece 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<MonotonicStatePool *>(pool)->~MonotonicStatePool();
- },
- nullptr, __dso_handle);
+ is_valid = !atexit([]() {
+ reinterpret_cast<MonotonicStatePool *>(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<ThreadLocalState *>(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<VDSOSym::GetRandom> 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<int>(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 1efdf607defe9b..26eba5c1071f56 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 00000000000000..66ec786d3b5012
--- /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 00000000000000..7b84dbad2685b8
--- /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<uint32_t>();
+ 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 0d57513d9c52eb..ff82616cc4a701 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 e7c20c7c70d9a6..00000000000000
--- 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<uint32_t>();
- 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
More information about the libc-commits
mailing list