sortix-mirror/kernel/random.cpp
Jonas 'Sortie' Termansen 6197e05a23 Add GETENTROPY_MAX.
2024-06-25 15:27:13 +02:00

175 lines
5.2 KiB
C++

/*
* Copyright (c) 2014, 2015, 2016, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* random.cpp
* Kernel entropy gathering.
*/
#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sortix/clock.h>
#include <sortix/limits.h>
#include <sortix/kernel/addralloc.h>
#include <sortix/kernel/copy.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/memorymanagement.h>
#include <sortix/kernel/random.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/time.h>
#include "multiboot.h"
// TODO: This implementation does not gather entropy. No claims are being made
// whatsoever that this implementation is secure. It isn't secure.
namespace Sortix {
namespace Random {
static kthread_mutex_t entropy_lock = KTHREAD_MUTEX_INITIALIZER;
static unsigned char entropy[256];
static size_t entropy_used = 0;
static size_t entropy_available = 0;
static bool any_random_seed = false;
static bool fallback = false;
void Init(multiboot_info_t* bootinfo)
{
size_t offset = 0;
// TODO: This assumes the multiboot structures are accessible. That
// assumption is wrong in general and we should map them ourselves in
// manner that cannot fail.
struct multiboot_mod_list* modules =
(struct multiboot_mod_list*) (uintptr_t) bootinfo->mods_addr;
for ( uint32_t i = 0; i < bootinfo->mods_count; i++ )
{
struct multiboot_mod_list* module = &modules[i];
// TODO: This assumes module is mapped.
size_t mod_size = module->mod_end - module->mod_start;
const char* cmdline = (const char*) (uintptr_t) module->cmdline;
// TODO: This assumes cmdline is mapped.
if ( strcmp(cmdline, "--random-seed") != 0 )
continue;
any_random_seed = true;
// TODO: Make an early map facility that cannot fail and use it to map
// the random seed.
// TODO: AllocateKernelAddress might invoke randomness in some way in
// future.
addralloc_t addralloc;
if ( !AllocateKernelAddress(&addralloc, mod_size) )
PanicF("Random::Init AllocateKernelAddress failed: %m");
addr_t physfrom = module->mod_start;
addr_t mapat = addralloc.from;
for ( size_t i = 0; i < mod_size; i += Page::Size() )
{
if ( !Memory::Map(physfrom + i, mapat + i, PROT_KREAD | PROT_KWRITE) )
PanicF("Random::Init Memory::Map failed: %m");
}
Memory::Flush();
unsigned char* seed = (unsigned char*) addralloc.from;
for ( size_t i = 0; i < mod_size; i++ )
{
entropy[offset++] ^= seed[i];
if ( entropy_available < offset )
entropy_available = offset;
offset %= sizeof(entropy);
}
explicit_bzero(seed, mod_size);
for ( size_t i = 0; i < mod_size; i += Page::Size() )
Memory::Unmap(mapat + i);
Memory::Flush();
FreeKernelAddress(&addralloc);
}
fallback = entropy_available < sizeof(entropy);
}
int GetFallbackStatus()
{
// If in fallback mode, mix in the current time. No particular reason.
(void) arc4random();
ScopedLock lock(&entropy_lock);
if ( !any_random_seed )
return 1;
if ( fallback )
return 2;
return 0;
}
bool HasEntropy(size_t amount)
{
ScopedLock lock(&entropy_lock);
if ( amount <= entropy_available )
return true;
// Keep mixing fallback values (current time) in the hope it's better than
// nothing.
if ( fallback )
return true;
return false;
}
void GetEntropy(void* result, size_t size)
{
kthread_mutex_lock(&entropy_lock);
size_t amount = size < entropy_available ? size : entropy_available;
memcpy(result, entropy + entropy_used, amount);
explicit_bzero(entropy + entropy_used, amount);
entropy_used += amount;
entropy_available -= amount;
kthread_mutex_unlock(&entropy_lock);
// If more entropy is needed, mix in the current time and the time since
// boot in the hope that the unpredictability of exactly when randomness is
// consumed introduces some entropy.
struct
{
struct timespec realtime;
struct timespec monotonic;
} seed;
size_t sofar = amount;
while ( sofar < size )
{
seed.realtime = Time::Get(CLOCK_REALTIME);
seed.monotonic = Time::Get(CLOCK_MONOTONIC);
unsigned char* out = (unsigned char*) result + sofar;
size_t left = size - sofar;
amount = left < sizeof(seed) ? left : sizeof(seed);
memcpy(out, &seed, amount);
sofar += amount;
}
explicit_bzero(&seed, sizeof(seed));
}
} // namespace Random
} // namespace Sortix
namespace Sortix {
int sys_getentropy(void* user_buffer, size_t size)
{
unsigned char buffer[GETENTROPY_MAX];
if ( sizeof(buffer) < size )
return errno = EIO, -1;
arc4random_buf(buffer, size);
if ( !CopyToUser(user_buffer, buffer, size) )
return -1;
return 0;
}
} // namespace Sortix