summary refs log tree commit diff
path: root/nixos/tests/boot-stage1.nix
blob: 756decd2039d631dcccbdf7a2609ddb51a34e47b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import ./make-test-python.nix ({ pkgs, ... }: {
  name = "boot-stage1";

  machine = { config, pkgs, lib, ... }: {
    boot.extraModulePackages = let
      compileKernelModule = name: source: pkgs.runCommandCC name rec {
        inherit source;
        kdev = config.boot.kernelPackages.kernel.dev;
        kver = config.boot.kernelPackages.kernel.modDirVersion;
        ksrc = "${kdev}/lib/modules/${kver}/build";
        hardeningDisable = [ "pic" ];
        nativeBuildInputs = kdev.moduleBuildDependencies;
      } ''
        echo "obj-m += $name.o" > Makefile
        echo "$source" > "$name.c"
        make -C "$ksrc" M=$(pwd) modules
        install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko"
      '';

      # This spawns a kthread which just waits until it gets a signal and
      # terminates if that is the case. We want to make sure that nothing during
      # the boot process kills any kthread by accident, like what happened in
      # issue #15226.
      kcanary = compileKernelModule "kcanary" ''
        #include <linux/version.h>
        #include <linux/init.h>
        #include <linux/module.h>
        #include <linux/kernel.h>
        #include <linux/kthread.h>
        #include <linux/sched.h>
        #include <linux/signal.h>
        #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
        #include <linux/sched/signal.h>
        #endif

        MODULE_LICENSE("GPL");

        struct task_struct *canaryTask;

        static int kcanary(void *nothing)
        {
          allow_signal(SIGINT);
          allow_signal(SIGTERM);
          allow_signal(SIGKILL);
          while (!kthread_should_stop()) {
            set_current_state(TASK_INTERRUPTIBLE);
            schedule_timeout_interruptible(msecs_to_jiffies(100));
            if (signal_pending(current)) break;
          }
          return 0;
        }

        static int kcanaryInit(void)
        {
          kthread_run(&kcanary, NULL, "kcanary");
          return 0;
        }

        static void kcanaryExit(void)
        {
          kthread_stop(canaryTask);
        }

        module_init(kcanaryInit);
        module_exit(kcanaryExit);
      '';

    in lib.singleton kcanary;

    boot.initrd.kernelModules = [ "kcanary" ];

    boot.initrd.extraUtilsCommands = let
      compile = name: source: pkgs.runCommandCC name { inherit source; } ''
        mkdir -p "$out/bin"
        echo "$source" | gcc -Wall -o "$out/bin/$name" -xc -
      '';

      daemonize = name: source: compile name ''
        #include <stdio.h>
        #include <unistd.h>

        void runSource(void) {
        ${source}
        }

        int main(void) {
          if (fork() > 0) return 0;
          setsid();
          runSource();
          return 1;
        }
      '';

      mkCmdlineCanary = { name, cmdline ? "", source ? "" }: (daemonize name ''
        char *argv[] = {"${cmdline}", NULL};
        execvp("${name}-child", argv);
      '') // {
        child = compile "${name}-child" ''
          #include <stdio.h>
          #include <unistd.h>

          int main(void) {
            ${source}
            while (1) sleep(1);
            return 1;
          }
        '';
      };

      copyCanaries = with lib; concatMapStrings (canary: ''
        ${optionalString (canary ? child) ''
          copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
        ''}
        copy_bin_and_libs "${canary}/bin/${canary.name}"
      '');

    in copyCanaries [
      # Simple canary process which just sleeps forever and should be killed by
      # stage 2.
      (daemonize "canary1" "while (1) sleep(1);")

      # We want this canary process to try mimicking a kthread using a cmdline
      # with a zero length so we can make sure that the process is properly
      # killed in stage 1.
      (mkCmdlineCanary {
        name = "canary2";
        source = ''
          FILE *f;
          f = fopen("/run/canary2.pid", "w");
          fprintf(f, "%d\n", getpid());
          fclose(f);
        '';
      })

      # This canary process mimicks a storage daemon, which we do NOT want to be
      # killed before going into stage 2. For more on root storage daemons, see:
      # https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
      (mkCmdlineCanary {
        name = "canary3";
        cmdline = "@canary3";
      })
    ];

    boot.initrd.postMountCommands = ''
      canary1
      canary2
      canary3
      # Make sure the pidfile of canary 2 is created so that we still can get
      # its former pid after the killing spree starts next within stage 1.
      while [ ! -s /run/canary2.pid ]; do sleep 0.1; done
    '';
  };

  testScript = ''
    machine.wait_for_unit("multi-user.target")
    machine.succeed("test -s /run/canary2.pid")
    machine.fail("pgrep -a canary1")
    machine.fail("kill -0 $(< /run/canary2.pid)")
    machine.succeed('pgrep -a -f "^@canary3$"')
    machine.succeed('pgrep -a -f "^kcanary$"')
  '';

  meta.maintainers = with pkgs.lib.maintainers; [ aszlig ];
})