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