import ./make-test.nix ({ pkgs, ... }: { name = "boot-stage1"; machine = { config, pkgs, lib, ... }: { boot.extraModulePackages = let compileKernelModule = name: source: pkgs.runCommandCC name rec { inherit source; kdev = config.boot.kernelPackages.kernel.dev; kver = config.boot.kernelPackages.kernel.modDirVersion; ksrc = "${kdev}/lib/modules/${kver}/build"; hardeningDisable = [ "pic" ]; nativeBuildInputs = kdev.moduleBuildDependencies; } '' echo "obj-m += $name.o" > Makefile echo "$source" > "$name.c" make -C "$ksrc" M=$(pwd) modules install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko" ''; # This spawns a kthread which just waits until it gets a signal and # terminates if that is the case. We want to make sure that nothing during # the boot process kills any kthread by accident, like what happened in # issue #15226. kcanary = compileKernelModule "kcanary" '' #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #include #endif struct task_struct *canaryTask; static int kcanary(void *nothing) { allow_signal(SIGINT); allow_signal(SIGTERM); allow_signal(SIGKILL); while (!kthread_should_stop()) { set_current_state(TASK_INTERRUPTIBLE); schedule_timeout_interruptible(msecs_to_jiffies(100)); if (signal_pending(current)) break; } return 0; } static int kcanaryInit(void) { kthread_run(&kcanary, NULL, "kcanary"); return 0; } static void kcanaryExit(void) { kthread_stop(canaryTask); } module_init(kcanaryInit); module_exit(kcanaryExit); ''; in lib.singleton kcanary; boot.initrd.kernelModules = [ "kcanary" ]; boot.initrd.extraUtilsCommands = let compile = name: source: pkgs.runCommandCC name { inherit source; } '' mkdir -p "$out/bin" echo "$source" | gcc -Wall -o "$out/bin/$name" -xc - ''; daemonize = name: source: compile name '' #include #include void runSource(void) { ${source} } int main(void) { if (fork() > 0) return 0; setsid(); runSource(); return 1; } ''; mkCmdlineCanary = { name, cmdline ? "", source ? "" }: (daemonize name '' char *argv[] = {"${cmdline}", NULL}; execvp("${name}-child", argv); '') // { child = compile "${name}-child" '' #include #include int main(void) { ${source} while (1) sleep(1); return 1; } ''; }; copyCanaries = with lib; concatMapStrings (canary: '' ${optionalString (canary ? child) '' copy_bin_and_libs "${canary.child}/bin/${canary.child.name}" ''} copy_bin_and_libs "${canary}/bin/${canary.name}" ''); in copyCanaries [ # Simple canary process which just sleeps forever and should be killed by # stage 2. (daemonize "canary1" "while (1) sleep(1);") # We want this canary process to try mimicking a kthread using a cmdline # with a zero length so we can make sure that the process is properly # killed in stage 1. (mkCmdlineCanary { name = "canary2"; source = '' FILE *f; f = fopen("/run/canary2.pid", "w"); fprintf(f, "%d\n", getpid()); fclose(f); ''; }) # This canary process mimicks a storage daemon, which we do NOT want to be # killed before going into stage 2. For more on root storage daemons, see: # https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/ (mkCmdlineCanary { name = "canary3"; cmdline = "@canary3"; }) ]; boot.initrd.postMountCommands = '' canary1 canary2 canary3 # Make sure the pidfile of canary 2 is created so that we still can get # its former pid after the killing spree starts next within stage 1. while [ ! -s /run/canary2.pid ]; do sleep 0.1; done ''; }; testScript = '' $machine->waitForUnit("multi-user.target"); $machine->succeed('test -s /run/canary2.pid'); $machine->fail('pgrep -a canary1'); $machine->fail('kill -0 $(< /run/canary2.pid)'); $machine->succeed('pgrep -a -f \'^@canary3$\'''); $machine->succeed('pgrep -a -f \'^kcanary$\'''); ''; meta.maintainers = with pkgs.stdenv.lib.maintainers; [ aszlig ]; })