Commit 84c0844f authored by Jonas Termansen's avatar Jonas Termansen

Seed kernel entropy with randomness from the previous boot.

The bootloader will now load the /boot/random.seed file if it exists, in
which case the kernel will use it as the initial kernel entropy. The kernel
warns if no random seed was loaded, unless the --no-random-seed option was
given. This option is used for live environments that inherently have no
prior secret state. The kernel initializes its entropy pool from the random
seed as of the first things, so randomness is available very early on.

init(8) will emit a fresh /boot/random.seed file on boot to avoid the same
entropy being used twice. init(8) also writes out /boot/random.seed on
system shutdown where the system has the most entropy. init(8) will warn if
writing the file fails, except if /boot is a real-only filesystem, and
keeping such state is impossible. The system administrator is then
responsible for ensuring the bootloader somehow passes a fresh random seed
on the next boot.

/boot/random.seed must be owned by the root user and root group and must
have file permissions 600 to avoid unprivileged users can read it. The file
is passed to the kernel by the bootloader as a multiboot module with the
command line --random-seed.

If no random seed is loaded, the kernel attempts a poor quality fallback
where it seeds the kernel arc4random(3) continuously with the current time.
The timing variance may provide some effective entropy. There is no real
kernel entropy gathering yet. The read of the CMOS real time clock is moved
to an early point in the kernel boot, so the current time is available as
fallback entropy.

The kernel access of the random seed module is supposed to be infallible
and happens before the kernel log is set up, but there is not yet a failsafe
API for mapping single pages in the early kernel.

