From 84c0844f5663057e45ba05c7c2202d33108f9d91 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sat, 20 Aug 2016 02:27:33 +0200 Subject: [PATCH] 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 --- build-aux/iso-grub-cfg.sh | 2 +- init/init.8 | 12 ++- init/init.c | 46 ++++++++ kernel/include/sortix/kernel/random.h | 8 +- kernel/initrd.cpp | 4 + kernel/kernel.cpp | 40 +++++-- kernel/libk.cpp | 4 +- kernel/random.cpp | 148 ++++++++++++++++++++------ libc/include/libk.h | 4 +- libc/stdlib/arc4random_buf.c | 4 +- share/man/man7/installation.7 | 8 +- share/man/man7/kernel.7 | 4 + sysinstall/Makefile | 2 +- sysinstall/fileops.c | 36 +++++++ sysinstall/fileops.h | 1 + sysinstall/hooks.c | 31 +++++- sysinstall/sysinstall.c | 5 + 17 files changed, 308 insertions(+), 51 deletions(-) diff --git a/build-aux/iso-grub-cfg.sh b/build-aux/iso-grub-cfg.sh index aa5dac24..8ce16ee3 100755 --- a/build-aux/iso-grub-cfg.sh +++ b/build-aux/iso-grub-cfg.sh @@ -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 diff --git a/init/init.8 b/init/init.8 index 74efc369..ad3818b7 100644 --- a/init/init.8 +++ b/init/init.8 @@ -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 diff --git a/init/init.c b/init/init.c index 8db8ca78..a62831a7 100644 --- a/init/init.c +++ b/init/init.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -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(); diff --git a/kernel/include/sortix/kernel/random.h b/kernel/include/sortix/kernel/random.h index 1cc8ea51..c7656441 100644 --- a/kernel/include/sortix/kernel/random.h +++ b/kernel/include/sortix/kernel/random.h @@ -1,5 +1,5 @@ /* - * 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 +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 diff --git a/kernel/initrd.cpp b/kernel/initrd.cpp index cb7612b4..d53719f3 100644 --- a/kernel/initrd.cpp +++ b/kernel/initrd.cpp @@ -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) ) diff --git a/kernel/kernel.cpp b/kernel/kernel.cpp index f9301347..72f5e839 100644 --- a/kernel/kernel.cpp +++ b/kernel/kernel.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -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."); diff --git a/kernel/libk.cpp b/kernel/libk.cpp index 0de6f683..0bd4cf31 100644 --- a/kernel/libk.cpp +++ b/kernel/libk.cpp @@ -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" diff --git a/kernel/random.cpp b/kernel/random.cpp index 1ecfabd2..5d90644d 100644 --- a/kernel/random.cpp +++ b/kernel/random.cpp @@ -1,5 +1,5 @@ /* - * 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 + #include #include #include #include +#include #include -#include +#include +#include +#include +#include #include #include +#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; diff --git a/libc/include/libk.h b/libc/include/libk.h index bd93d07e..32a11987 100644 --- a/libc/include/libk.h +++ b/libc/include/libk.h @@ -1,5 +1,5 @@ /* - * 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); diff --git a/libc/stdlib/arc4random_buf.c b/libc/stdlib/arc4random_buf.c index 15d12104..93568034 100644 --- a/libc/stdlib/arc4random_buf.c +++ b/libc/stdlib/arc4random_buf.c @@ -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 diff --git a/share/man/man7/installation.7 b/share/man/man7/installation.7 index 8a350b4a..519303a1 100644 --- a/share/man/man7/installation.7 +++ b/share/man/man7/installation.7 @@ -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. diff --git a/share/man/man7/kernel.7 b/share/man/man7/kernel.7 index d0ce93de..214292b0 100644 --- a/share/man/man7/kernel.7 +++ b/share/man/man7/kernel.7 @@ -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 , diff --git a/sysinstall/Makefile b/sysinstall/Makefile index 79d77f52..5eb6b6a2 100644 --- a/sysinstall/Makefile +++ b/sysinstall/Makefile @@ -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 diff --git a/sysinstall/fileops.c b/sysinstall/fileops.c index 27dbb10c..b3201d35 100644 --- a/sysinstall/fileops.c +++ b/sysinstall/fileops.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -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); +} diff --git a/sysinstall/fileops.h b/sysinstall/fileops.h index 0f497dc7..0f3991a3 100644 --- a/sysinstall/fileops.h +++ b/sysinstall/fileops.h @@ -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 diff --git a/sysinstall/hooks.c b/sysinstall/hooks.c index eac54ff3..4d74b316 100644 --- a/sysinstall/hooks.c +++ b/sysinstall/hooks.c @@ -17,8 +17,15 @@ * Upgrade compatibility hooks. */ -#include +#include +#include +#include +#include +#include +#include + +#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); + } } diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c index e048d226..5daf5ccd 100644 --- a/sysinstall/sysinstall.c +++ b/sysinstall/sysinstall.c @@ -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 )