1{ pkgs, ... }: 2{ 3 name = "boot-stage1"; 4 5 nodes.machine = 6 { 7 config, 8 pkgs, 9 lib, 10 ... 11 }: 12 { 13 boot.extraModulePackages = 14 let 15 compileKernelModule = 16 name: source: 17 pkgs.runCommandCC name 18 rec { 19 inherit source; 20 kdev = config.boot.kernelPackages.kernel.dev; 21 kver = config.boot.kernelPackages.kernel.modDirVersion; 22 ksrc = "${kdev}/lib/modules/${kver}/build"; 23 hardeningDisable = [ "pic" ]; 24 nativeBuildInputs = kdev.moduleBuildDependencies; 25 } 26 '' 27 echo "obj-m += $name.o" > Makefile 28 echo "$source" > "$name.c" 29 make -C "$ksrc" M=$(pwd) modules 30 install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko" 31 ''; 32 33 # This spawns a kthread which just waits until it gets a signal and 34 # terminates if that is the case. We want to make sure that nothing during 35 # the boot process kills any kthread by accident, like what happened in 36 # issue #15226. 37 kcanary = compileKernelModule "kcanary" '' 38 #include <linux/version.h> 39 #include <linux/init.h> 40 #include <linux/module.h> 41 #include <linux/kernel.h> 42 #include <linux/kthread.h> 43 #include <linux/sched.h> 44 #include <linux/signal.h> 45 #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) 46 #include <linux/sched/signal.h> 47 #endif 48 49 MODULE_LICENSE("GPL"); 50 51 struct task_struct *canaryTask; 52 53 static int kcanary(void *nothing) 54 { 55 allow_signal(SIGINT); 56 allow_signal(SIGTERM); 57 allow_signal(SIGKILL); 58 while (!kthread_should_stop()) { 59 set_current_state(TASK_INTERRUPTIBLE); 60 schedule_timeout_interruptible(msecs_to_jiffies(100)); 61 if (signal_pending(current)) break; 62 } 63 return 0; 64 } 65 66 static int kcanaryInit(void) 67 { 68 kthread_run(&kcanary, NULL, "kcanary"); 69 return 0; 70 } 71 72 static void kcanaryExit(void) 73 { 74 kthread_stop(canaryTask); 75 } 76 77 module_init(kcanaryInit); 78 module_exit(kcanaryExit); 79 ''; 80 81 in 82 lib.singleton kcanary; 83 84 boot.initrd.kernelModules = [ "kcanary" ]; 85 86 boot.initrd.extraUtilsCommands = 87 let 88 compile = 89 name: source: 90 pkgs.runCommandCC name { inherit source; } '' 91 mkdir -p "$out/bin" 92 echo "$source" | gcc -Wall -o "$out/bin/$name" -xc - 93 ''; 94 95 daemonize = 96 name: source: 97 compile name '' 98 #include <stdio.h> 99 #include <unistd.h> 100 101 void runSource(void) { 102 ${source} 103 } 104 105 int main(void) { 106 if (fork() > 0) return 0; 107 setsid(); 108 runSource(); 109 return 1; 110 } 111 ''; 112 113 mkCmdlineCanary = 114 { 115 name, 116 cmdline ? "", 117 source ? "", 118 }: 119 (daemonize name '' 120 char *argv[] = {"${cmdline}", NULL}; 121 execvp("${name}-child", argv); 122 '') 123 // { 124 child = compile "${name}-child" '' 125 #include <stdio.h> 126 #include <unistd.h> 127 128 int main(void) { 129 ${source} 130 while (1) sleep(1); 131 return 1; 132 } 133 ''; 134 }; 135 136 copyCanaries = lib.concatMapStrings (canary: '' 137 ${lib.optionalString (canary ? child) '' 138 copy_bin_and_libs "${canary.child}/bin/${canary.child.name}" 139 ''} 140 copy_bin_and_libs "${canary}/bin/${canary.name}" 141 ''); 142 143 in 144 copyCanaries [ 145 # Simple canary process which just sleeps forever and should be killed by 146 # stage 2. 147 (daemonize "canary1" "while (1) sleep(1);") 148 149 # We want this canary process to try mimicking a kthread using a cmdline 150 # with a zero length so we can make sure that the process is properly 151 # killed in stage 1. 152 (mkCmdlineCanary { 153 name = "canary2"; 154 source = '' 155 FILE *f; 156 f = fopen("/run/canary2.pid", "w"); 157 fprintf(f, "%d\n", getpid()); 158 fclose(f); 159 ''; 160 }) 161 162 # This canary process mimics a storage daemon, which we do NOT want to be 163 # killed before going into stage 2. For more on root storage daemons, see: 164 # https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/ 165 (mkCmdlineCanary { 166 name = "canary3"; 167 cmdline = "@canary3"; 168 }) 169 ]; 170 171 boot.initrd.postMountCommands = '' 172 canary1 173 canary2 174 canary3 175 # Make sure the pidfile of canary 2 is created so that we still can get 176 # its former pid after the killing spree starts next within stage 1. 177 while [ ! -s /run/canary2.pid ]; do sleep 0.1; done 178 ''; 179 }; 180 181 testScript = '' 182 machine.wait_for_unit("multi-user.target") 183 machine.succeed("test -s /run/canary2.pid") 184 machine.fail("pgrep -a canary1") 185 machine.fail("kill -0 $(< /run/canary2.pid)") 186 machine.succeed('pgrep -a -f "^@canary3$"') 187 machine.succeed('pgrep -a -f "^\\[kcanary\\]$"') 188 ''; 189 190 meta.maintainers = with pkgs.lib.maintainers; [ aszlig ]; 191}