nixpkgs/nixos/tests/hardened.nix
Emily ad9bfe2254 nixos/hardened: enable user namespaces for root
linux-hardened sets kernel.unprivileged_userns_clone=0 by default; see
anthraxx/linux-hardened@104f44058f.

This allows the Nix sandbox to function while reducing the attack
surface posed by user namespaces, which allow unprivileged code to
exercise lots of root-only code paths and have lead to privilege
escalation vulnerabilities in the past.

We can safely leave user namespaces on for privileged users, as root
already has root privileges, but if you're not running builds on your
machine and really want to minimize the kernel attack surface then you
can set security.allowUserNamespaces to false.

Note that Chrome's sandbox requires either unprivileged CLONE_NEWUSER or
setuid, and Firefox's silently reduces the security level if it isn't
allowed (see about:support), so desktop users may want to set:

    boot.kernel.sysctl."kernel.unprivileged_userns_clone" = true;
2020-04-17 16:13:39 +01:00

127 lines
4.1 KiB
Nix

import ./make-test.nix ({ pkgs, ...} : {
name = "hardened";
meta = with pkgs.stdenv.lib.maintainers; {
maintainers = [ joachifm ];
};
machine =
{ lib, pkgs, config, ... }:
with lib;
{ users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
users.users.sybil = { isNormalUser = true; group = "wheel"; };
imports = [ ../modules/profiles/hardened.nix ];
environment.memoryAllocator.provider = "graphene-hardened";
nix.useSandbox = false;
virtualisation.emptyDiskImages = [ 4096 ];
boot.initrd.postDeviceCommands = ''
${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
'';
fileSystems = lib.mkVMOverride {
"/efi" = {
device = "/dev/disk/by-label/EFISYS";
fsType = "vfat";
options = [ "noauto" ];
};
};
boot.extraModulePackages = [ config.boot.kernelPackages.wireguard ];
boot.kernelModules = [ "wireguard" ];
};
testScript =
let
hardened-malloc-tests = pkgs.stdenv.mkDerivation {
name = "hardened-malloc-tests-${pkgs.graphene-hardened-malloc.version}";
src = pkgs.graphene-hardened-malloc.src;
buildPhase = ''
cd test/simple-memory-corruption
make -j4
'';
installPhase = ''
find . -type f -executable -exec install -Dt $out/bin '{}' +
'';
};
in
''
$machine->waitForUnit("multi-user.target");
subtest "apparmor-loaded", sub {
$machine->succeed("systemctl status apparmor.service");
};
# AppArmor securityfs
subtest "apparmor-securityfs", sub {
$machine->succeed("mountpoint -q /sys/kernel/security");
$machine->succeed("cat /sys/kernel/security/apparmor/profiles");
};
# Test loading out-of-tree modules
subtest "extra-module-packages", sub {
$machine->succeed("grep -Fq wireguard /proc/modules");
};
# Test hidepid
subtest "hidepid", sub {
$machine->succeed("grep -Fq hidepid=2 /proc/mounts");
# cannot use pgrep -u here, it segfaults when access to process info is denied
$machine->succeed("[ `su - sybil -c 'ps --no-headers --user root | wc -l'` = 0 ]");
$machine->succeed("[ `su - alice -c 'ps --no-headers --user root | wc -l'` != 0 ]");
};
# Test kernel module hardening
subtest "lock-modules", sub {
# note: this better a be module we normally wouldn't load ...
$machine->fail("modprobe dccp");
};
# Test userns
subtest "userns", sub {
$machine->succeed("unshare --user true");
$machine->fail("su -l alice -c 'unshare --user true'");
};
# Test dmesg restriction
subtest "dmesg", sub {
$machine->fail("su -l alice -c dmesg");
};
# Test access to kcore
subtest "kcore", sub {
$machine->fail("cat /proc/kcore");
};
# Test deferred mount
subtest "mount", sub {
$machine->fail("mountpoint -q /efi"); # was deferred
$machine->execute("mkdir -p /efi");
$machine->succeed("mount /dev/disk/by-label/EFISYS /efi");
$machine->succeed("mountpoint -q /efi"); # now mounted
};
# Test Nix dæmon usage
subtest "nix-daemon", sub {
$machine->fail("su -l nobody -s /bin/sh -c 'nix ping-store'");
$machine->succeed("su -l alice -c 'nix ping-store'") =~ "OK";
};
# Test kernel image protection
subtest "kernelimage", sub {
$machine->fail("systemctl hibernate");
$machine->fail("systemctl kexec");
};
# Test hardened memory allocator
sub runMallocTestProg {
my ($progName, $errorText) = @_;
my $text = "fatal allocator error: " . $errorText;
$machine->fail("${hardened-malloc-tests}/bin/" . $progName) =~ $text;
};
subtest "hardenedmalloc", sub {
runMallocTestProg("double_free_large", "invalid free");
runMallocTestProg("unaligned_free_small", "invalid unaligned free");
runMallocTestProg("write_after_free_small", "detected write after free");
};
'';
})