1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 02:17:35 +00:00

Kernel: Make KernelRng thread-safe

This adds an optional argument to get_good_random_bytes that can be
used to only return randomness if it doesn't have to block.
Also add a SpinLock around using FortunaPRNG.

Fixes #5132
This commit is contained in:
Tom 2021-01-27 13:16:30 -07:00 committed by Andreas Kling
parent 8104abf640
commit e2d7945e0c
2 changed files with 77 additions and 25 deletions

View file

@ -88,6 +88,7 @@ KernelRng::KernelRng()
void KernelRng::wait_for_entropy() void KernelRng::wait_for_entropy()
{ {
ScopedSpinLock lock(get_lock());
if (!resource().is_ready()) { if (!resource().is_ready()) {
dbgln("Entropy starvation..."); dbgln("Entropy starvation...");
m_seed_queue.wait_on({}, "KernelRng"); m_seed_queue.wait_on({}, "KernelRng");
@ -96,6 +97,7 @@ void KernelRng::wait_for_entropy()
void KernelRng::wake_if_ready() void KernelRng::wake_if_ready()
{ {
ASSERT(get_lock().is_locked());
if (resource().is_ready()) { if (resource().is_ready()) {
m_seed_queue.wake_all(); m_seed_queue.wake_all();
} }
@ -103,26 +105,9 @@ void KernelRng::wake_if_ready()
size_t EntropySource::next_source { static_cast<size_t>(EntropySource::Static::MaxHardcodedSourceIndex) }; size_t EntropySource::next_source { static_cast<size_t>(EntropySource::Static::MaxHardcodedSourceIndex) };
void get_good_random_bytes(u8* buffer, size_t buffer_size) static void do_get_fast_random_bytes(u8* buffer, size_t buffer_size)
{ {
KernelRng::the().wait_for_entropy(); static Atomic<u32, AK::MemoryOrder::memory_order_relaxed> next = 1;
// FIXME: What if interrupts are disabled because we're in an interrupt?
if (are_interrupts_enabled()) {
LOCKER(KernelRng::the().lock());
KernelRng::the().resource().get_random_bytes(buffer, buffer_size);
} else {
KernelRng::the().resource().get_random_bytes(buffer, buffer_size);
}
}
void get_fast_random_bytes(u8* buffer, size_t buffer_size)
{
if (KernelRng::the().resource().is_ready()) {
return get_good_random_bytes(buffer, buffer_size);
}
static u32 next = 1;
union { union {
u8 bytes[4]; u8 bytes[4];
@ -131,12 +116,67 @@ void get_fast_random_bytes(u8* buffer, size_t buffer_size)
size_t offset = 4; size_t offset = 4;
for (size_t i = 0; i < buffer_size; ++i) { for (size_t i = 0; i < buffer_size; ++i) {
if (offset >= 4) { if (offset >= 4) {
next = next * 1103515245 + 12345; auto current_next = next.load();
u.value = next; for (;;) {
auto new_next = current_next * 1103515245 + 12345;
if (next.compare_exchange_strong(current_next, new_next)) {
u.value = new_next;
break;
}
}
offset = 0; offset = 0;
} }
buffer[i] = u.bytes[offset++]; buffer[i] = u.bytes[offset++];
} }
} }
bool get_good_random_bytes(u8* buffer, size_t buffer_size, bool allow_wait, bool fallback_to_fast)
{
bool result = false;
auto& kernel_rng = KernelRng::the();
// FIXME: What if interrupts are disabled because we're in an interrupt?
bool can_wait = are_interrupts_enabled();
if (!can_wait && allow_wait) {
// If we can't wait but the caller would be ok with it, then we
// need to definitely fallback to *something*, even if it's less
// secure...
fallback_to_fast = true;
}
if (can_wait && allow_wait) {
for (;;) {
{
LOCKER(KernelRng::the().lock());
if (kernel_rng.resource().get_random_bytes(buffer, buffer_size)) {
result = true;
break;
}
}
kernel_rng.wait_for_entropy();
}
} else {
// We can't wait/block here, or we are not allowed to block/wait
if (kernel_rng.resource().get_random_bytes(buffer, buffer_size)) {
result = true;
} else if (fallback_to_fast) {
// If interrupts are disabled
do_get_fast_random_bytes(buffer, buffer_size);
result = true;
}
}
// NOTE: The only case where this function should ever return false and
// not actually return random data is if fallback_to_fast == false and
// allow_wait == false and interrupts are enabled!
ASSERT(result || !fallback_to_fast);
return result;
}
void get_fast_random_bytes(u8* buffer, size_t buffer_size)
{
// Try to get good randomness, but don't block if we can't right now
// and allow falling back to fast randomness
auto result = get_good_random_bytes(buffer, buffer_size, false, true);
ASSERT(result);
}
} }

View file

@ -55,8 +55,11 @@ public:
{ {
} }
void get_random_bytes(u8* buffer, size_t n) bool get_random_bytes(u8* buffer, size_t n)
{ {
ScopedSpinLock lock(m_lock);
if (!is_ready())
return false;
if (m_p0_len >= reseed_threshold) { if (m_p0_len >= reseed_threshold) {
this->reseed(); this->reseed();
} }
@ -75,6 +78,7 @@ public:
// Extract a new key from the prng stream. // Extract a new key from the prng stream.
Bytes key_span = m_key.bytes(); Bytes key_span = m_key.bytes();
cipher.key_stream(key_span, counter_span, &counter_span); cipher.key_stream(key_span, counter_span, &counter_span);
return true;
} }
template<typename T> template<typename T>
@ -94,9 +98,12 @@ public:
[[nodiscard]] bool is_ready() const [[nodiscard]] bool is_ready() const
{ {
ASSERT(m_lock.is_locked());
return is_seeded() || m_p0_len >= reseed_threshold; return is_seeded() || m_p0_len >= reseed_threshold;
} }
SpinLock<u8>& get_lock() { return m_lock; }
private: private:
void reseed() void reseed()
{ {
@ -121,6 +128,7 @@ private:
size_t m_p0_len { 0 }; size_t m_p0_len { 0 };
ByteBuffer m_key; ByteBuffer m_key;
HashType m_pools[pool_count]; HashType m_pools[pool_count];
SpinLock<u8> m_lock;
}; };
class KernelRng : public Lockable<FortunaPRNG<Crypto::Cipher::AESCipher, Crypto::Hash::SHA256, 256>> { class KernelRng : public Lockable<FortunaPRNG<Crypto::Cipher::AESCipher, Crypto::Hash::SHA256, 256>> {
@ -134,6 +142,8 @@ public:
void wake_if_ready(); void wake_if_ready();
SpinLock<u8>& get_lock() { return resource().get_lock(); }
private: private:
WaitQueue m_seed_queue; WaitQueue m_seed_queue;
}; };
@ -165,11 +175,13 @@ public:
template<typename T> template<typename T>
void add_random_event(const T& event_data) void add_random_event(const T& event_data)
{ {
auto& kernel_rng = KernelRng::the();
ScopedSpinLock lock(kernel_rng.get_lock());
// We don't lock this because on the off chance a pool is corrupted, entropy isn't lost. // We don't lock this because on the off chance a pool is corrupted, entropy isn't lost.
Event<T> event = { read_tsc(), m_source, event_data }; Event<T> event = { read_tsc(), m_source, event_data };
KernelRng::the().resource().add_random_event(event, m_pool); kernel_rng.resource().add_random_event(event, m_pool);
m_pool++; m_pool++;
KernelRng::the().wake_if_ready(); kernel_rng.wake_if_ready();
} }
private: private:
@ -182,7 +194,7 @@ private:
// The only difference is that get_fast_random is guaranteed not to block. // The only difference is that get_fast_random is guaranteed not to block.
void get_fast_random_bytes(u8*, size_t); void get_fast_random_bytes(u8*, size_t);
void get_good_random_bytes(u8*, size_t); bool get_good_random_bytes(u8*, size_t, bool allow_wait = true, bool fallback_to_fast = true);
template<typename T> template<typename T>
inline T get_fast_random() inline T get_fast_random()