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)