nix-super/tests/nixos/user-sandboxing/default.nix
Théophane Hufschmitt 1d3696f0fb Run the builds in a daemon-controled directory
Instead of running the builds under
`$TMPDIR/{unique-build-directory-owned-by-the-build-user}`, run them
under `$TMPDIR/{unique-build-directory-owned-by-the-daemon}/{subdir-owned-by-the-build-user}`
where the build directory is only readable and traversable by the daemon user.

This achieves two things:

1. It prevents builders from making their build directory world-readable
   (or even writeable), which would allow the outside world to interact
   with them.
2. It prevents external processes running as the build user (either
   because that somehow leaked, maybe as a consequence of 1., or because
   `build-users` isn't in use) from gaining access to the build
   directory.
2024-06-21 17:06:19 +02:00

129 lines
4.1 KiB
Nix

{ config, ... }:
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
attacker = pkgs.runCommandWith {
name = "attacker";
stdenv = pkgs.pkgsStatic.stdenv;
} ''
$CC -static -o $out ${./attacker.c}
'';
try-open-build-dir = pkgs.writeScript "try-open-build-dir" ''
export PATH=${pkgs.coreutils}/bin:$PATH
set -x
chmod 700 .
# Shouldn't be able to open the root build directory
(! chmod 700 ..)
touch foo
# Synchronisation point: create a world-writable fifo and wait for someone
# to write into it
mkfifo syncPoint
chmod 777 syncPoint
cat syncPoint
touch $out
set +x
'';
create-hello-world = pkgs.writeScript "create-hello-world" ''
export PATH=${pkgs.coreutils}/bin:$PATH
set -x
echo "hello, world" > result
# Synchronisation point: create a world-writable fifo and wait for someone
# to write into it
mkfifo syncPoint
chmod 777 syncPoint
cat syncPoint
cp result $out
set +x
'';
in
{
name = "sandbox-setuid-leak";
nodes.machine =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
nix.nrBuildUsers = 1;
virtualisation.additionalPaths = [ pkgs.busybox-sandbox-shell attacker try-open-build-dir create-hello-world pkgs.socat ];
boot.kernelPackages = pkgs.linuxPackages_latest;
users.users.alice = {
isNormalUser = true;
};
};
testScript = { nodes }: ''
start_all()
with subtest("A builder can't give access to its build directory"):
# Make sure that a builder can't change the permissions on its build
# directory to the point of opening it up to external users
# A derivation whose builder tries to make its build directory as open
# as possible and wait for someone to hijack it
machine.succeed(r"""
nix-build -v -E '
builtins.derivation {
name = "open-build-dir";
system = builtins.currentSystem;
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ (builtins.storePath "${try-open-build-dir}") ];
}' >&2 &
""".strip())
# Wait for the build to be ready
# This is OK because it runs as root, so we can access everything
machine.wait_for_file("/tmp/nix-build-open-build-dir.drv-0/build/syncPoint")
# But Alice shouldn't be able to access the build directory
machine.fail("su alice -c 'ls /tmp/nix-build-open-build-dir.drv-0/build'")
machine.fail("su alice -c 'touch /tmp/nix-build-open-build-dir.drv-0/build/bar'")
machine.fail("su alice -c 'cat /tmp/nix-build-open-build-dir.drv-0/build/foo'")
# Tell the user to finish the build
machine.succeed("echo foo > /tmp/nix-build-open-build-dir.drv-0/build/syncPoint")
with subtest("Being able to execute stuff as the build user doesn't give access to the build dir"):
machine.succeed(r"""
nix-build -E '
builtins.derivation {
name = "innocent";
system = builtins.currentSystem;
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ (builtins.storePath "${create-hello-world}") ];
}' >&2 &
""".strip())
machine.wait_for_file("/tmp/nix-build-innocent.drv-0/build/syncPoint")
# The build ran as `nixbld1` (which is the only build user on the
# machine), but a process running as `nixbld1` outside the sandbox
# shouldn't be able to touch the build directory regardless
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'ls /tmp/nix-build-innocent.drv-0/build'")
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'echo pwned > /tmp/nix-build-innocent.drv-0/build/result'")
# Finish the build
machine.succeed("echo foo > /tmp/nix-build-innocent.drv-0/build/syncPoint")
# Check that the build was not affected
machine.succeed(r"""
cat ./result
test "$(cat ./result)" = "hello, world"
""".strip())
'';
}