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 )