In bfe9c928c1
the default kernel has been
updated to version 4.14 and the declarations for allow_signal() and
signal_pending() are no longer exposed via kthread.h, so let's actually
use the right header files.
I've added a condition for kernel 4.10 and upwards to include the
linux/sched/signal.h header file, because that got introduced in version
4.10. Even if the declaration would still reside in kthread.h (I haven't
checked) for version 4.10 it won't hurt and the compilation will still
succeed.
Tested against kernel 4.9 and 4.14 and the build now succeeds.
Signed-off-by: aszlig <aszlig@nix.build>
162 lines
4.8 KiB
Nix
162 lines
4.8 KiB
Nix
import ./make-test.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" ];
|
|
} ''
|
|
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
|
|
|
|
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->waitForUnit("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.stdenv.lib.maintainers; [ aszlig ];
|
|
})
|