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