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}