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