Skip to content

Commit 2b81034

Browse files
renew
1 parent 6e95fe9 commit 2b81034

File tree

11 files changed

+427
-429
lines changed

11 files changed

+427
-429
lines changed

libc/src/__support/OSUtil/linux/CMakeLists.txt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,21 @@ add_object_library(
5555
)
5656

5757
add_object_library(
58-
random
58+
cprng
5959
HDRS
60-
random.h
60+
cprng.h
6161
SRCS
62-
random.cpp
62+
cprng.cpp
6363
DEPENDS
64-
libc.src.sys.random.getrandom
65-
libc.src.sys.mman.mmap
66-
libc.src.sys.mman.munmap
67-
libc.src.unistd.sysconf
68-
libc.src.errno.errno
6964
libc.src.__support.common
7065
libc.src.__support.OSUtil.linux.vdso
7166
libc.src.__support.threads.callonce
7267
libc.src.__support.threads.linux.raw_mutex
73-
libc.src.__support.threads.thread
68+
libc.src.__support.CPP.optional
69+
libc.src.__support.CPP.new
70+
libc.src.sys.mman.mmap
71+
libc.src.sys.mman.munmap
72+
libc.src.unistd.sysconf
7473
libc.src.sched.sched_getaffinity
7574
libc.src.sched.__sched_getcpucount
7675
)
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
//===- Linux implementation of secure random buffer generation --*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
#include "src/__support/OSUtil/linux/cprng.h"
9+
#include "src/__support/CPP/atomic.h"
10+
#include "src/__support/CPP/mutex.h"
11+
#include "src/__support/CPP/new.h"
12+
#include "src/__support/OSUtil/linux/syscall.h"
13+
#include "src/__support/OSUtil/linux/vdso.h"
14+
#include "src/__support/block.h"
15+
#include "src/__support/libc_assert.h"
16+
#include "src/__support/threads/callonce.h"
17+
#include "src/__support/threads/linux/raw_mutex.h"
18+
#include "src/sched/sched_getaffinity.h"
19+
#include "src/sched/sched_getcpucount.h"
20+
#include "src/sys/mman/mmap.h"
21+
#include "src/sys/mman/munmap.h"
22+
#include "src/unistd/sysconf.h"
23+
24+
extern "C" int __cxa_thread_atexit_impl(void (*)(void *), void *, void *);
25+
extern "C" int __cxa_atexit(void (*)(void *), void *, void *);
26+
extern "C" [[gnu::weak, gnu::visibility("hidden")]] void *__dso_handle =
27+
nullptr;
28+
29+
namespace LIBC_NAMESPACE_DECL {
30+
namespace cprng {
31+
namespace {
32+
33+
using namespace vdso;
34+
// A block of random state together with enough space to hold all its freelist.
35+
struct StateBlock {
36+
StateBlock *prev;
37+
StateBlock *next;
38+
void *appendix[0];
39+
void *&pages() { return appendix[0]; }
40+
void *&freelist(size_t index) { return appendix[index + 1]; }
41+
};
42+
43+
struct vgetrandom_opaque_params {
44+
unsigned size_of_opaque_state = 0;
45+
unsigned mmap_prot = 0;
46+
unsigned mmap_flags = 0;
47+
unsigned reserved[13];
48+
};
49+
50+
class GlobalConfig {
51+
public:
52+
const size_t page_size = 0;
53+
const size_t pages_per_block = 0;
54+
const size_t states_per_page = 0;
55+
const vgetrandom_opaque_params params = {};
56+
57+
private:
58+
static size_t guess_cpu_count() {
59+
cpu_set_t cpuset{};
60+
if (LIBC_NAMESPACE::sched_getaffinity(0, sizeof(cpuset), &cpuset))
61+
return 1u;
62+
int count = LIBC_NAMESPACE::__sched_getcpucount(sizeof(cpu_set_t), &cpuset);
63+
return static_cast<size_t>(count > 1 ? count : 1);
64+
}
65+
66+
private:
67+
constexpr GlobalConfig(size_t page_size, size_t pages_per_block,
68+
size_t states_per_page,
69+
vgetrandom_opaque_params params)
70+
: page_size(page_size), pages_per_block(pages_per_block),
71+
states_per_page(states_per_page), params(params) {}
72+
73+
public:
74+
static cpp::optional<GlobalConfig> get() {
75+
size_t page_size = 0;
76+
size_t states_per_page = 0;
77+
size_t pages_per_block = 0;
78+
vgetrandom_opaque_params params = {};
79+
80+
// check symbol availability
81+
TypedSymbol<VDSOSym::GetRandom> vgetrandom;
82+
if (!vgetrandom)
83+
return cpp::nullopt;
84+
85+
// get valid page size
86+
long page_size_res = sysconf(_SC_PAGESIZE);
87+
if (page_size_res <= 0)
88+
return cpp::nullopt;
89+
page_size = static_cast<size_t>(page_size_res);
90+
91+
// get parameters for state allocation
92+
if (vgetrandom(nullptr, 0, 0, &params, ~0U))
93+
return cpp::nullopt;
94+
95+
// Compute the number of states per page. On a valid human-constructable
96+
// computer as year 2024, the following operations shall not overflow given
97+
// the above operations are correctly returned.
98+
size_t guessed_bytes = guess_cpu_count() * params.size_of_opaque_state;
99+
size_t aligned_bytes = align_up(guessed_bytes, page_size);
100+
states_per_page = page_size / params.size_of_opaque_state;
101+
pages_per_block = aligned_bytes / page_size;
102+
return GlobalConfig(page_size, pages_per_block, states_per_page, params);
103+
}
104+
};
105+
106+
// Utilities to allocate memory and hold it temporarily.
107+
template <typename T> struct Allocation {
108+
T *ptr;
109+
size_t raw_size;
110+
Allocation(size_t raw_size) : ptr(nullptr), raw_size(raw_size) {
111+
AllocChecker ac{};
112+
ptr = static_cast<T *>(
113+
/* NOLINT(llvmlibc-callee-namespace) */ ::operator new(raw_size, ac));
114+
if (!ac)
115+
ptr = nullptr;
116+
}
117+
T *operator->() { return ptr; }
118+
operator bool() const { return ptr != nullptr; }
119+
~Allocation() {
120+
if (ptr)
121+
/* NOLINT(llvmlibc-callee-namespace )*/ ::operator delete(ptr, raw_size);
122+
}
123+
void release() { ptr = nullptr; }
124+
};
125+
126+
// A monotonic state pool.
127+
class MonotonicStatePool {
128+
RawMutex mutex;
129+
GlobalConfig config;
130+
StateBlock sentinel;
131+
StateBlock *free_cursor;
132+
// invariant: at sentinel, free_count is always locally full such that we
133+
// don't need to specially check the condition for sentinel when recycling.
134+
size_t free_count;
135+
136+
constexpr MonotonicStatePool(GlobalConfig config)
137+
: mutex(), config(config), sentinel{&sentinel, &sentinel, {}},
138+
free_cursor(&sentinel),
139+
free_count(config.pages_per_block * config.states_per_page) {}
140+
MonotonicStatePool(const MonotonicStatePool &) = delete;
141+
MonotonicStatePool(MonotonicStatePool &&) = delete;
142+
143+
bool is_empty() const { return free_cursor == &sentinel; }
144+
bool is_locally_full() const {
145+
return free_count == config.pages_per_block * config.states_per_page;
146+
}
147+
size_t state_block_raw_size() const {
148+
return sizeof(StateBlock) +
149+
(config.states_per_page * config.pages_per_block + 1) *
150+
sizeof(void *);
151+
}
152+
bool allocate_new_block() {
153+
LIBC_ASSERT(is_empty());
154+
// allocate a new block
155+
size_t raw_size = state_block_raw_size();
156+
Allocation<StateBlock> block(raw_size);
157+
if (!block)
158+
return false;
159+
// allocate associated pages
160+
auto pages = static_cast<char *>(
161+
mmap(nullptr, config.pages_per_block * config.page_size,
162+
config.params.mmap_prot, config.params.mmap_flags, -1, 0));
163+
if (pages == MAP_FAILED)
164+
return false;
165+
// populate the block
166+
block->pages() = pages;
167+
size_t state_idx = 0;
168+
for (size_t p = 0; p < config.pages_per_block; ++p) {
169+
char *page = pages + p * config.page_size;
170+
for (size_t s = 0; s < config.states_per_page; ++s) {
171+
block->freelist(state_idx++) =
172+
page + s * config.params.size_of_opaque_state;
173+
}
174+
}
175+
// link the block to the sentinel
176+
block->next = sentinel.next;
177+
block->prev = &sentinel;
178+
block->next->prev = block.ptr;
179+
block->prev->next = block.ptr;
180+
// update the cursor
181+
free_cursor = block.ptr;
182+
free_count = state_idx;
183+
// release the ownership of allocation
184+
block.release();
185+
return true;
186+
}
187+
188+
public:
189+
void *get() {
190+
cpp::lock_guard guard{this->mutex};
191+
// if freelist is empty, allocate a new block
192+
if (is_empty() && !allocate_new_block())
193+
return nullptr;
194+
LIBC_ASSERT(free_count != 0);
195+
void *page = free_cursor->freelist(--free_count);
196+
// move cursor to the previous block if free_count is exhausted
197+
if (free_count == 0) {
198+
free_cursor = free_cursor->prev;
199+
free_count = config.states_per_page * config.pages_per_block;
200+
}
201+
return page;
202+
}
203+
size_t state_size() const { return config.params.size_of_opaque_state; }
204+
void recycle(void *state) {
205+
cpp::lock_guard guard{this->mutex};
206+
if (is_locally_full()) {
207+
free_cursor = free_cursor->next;
208+
free_count = 0;
209+
}
210+
free_cursor->freelist(free_count++) = static_cast<char *>(state);
211+
}
212+
~MonotonicStatePool() {
213+
StateBlock *block = sentinel.next;
214+
size_t raw_size = state_block_raw_size();
215+
while (block != &sentinel) {
216+
munmap(block->pages(), config.pages_per_block * config.page_size);
217+
StateBlock *next = block->next;
218+
/* NOLINT(llvmlibc-callee-namespace) */ ::operator delete(
219+
block, raw_size, std::align_val_t{alignof(StateBlock)});
220+
block = next;
221+
}
222+
}
223+
static MonotonicStatePool *instance() {
224+
alignas(MonotonicStatePool) static char pool[sizeof(MonotonicStatePool)];
225+
static bool is_valid = false;
226+
static CallOnceFlag once_flag = callonce_impl::NOT_CALLED;
227+
callonce(&once_flag, []() {
228+
cpp::optional<GlobalConfig> config = GlobalConfig::get();
229+
if (!config)
230+
return;
231+
new (pool) MonotonicStatePool(*config);
232+
is_valid = /* NOLINT(llvmlibc-callee-namespace) */ !__cxa_atexit(
233+
[](void *) {
234+
reinterpret_cast<MonotonicStatePool *>(pool)->~MonotonicStatePool();
235+
},
236+
nullptr, __dso_handle);
237+
});
238+
if (!is_valid)
239+
return nullptr;
240+
return reinterpret_cast<MonotonicStatePool *>(pool);
241+
}
242+
};
243+
244+
// We do not guarantee the correctness of calling the cprng functions in signal
245+
// frames. However, we do want to make sure that an mistaken (maliciously
246+
// induced) call to the cprng will never cause a state to be used twice due to
247+
// reentrancy.
248+
class ThreadLocalState {
249+
void *local_state;
250+
bool in_use;
251+
size_t remaining_trials;
252+
253+
void destroy() {
254+
in_use = true;
255+
cpp::atomic_signal_fence(cpp::MemoryOrder::SEQ_CST);
256+
if (local_state)
257+
MonotonicStatePool::instance()->recycle(local_state);
258+
}
259+
260+
public:
261+
constexpr ThreadLocalState()
262+
: local_state(nullptr), in_use(false), remaining_trials(256) {}
263+
void *acquire() {
264+
if (in_use)
265+
return nullptr;
266+
in_use = true;
267+
cpp::atomic_signal_fence(cpp::MemoryOrder::SEQ_CST);
268+
if (local_state)
269+
return local_state;
270+
271+
MonotonicStatePool *pool = MonotonicStatePool::instance();
272+
if (pool && remaining_trials) {
273+
remaining_trials--;
274+
local_state = pool->get();
275+
if (local_state) {
276+
if (/* NOLINT(llvmlibc-callee-namespace) */ __cxa_thread_atexit_impl(
277+
[](void *opaque) {
278+
auto *state = static_cast<ThreadLocalState *>(opaque);
279+
state->destroy();
280+
},
281+
this, __dso_handle) != 0) {
282+
pool->recycle(local_state);
283+
local_state = nullptr;
284+
}
285+
}
286+
}
287+
return local_state;
288+
}
289+
void release() {
290+
in_use = false;
291+
cpp::atomic_signal_fence(cpp::MemoryOrder::SEQ_CST);
292+
}
293+
};
294+
thread_local ThreadLocalState local_state{};
295+
296+
template <typename G>
297+
size_t fill_buffer_impl(G generator, char *buffer, size_t length) {
298+
size_t filled = 0;
299+
while (filled < length) {
300+
int n = generator(&buffer[filled], length - filled);
301+
if (n < 0) {
302+
if (n == -EAGAIN || n == -EINTR)
303+
continue;
304+
break;
305+
}
306+
filled += static_cast<size_t>(n);
307+
}
308+
return filled;
309+
}
310+
311+
} // namespace
312+
size_t fill_buffer(char *buffer, size_t length) {
313+
void *state = local_state.acquire();
314+
if (state) {
315+
// Given state is valid, state_size shall be valid.
316+
size_t state_size = MonotonicStatePool::instance()->state_size();
317+
auto impl = [state, state_size](char *cursor, size_t len) {
318+
TypedSymbol<VDSOSym::GetRandom> vgetrandom;
319+
return vgetrandom(cursor, len, 0, state, state_size);
320+
};
321+
return fill_buffer_impl(impl, buffer, length);
322+
} else {
323+
auto impl = [](char *cursor, size_t len) {
324+
// use syscall to avoid errno handling
325+
return syscall_impl<int>(SYS_getrandom, cursor, len, 0);
326+
};
327+
return fill_buffer_impl(impl, buffer, length);
328+
}
329+
local_state.release();
330+
}
331+
} // namespace cprng
332+
} // namespace LIBC_NAMESPACE_DECL

0 commit comments

Comments
 (0)