[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, &params, ~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, &params, ~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, &params, ~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