sysupgrade(8) creates /boot/random.seed if it's absent as a temporary
compatibility measure for people upgrading from the 1.0 release. The GRUB
port will need to be upgraded with support for /boot/random.seed in the
10_sortix script. Installation with manual bootloader configuration will
need to load the random seed with the --random-seed command line. With GRUB,
this can be done with: module /boot/random.seed --random-seed
parent 4ab5765a
......@@ -162,7 +162,7 @@ EOF
esac
cat << EOF
echo -n "Loading /$kernel ($(human_size $kernel)) ... "
multiboot /$kernel "\$@"
multiboot /$kernel --no-random-seed "\$@"
echo done
EOF
for initrd in $system_initrd $src_initrd $live_initrd $overlay_initrd; do
......
......@@ -127,6 +127,14 @@ set graphics resolution (see
.Nm
mounts all the filesystems according to
.Xr fstab 5 .
.Ss Random Seed
.Nm
will write 256 bytes of randomness to
.Pa /boot/random.seed ,
which serves as the initial entropy for the
.Xr kernel 7
on the next boot. The file is also written on system shutdown where the
system has the most entropy.
.Ss Merge
The
.Sy merge
......@@ -174,7 +182,9 @@ sortix
root
.El
.Sh FILES
.Bl -tag -width "/etc/init/target" -compact
.Bl -tag -width "/boot/random.seed" -compact
.It Pa /boot/random.seed
initial kernel entropy
.It Pa /etc/init/target
default initialization target
.It Pa /etc/fstab
......
......@@ -31,6 +31,7 @@
#include <fcntl.h>
#include <fstab.h>
#include <grp.h>
#include <ioleast.h>
#include <locale.h>
#include <pwd.h>
#include <signal.h>
......@@ -148,6 +149,48 @@ static void note(const char* format, ...)
va_end(ap);
}
static void write_random_seed(void)
{
const char* will_not = "next boot will not have fresh randomness";
const char* path = "/boot/random.seed";
int fd = open(path, O_WRONLY | O_CREAT | O_NOFOLLOW, 0600);
if ( fd < 0 )
{
if ( errno != ENOENT && errno != EROFS )
warning("%s: %s: %m", will_not, path);
return;
}
if ( fchown(fd, 0, 0) < 0 )
{
warning("%s: chown: %s: %m", will_not, path);
close(fd);
return;
}
if ( fchmod(fd, 0600) < 0 )
{
warning("%s: chown: %s: %m", will_not, path);
close(fd);
return;
}
unsigned char buf[256];
arc4random_buf(buf, sizeof(buf));
size_t done = writeall(fd, buf, sizeof(buf));
explicit_bzero(buf, sizeof(buf));
if ( done < sizeof(buf) )
{
warning("%s: write: %s: %m", will_not, path);
close(fd);
return;
}
if ( ftruncate(fd, sizeof(buf)) < 0 )
{
warning("%s: truncate: %s: %m", will_not, path);
close(fd);
return;
}
close(fd);
}
static void prepare_filesystem(const char* path, struct blockdevice* bdev)
{
enum filesystem_error fserr = blockdevice_inspect_filesystem(&bdev->fs, bdev);
......@@ -732,6 +775,8 @@ static void niht(void)
if ( getpid() != main_pid )
return;
write_random_seed();
if ( chain_location_dev_made )
{
unmount(chain_location_dev, 0);
......@@ -756,6 +801,7 @@ static int init(const char* target)
prepare_block_devices();
load_fstab();
mountpoints_mount(false);
write_random_seed();
if ( !strcmp(target, "merge") )
{
pid_t child_pid = fork();
......
/*
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2016 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
......@@ -22,11 +22,15 @@
#include <stddef.h>
typedef struct multiboot_info multiboot_info_t;
namespace Sortix {
namespace Random {
bool HasEntropy();
void Init(multiboot_info_t* bootinfo);
bool HasEntropy(size_t amount);
void GetEntropy(void* buffer, size_t size);
int GetFallbackStatus();
} // namespace Random
} // namespace Sortix
......
......@@ -728,6 +728,10 @@ static void ExtractModule(struct multiboot_mod_list* module,
size_t mod_size = module->mod_end - module->mod_start;
const char* cmdline = (const char*) (uintptr_t) module->cmdline;
// Ignore the random seed.
if ( !strcmp(cmdline, "--random-seed") )
return;
// Allocate the needed kernel virtual address space.
addralloc_t initrd_addr_alloc;
if ( !AllocateKernelAddress(&initrd_addr_alloc, mod_size) )
......
......@@ -53,6 +53,7 @@
#include <sortix/kernel/pci.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/ptable.h>
#include <sortix/kernel/random.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/signal.h>
......@@ -166,6 +167,9 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
// Detect available physical memory.
Memory::Init(bootinfo);
// Initialize randomness from the random seed if provided.
Random::Init(bootinfo);
// Initialize the kernel log.
Log::Init(bootinfo);
......@@ -228,6 +232,7 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
FreeKernelAddress(&alloc);
}
bool no_random_seed = false;
char* arg_saved = cmdline;
char* arg;
struct kernel_option
......@@ -237,6 +242,7 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
} options[] =
{
{ "--init", true },
{ "--no-random-seed", false },
};
size_t options_count = sizeof(options) / sizeof(options[0]);
while ( (arg = cmdline_tokenize(&arg_saved)) )
......@@ -277,6 +283,8 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
}
if ( !strcmp(option->name, "--init") )
init_cmdline = parameter;
else if ( !strcmp(option->name, "--no-random-seed") )
no_random_seed = true;
}
// Initialize the interrupt handler table and enable interrupts.
......@@ -285,13 +293,36 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
// Initialize the interrupt worker (before scheduling is enabled).
Interrupt::InitWorker();
// Initialize the clocks.
Time::Init();
// Initialize the real-time clock.
CMOS::Init();
// Check a random seed was provided, or try to fallback and warn.
int random_status = Random::GetFallbackStatus();
if ( random_status )
{
if ( no_random_seed )
{
// There's not much we can if this is an initial boot.
}
else if ( random_status == 1 )
{
Log::PrintF("kernel: warning: No random seed file was loaded\n");
Log::PrintF("kernel: warning: With GRUB, try: "
"module /boot/random.seed --random-seed\n");
}
else
{
Log::PrintF("kernel: warning: The random seed file is too small\n");
}
}
//
// Stage 2. Transition to Multithreaded Environment
//
// Initialize the clocks.
Time::Init();
// Initialize Unix Signals.
Signal::Init();
......@@ -424,9 +455,6 @@ static void BootThread(void* /*user*/)
// Stage 5. Loading and Initializing Core Drivers.
//
// Initialize the real-time clock.
CMOS::Init();
// Get a descriptor for the /dev directory so we can populate it.
if ( droot->mkdir(&ctx, "dev", 0775) != 0 && errno != EEXIST )
Panic("Unable to create RAM filesystem /dev directory.");
......
......@@ -109,9 +109,9 @@ void libk_random_unlock(void)
}
extern "C"
bool libk_hasentropy(void)
bool libk_hasentropy(size_t amount)
{
return Sortix::Random::HasEntropy();
return Sortix::Random::HasEntropy(amount);
}
extern "C"
......
/*
* Copyright (c) 2014, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2014, 2015, 2016 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
......@@ -17,54 +17,142 @@
* Kernel entropy gathering.
*/
#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sortix/clock.h>
#include <sortix/kernel/addralloc.h>
#include <sortix/kernel/copy.h>
#include <sortix/kernel/interlock.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 unsigned long sequence = 0;
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;
bool HasEntropy()
void Init(multiboot_info_t* bootinfo)
{
// We only have new entropy once and that's at boot.
return sequence == 0;
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)
{
union
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
{
unsigned char buffer[256];
struct
{
struct timespec realtime;
struct timespec monotonic;
unsigned long sequence;
} seed;
};
if ( sizeof(buffer) < size )
size = sizeof(buffer);
// TODO: SECURITY: We need to actually gather entropy and deliver it.
for ( size_t i = 0; i < size; i++ )
buffer[i] = i;
// NOTE: This is not random and is not meant to be random, this is just
// meant to make the returned entropy a little different each time
// until we have real randomness, even across reboots. The userland
// arc4random mixer will mix it around and the produced streams will
// look random and should not repeat in practice.
seed.realtime = Time::Get(CLOCK_REALTIME);
seed.monotonic = Time::Get(CLOCK_MONOTONIC);
seed.sequence = InterlockedIncrement(&sequence).o;
memcpy(result, buffer, size);
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
......@@ -77,7 +165,7 @@ int sys_getentropy(void* user_buffer, size_t size)
unsigned char buffer[256];
if ( sizeof(buffer) < size )
return errno = EIO, -1;
arc4random_buf(buffer, sizeof(buffer));
arc4random_buf(buffer, size);
if ( !CopyToUser(user_buffer, buffer, size) )
return -1;
return 0;
......
/*
* Copyright (c) 2011, 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 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
......@@ -43,7 +43,7 @@ __attribute__((noreturn))
void libk_abort(void);
void libk_random_lock(void);
void libk_random_unlock(void);
bool libk_hasentropy(void);
bool libk_hasentropy(size_t);
void libk_getentropy(void*, size_t);
__attribute__((noreturn))
void libk_ubsan_abort(const char*, const char*, uint32_t, uint32_t);
......
......@@ -168,12 +168,13 @@ static unsigned char rs_buf[16 * 64];
void arc4random_buf(void* buffer_ptr, size_t size)
{
unsigned char entropy[KEYSZ + IVSZ];
unsigned char* buffer = (unsigned char*) buffer_ptr;
#ifdef __is_sortix_libk
libk_random_lock();
if ( libk_hasentropy() )
if ( libk_hasentropy(sizeof(entropy)) )
{
rs_count = 0;
rs_have = 0;
......@@ -215,7 +216,6 @@ void arc4random_buf(void* buffer_ptr, size_t size)
if ( rs_count == 0 )
{
unsigned char entropy[KEYSZ + IVSZ];
#ifdef __is_sortix_libk
libk_getentropy(entropy, sizeof(entropy));
#else
......
......@@ -251,9 +251,11 @@ installation. If you answer yes, then the installation will begin.
The installer will copy the live environment into the target root filesystem
according to the file lists in
.Pa /tix/manifest
and create configuration files matching your earlier choices. It will generate
an initrd that locates and boots the root filesystem. It will install the
bootloader if desired. The installation will take a moment.
and create configuration files matching your earlier choices. It will write
256 bytes of randomness to
.Pa /boot/random.seed .
It will generate an initrd that locates and boots the root filesystem. It will
install the bootloader if desired. The installation will take a moment.
.Ss Configuration
After the installation is complete, a bare system is installed but it lacks
crucial configuration files and it will refuse to start when booted.
......
......@@ -34,6 +34,10 @@ The
argument is split into tokens and used as the command line to invoke the
specified
.Xr init 8 .
.It Fl \-no-random-seed
Don't warn if no random seed file was loaded by the bootloader (usually from
.Pa /boot/random.seed ) .
This option is useful for live environments where this situation is unavoidable.
.El
.Sh SEE ALSO
.Xr initrd 7 ,
......
......@@ -68,7 +68,7 @@ conf.o: conf.h
devices.o: devices.h
execute.o: execute.h
fileops.o: fileops.h
hooks.o: release.h
hooks.o: fileops.h release.h
interactive.o: interactive.h execute.h
manifest.o: manifest.h execute.h fileops.h
release.o: release.h
......
......@@ -22,6 +22,7 @@
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ioleast.h>
#include <libgen.h>
#include <stdbool.h>
#include <stdio.h>
......@@ -93,3 +94,38 @@ void mkdir_or_chmod_or_die(const char* path, mode_t mode)
}
err(2, "mkdir: %s", path);
}
void write_random_seed(const char* path)
{
int fd = open(path, O_WRONLY | O_CREAT | O_NOFOLLOW, 0600);
if ( fd < 0 )
{
warn("%s", path);
_exit(2);
}
if ( fchown(fd, 0, 0) < 0 )
{
warn("chown: %s", path);
_exit(2);
}
if ( fchmod(fd, 0600) < 0 )
{
warn("chmod: %s", path);
_exit(2);
}
unsigned char buf[256];
arc4random_buf(buf, sizeof(buf));
size_t done = writeall(fd, buf, sizeof(buf));
explicit_bzero(buf, sizeof(buf));
if ( done < sizeof(buf) )
{
warn("write: %s", path);
_exit(2);
}
if ( ftruncate(fd, sizeof(buf)) < 0 )
{
warn("truncate: %s", path);
_exit(2);
}
close(fd);
}
......@@ -24,5 +24,6 @@ char* join_paths(const char* a, const char* b);
int mkdir_p(const char* path, mode_t mode);
int access_or_die(const char* path, int mode);
void mkdir_or_chmod_or_die(const char* path, mode_t mode);
void write_random_seed(const char* path);
#endif
......@@ -17,8 +17,15 @@
* Upgrade compatibility hooks.
*/
#include <sys/types.h>
#include <err.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "fileops.h"
#include "hooks.h"
#include "release.h"
......@@ -42,4 +49,26 @@ void upgrade_finalize(const struct release* old_release,
(void) new_release;
(void) source_prefix;
(void) target_prefix;
// TODO: After releasing Sortix 1.1, remove this compatibility.
if ( old_release->version_major < 1 ||
(old_release->version_major == 1 &&
old_release->version_minor < 1) ||
(old_release->version_major == 1 &&
old_release->version_minor == 1 &&
old_release->version_dev) )
{
char* random_seed_path;
if ( asprintf(&random_seed_path, "%sboot/random.seed", target_prefix) < 0 )
{
warn("asprintf");
_exit(1);
}
if ( access_or_die(random_seed_path, F_OK) < 0 )
{
printf(" - Creating random seed...\n");
write_random_seed(random_seed_path);
}
free(random_seed_path);
}
}
......@@ -750,6 +750,11 @@ int main(void)
// TODO: Auto detect appropriate bcrypt rounds and set up etc/login.conf
// and use those below instead of blowfish,a.
install_ports("", ".");
if ( access_or_die("boot/random.seed", F_OK) < 0 )
{
printf(" - Creating random seed...\n");
write_random_seed("boot/random.seed");
}
printf(" - Creating initrd...\n");
execute((const char*[]) { "update-initrd", "--sysroot", fs, NULL }, "_e");
if ( strcasecmp(accept_grub, "yes") == 0 )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment