Skip to content

Commit

Permalink
Run the builds in a daemon-controled directory
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
thufschmitt authored and edolstra committed Jun 21, 2024
1 parent 717f3ee commit 1d3696f
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 12 deletions.
5 changes: 3 additions & 2 deletions src/libstore/unix/build/local-derivation-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -503,8 +503,9 @@ void LocalDerivationGoal::startBuilder()

/* Create a temporary directory where the build will take
place. */
tmpDir = createTempDir(settings.buildDir.get().value_or(""), "nix-build-" + std::string(drvPath.name()), false, false, 0700);

auto parentTmpDir = createTempDir(settings.buildDir.get().value_or(""), "nix-build-" + std::string(drvPath.name()), false, false, 0700);
tmpDir = parentTmpDir + "/build";
createDir(tmpDir, 0700);
chownToBuilder(tmpDir);

for (auto & [outputName, status] : initialOutputs) {
Expand Down
5 changes: 5 additions & 0 deletions src/libutil/file-system.cc
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,11 @@ void deletePath(const fs::path & path)
deletePath(path, dummy);
}

void createDir(const Path &path, mode_t mode)
{
if (mkdir(path.c_str(), mode) == -1)
throw SysError("creating directory '%1%'", path);
}

Paths createDirs(const Path & path)
{
Expand Down
5 changes: 5 additions & 0 deletions src/libutil/file-system.hh
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ inline Paths createDirs(PathView path)
return createDirs(Path(path));
}

/**
* Create a single directory.
*/
void createDir(const Path & path, mode_t mode = 0755);

/**
* Create a symlink.
*/
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ test_custom_build_dir() {
--no-out-link --keep-failed --option build-dir "$TEST_ROOT/custom-build-dir" 2> $TEST_ROOT/log || status=$?
[ "$status" = "100" ]
[[ 1 == "$(count "$customBuildDir/nix-build-"*)" ]]
local buildDir="$customBuildDir/nix-build-"*
local buildDir="$customBuildDir/nix-build-"*"/build"
grep $checkBuildId $buildDir/checkBuildId
}
test_custom_build_dir
Expand Down
20 changes: 11 additions & 9 deletions tests/nixos/user-sandboxing/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ let
set -x
chmod 700 .
# Shouldn't be able to open the root build directory
(! chmod 700 ..)
touch foo
Expand Down Expand Up @@ -85,15 +87,15 @@ in
# 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/syncPoint")
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'")
machine.fail("su alice -c 'touch /tmp/nix-build-open-build-dir.drv-0/bar'")
machine.fail("su alice -c 'cat /tmp/nix-build-open-build-dir.drv-0/foo'")
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/syncPoint")
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"""
Expand All @@ -105,16 +107,16 @@ in
args = [ (builtins.storePath "${create-hello-world}") ];
}' >&2 &
""".strip())
machine.wait_for_file("/tmp/nix-build-innocent.drv-0/syncPoint")
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'")
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'echo pwned > /tmp/nix-build-innocent.drv-0/result'")
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/syncPoint")
machine.succeed("echo foo > /tmp/nix-build-innocent.drv-0/build/syncPoint")
# Check that the build was not affected
machine.succeed(r"""
Expand Down

0 comments on commit 1d3696f

Please sign in to comment.