libstore: move some linux-specific child setup to platform code

best viewed with --color-moved --color-moved-ws=all

Change-Id: I3738f07fd0b39498abf253967906270dc0b215f4
This commit is contained in:
eldritch horrors
2026-01-21 15:59:30 +01:00
parent 113c6fd618
commit c39488d2a4
4 changed files with 354 additions and 339 deletions
+1 -334
View File
@@ -62,7 +62,6 @@
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
#endif
#if __APPLE__
@@ -1135,8 +1134,6 @@ void LocalDerivationGoal::runChild()
setupSyscallFilter();
bool setUser = true;
/* Make the contents of netrc and the CA certificate bundle
available to builtin:fetchurl (which may run under a
different uid and/or in a sandbox). */
@@ -1156,337 +1153,7 @@ void LocalDerivationGoal::runChild()
setupConfiguredCertificateAuthority();
}
#if __linux__
if (useChroot) {
userNamespaceSync.writeSide.reset();
if (drainFD(userNamespaceSync.readSide.get()) != "1") {
throw Error("user namespace initialisation failed");
}
userNamespaceSync.readSide.reset();
if (privateNetwork) {
/* Initialise the loopback interface. */
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (!fd) {
throw SysError("cannot open IP socket");
}
struct ifreq ifr;
strcpy(ifr.ifr_name, "lo");
ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
if (ioctl(fd.get(), SIOCSIFFLAGS, &ifr) == -1) {
throw SysError("cannot set loopback interface flags");
}
}
/* Set the hostname etc. to fixed values. */
char hostname[] = "localhost";
if (sethostname(hostname, sizeof(hostname)) == -1) {
throw SysError("cannot set host name");
}
char domainname[] = "(none)"; // kernel default
if (setdomainname(domainname, sizeof(domainname)) == -1) {
throw SysError("cannot set domain name");
}
/* Make all filesystems private. This is necessary
because subtrees may have been mounted as "shared"
(MS_SHARED). (Systemd does this, for instance.) Even
though we have a private mount namespace, mounting
filesystems on top of a shared subtree still propagates
outside of the namespace. Making a subtree private is
local to the namespace, though, so setting MS_PRIVATE
does not affect the outside world. */
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1) {
throw SysError("unable to make '/' private");
}
/* Bind-mount chroot directory to itself, to treat it as a
different filesystem from /, as needed for pivot_root. */
if (sys::mount(chrootRootDir, chrootRootDir, "", MS_BIND, 0) == -1) {
throw SysError("unable to bind mount '%1%'", chrootRootDir);
}
/* Bind-mount the sandbox's Nix store onto itself so that
we can mark it as a "shared" subtree, allowing bind
mounts made in *this* mount namespace to be propagated
into the child namespace created by the
unshare(CLONE_NEWNS) call below.
Marking chrootRootDir as MS_SHARED causes pivot_root()
to fail with EINVAL. Don't know why. */
Path chrootStoreDir = chrootRootDir + worker.store.config().storeDir;
if (sys::mount(chrootStoreDir, chrootStoreDir, "", MS_BIND, 0) == -1) {
throw SysError("unable to bind mount the Nix store", chrootStoreDir);
}
if (sys::mount("", chrootStoreDir, "", MS_SHARED, 0) == -1) {
throw SysError("unable to make '%s' shared", chrootStoreDir);
}
/* Set up a nearly empty /dev, unless the user asked to
bind-mount the host /dev. */
Strings ss;
if (pathsInChroot.find("/dev") == pathsInChroot.end()) {
createDirs(chrootRootDir + "/dev/shm");
createDirs(chrootRootDir + "/dev/pts");
ss.push_back("/dev/full");
if (worker.store.config().systemFeatures.get().count("kvm") && pathExists("/dev/kvm")) {
ss.push_back("/dev/kvm");
}
ss.push_back("/dev/null");
ss.push_back("/dev/random");
ss.push_back("/dev/tty");
ss.push_back("/dev/urandom");
ss.push_back("/dev/zero");
createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd");
createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin");
createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout");
createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr");
}
/* Fixed-output derivations typically need to access the
network, so give them access to /etc/resolv.conf and so
on. */
if (!derivationType->isSandboxed()) {
// Only use nss functions to resolve hosts and
// services. Dont use it for anything else that may
// be configured for this system. This limits the
// potential impurities introduced in fixed-outputs.
writeFile(chrootRootDir + "/etc/nsswitch.conf", "hosts: files dns\nservices: files\n");
/* N.B. it is realistic that these paths might not exist. It
happens when testing Nix building fixed-output derivations
within a pure derivation. */
for (auto & path : {"/etc/services", "/etc/hosts"}) {
if (pathAccessible(path, true)) {
// Copy the actual file, not the symlink, because we don't know where
// the symlink is pointing, and we don't want to chase down the entire
// chain.
//
// This means if your network config changes during a FOD build,
// the DNS in the sandbox will be wrong. However, this is pretty unlikely
// to actually be a problem, because FODs are generally pretty fast,
// and machines with often-changing network configurations probably
// want to run resolved or some other local resolver anyway.
//
// There's also just no simple way to do this correctly, you have to manually
// inotify watch the files for changes on the outside and update the sandbox
// while the build is running (or at least that's what Flatpak does).
//
// I also just generally feel icky about modifying sandbox state under a build,
// even though it really shouldn't be a big deal. -K900
copyFile(path, chrootRootDir + path, {.followSymlinks = true});
} else if (pathExists(path)) {
// The path exist but we were not able to access it. This is not a fatal
// error, warn about this so the user can remediate.
printTaggedWarning(
"'%1%' exists but is inaccessible, it will not be copied in the "
"sandbox",
path
);
}
}
if (pathAccessible("/etc/resolv.conf", true)) {
const auto resolvConf = rewriteResolvConf(readFile("/etc/resolv.conf"));
writeFile(chrootRootDir + "/etc/resolv.conf", resolvConf);
} else if (pathExists("/etc/resolv.conf")) {
// The path exist but we were not able to access it. This is not a fatal error,
// warn about this so the user can remediate.
printTaggedWarning(
"'/etc/resolv.conf' exists but is inaccessible, it will not be rewritten "
"inside the sandbox; DNS operations inside the sandbox may be "
"non-functional."
);
}
}
for (auto & i : ss) {
pathsInChroot.emplace(i, i);
}
/* Bind-mount all the directories from the "host"
filesystem that we want in the chroot
environment. */
for (auto & i : pathsInChroot) {
if (i.second.source == "/proc") {
continue; // backwards compatibility
}
#if HAVE_EMBEDDED_SANDBOX_SHELL
if (i.second.source == "__embedded_sandbox_shell__") {
static unsigned char sh[] = {
#include "embedded-sandbox-shell.gen.hh"
};
auto dst = chrootRootDir + i.first;
createDirs(dirOf(dst));
writeFile(dst, std::string_view((const char *) sh, sizeof(sh)));
chmodPath(dst, 0555);
} else
#endif
bindPath(i.second.source, chrootRootDir + i.first, i.second.optional);
}
/* Bind a new instance of procfs on /proc. */
createDirs(chrootRootDir + "/proc");
if (sys::mount("none", chrootRootDir + "/proc", "proc", 0, 0) == -1) {
throw SysError("mounting /proc");
}
/* Mount sysfs on /sys. */
if (buildUser && buildUser->getUIDCount() != 1) {
createDirs(chrootRootDir + "/sys");
if (sys::mount("none", chrootRootDir + "/sys", "sysfs", 0, 0) == -1) {
throw SysError("mounting /sys");
}
}
/* Mount a new tmpfs on /dev/shm to ensure that whatever
the builder puts in /dev/shm is cleaned up automatically. */
if (pathExists("/dev/shm")
&& sys::mount(
"none",
chrootRootDir + "/dev/shm",
"tmpfs",
0,
fmt("size=%s", settings.sandboxShmSize).c_str()
) == -1)
{
throw SysError("mounting /dev/shm");
}
/* Mount a new devpts on /dev/pts. Note that this
requires the kernel to be compiled with
CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
if /dev/ptx/ptmx exists). */
if (pathExists("/dev/pts/ptmx") && !pathExists(chrootRootDir + "/dev/ptmx")
&& !pathsInChroot.count("/dev/pts"))
{
if (sys::mount("none", (chrootRootDir + "/dev/pts"), "devpts", 0, "newinstance,mode=0620")
== 0)
{
createSymlink("/dev/pts/ptmx", chrootRootDir + "/dev/ptmx");
/* Make sure /dev/pts/ptmx is world-writable. With some
Linux versions, it is created with permissions 0. */
chmodPath(chrootRootDir + "/dev/pts/ptmx", 0666);
} else {
if (errno != EINVAL) {
throw SysError("mounting /dev/pts");
}
bindPath("/dev/pts", chrootRootDir + "/dev/pts");
bindPath("/dev/ptmx", chrootRootDir + "/dev/ptmx");
}
}
/* Make /etc unwritable */
if (!parsedDrv->useUidRange()) {
chmodPath(chrootRootDir + "/etc", 0555);
}
/* The comment below is now outdated. Recursive Nix has been removed.
* So there's no need to make path appear in the sandbox.
* TODO(Raito): cleanup before a merge.
*/
/* Unshare this mount namespace. This is necessary because
pivot_root() below changes the root of the mount
namespace. This means that the call to setns() in
addDependency() would hide the host's filesystem,
making it impossible to bind-mount paths from the host
Nix store into the sandbox. Therefore, we save the
pre-pivot_root namespace in
sandboxMountNamespace. Since we made /nix/store a
shared subtree above, this allows addDependency() to
make paths appear in the sandbox. */
if (unshare(CLONE_NEWNS) == -1) {
throw SysError("unsharing mount namespace");
}
/* Creating a new cgroup namespace is independent of whether we enabled the cgroup experimental
* feature. We always create a new cgroup namespace from a sandboxing perspective. */
/* Unshare the cgroup namespace. This means
/proc/self/cgroup will show the child's cgroup as '/'
rather than whatever it is in the parent. */
if (unshare(CLONE_NEWCGROUP) == -1) {
throw SysError("unsharing cgroup namespace");
}
/* Do the chroot(). */
if (sys::chdir(chrootRootDir) == -1) {
throw SysError("cannot change directory to '%1%'", chrootRootDir);
}
if (mkdir("real-root", 0) == -1) {
throw SysError("cannot create real-root directory");
}
if (pivot_root(".", "real-root") == -1) {
throw SysError("cannot pivot old root directory onto '%1%'", (chrootRootDir + "/real-root"));
}
if (chroot(".") == -1) {
throw SysError("cannot change root directory to '%1%'", chrootRootDir);
}
if (umount2("real-root", MNT_DETACH) == -1) {
throw SysError("cannot unmount real root filesystem");
}
if (rmdir("real-root") == -1) {
throw SysError("cannot remove real-root directory");
}
/* Switch to the sandbox uid/gid in the user namespace,
which corresponds to the build user or calling user in
the parent namespace. */
if (setgid(sandboxGid()) == -1) {
throw SysError("setgid failed");
}
if (setuid(sandboxUid()) == -1) {
throw SysError("setuid failed");
}
if (runPasta) {
// wait for the pasta interface to appear. pasta can't signal us when
// it's done setting up the namespace, so we have to wait for a while
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (!fd) {
throw SysError("cannot open IP socket");
}
struct ifreq ifr;
strcpy(ifr.ifr_name, LinuxLocalDerivationGoal::PASTA_NS_IFNAME);
// wait two minutes for the interface to appear. if it does not do so
// we are either grossly overloaded, or pasta startup failed somehow.
static constexpr int SINGLE_WAIT_US = 1000;
static constexpr int TOTAL_WAIT_US = 120'000'000;
for (unsigned tries = 0;; tries++) {
if (tries > TOTAL_WAIT_US / SINGLE_WAIT_US) {
throw Error(
"sandbox network setup timed out, please check daemon logs for "
"possible error output."
);
} else if (ioctl(fd.get(), SIOCGIFFLAGS, &ifr) == 0) {
if ((ifr.ifr_ifru.ifru_flags & IFF_UP) != 0) {
break;
}
} else if (errno == ENODEV) {
usleep(SINGLE_WAIT_US);
} else {
throw SysError("cannot get loopback interface flags");
}
}
}
setUser = false;
}
#endif
const bool setUser = prepareChildSetup();
if (sys::chdir(tmpDirInSandbox) == -1) {
throw SysError("changing into '%1%'", tmpDir);
+12 -5
View File
@@ -59,11 +59,6 @@ struct LocalDerivationGoal : public DerivationGoal
*/
AutoCloseFD builderOutPTY;
/**
* Pipe for synchronising updates to the builder namespaces.
*/
Pipe userNamespaceSync;
/**
* Whether we're currently doing a chroot build.
*/
@@ -327,6 +322,18 @@ protected:
*/
virtual void setupSyscallFilter() {}
/**
* Prepare the sandbox. Currently only used on linux to build the sandbox namespace,
* write configuration files inside it, and to set up networking with pasta enabled.
* Returns `true` if sandbox is running under the same credentials as the daemon, or
* `false` if this step has changed our credentials to the build user/group already.
*/
[[nodiscard]]
virtual bool prepareChildSetup()
{
return true;
}
/**
* Create a special accessor that can access paths that were built within the sandbox's
* chroot.
+334
View File
@@ -5,6 +5,7 @@
#include "lix/libutil/file-system.hh"
#include "lix/libutil/finally.hh"
#include "lix/libstore/gc-store.hh"
#include "lix/libutil/mount.hh"
#include "lix/libutil/processes.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/signals.hh"
@@ -16,8 +17,13 @@
#include <cstdlib>
#include <grp.h>
#include <memory>
#include <net/if.h>
#include <netinet/in.h>
#include <regex>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#if __linux__
#include <linux/capability.h>
@@ -898,6 +904,334 @@ std::string LinuxLocalDerivationGoal::rewriteResolvConf(std::string fromHost)
return std::regex_replace(fromHost, lineRegex, "") + nsInSandbox;
}
bool LinuxLocalDerivationGoal::prepareChildSetup()
{
if (!useChroot) {
return true;
}
userNamespaceSync.writeSide.reset();
if (drainFD(userNamespaceSync.readSide.get()) != "1") {
throw Error("user namespace initialisation failed");
}
userNamespaceSync.readSide.reset();
if (privateNetwork) {
/* Initialise the loopback interface. */
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (!fd) {
throw SysError("cannot open IP socket");
}
struct ifreq ifr;
strcpy(ifr.ifr_name, "lo");
ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
if (ioctl(fd.get(), SIOCSIFFLAGS, &ifr) == -1) {
throw SysError("cannot set loopback interface flags");
}
}
/* Set the hostname etc. to fixed values. */
char hostname[] = "localhost";
if (sethostname(hostname, sizeof(hostname)) == -1) {
throw SysError("cannot set host name");
}
char domainname[] = "(none)"; // kernel default
if (setdomainname(domainname, sizeof(domainname)) == -1) {
throw SysError("cannot set domain name");
}
/* Make all filesystems private. This is necessary
because subtrees may have been mounted as "shared"
(MS_SHARED). (Systemd does this, for instance.) Even
though we have a private mount namespace, mounting
filesystems on top of a shared subtree still propagates
outside of the namespace. Making a subtree private is
local to the namespace, though, so setting MS_PRIVATE
does not affect the outside world. */
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1) {
throw SysError("unable to make '/' private");
}
/* Bind-mount chroot directory to itself, to treat it as a
different filesystem from /, as needed for pivot_root. */
if (sys::mount(chrootRootDir, chrootRootDir, "", MS_BIND, 0) == -1) {
throw SysError("unable to bind mount '%1%'", chrootRootDir);
}
/* Bind-mount the sandbox's Nix store onto itself so that
we can mark it as a "shared" subtree, allowing bind
mounts made in *this* mount namespace to be propagated
into the child namespace created by the
unshare(CLONE_NEWNS) call below.
Marking chrootRootDir as MS_SHARED causes pivot_root()
to fail with EINVAL. Don't know why. */
Path chrootStoreDir = chrootRootDir + worker.store.config().storeDir;
if (sys::mount(chrootStoreDir, chrootStoreDir, "", MS_BIND, 0) == -1) {
throw SysError("unable to bind mount the Nix store", chrootStoreDir);
}
if (sys::mount("", chrootStoreDir, "", MS_SHARED, 0) == -1) {
throw SysError("unable to make '%s' shared", chrootStoreDir);
}
/* Set up a nearly empty /dev, unless the user asked to
bind-mount the host /dev. */
Strings ss;
if (pathsInChroot.find("/dev") == pathsInChroot.end()) {
createDirs(chrootRootDir + "/dev/shm");
createDirs(chrootRootDir + "/dev/pts");
ss.push_back("/dev/full");
if (worker.store.config().systemFeatures.get().count("kvm") && pathExists("/dev/kvm")) {
ss.push_back("/dev/kvm");
}
ss.push_back("/dev/null");
ss.push_back("/dev/random");
ss.push_back("/dev/tty");
ss.push_back("/dev/urandom");
ss.push_back("/dev/zero");
createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd");
createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin");
createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout");
createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr");
}
/* Fixed-output derivations typically need to access the
network, so give them access to /etc/resolv.conf and so
on. */
if (!derivationType->isSandboxed()) {
// Only use nss functions to resolve hosts and
// services. Dont use it for anything else that may
// be configured for this system. This limits the
// potential impurities introduced in fixed-outputs.
writeFile(chrootRootDir + "/etc/nsswitch.conf", "hosts: files dns\nservices: files\n");
/* N.B. it is realistic that these paths might not exist. It
happens when testing Nix building fixed-output derivations
within a pure derivation. */
for (auto & path : {"/etc/services", "/etc/hosts"}) {
if (pathAccessible(path, true)) {
// Copy the actual file, not the symlink, because we don't know where
// the symlink is pointing, and we don't want to chase down the entire
// chain.
//
// This means if your network config changes during a FOD build,
// the DNS in the sandbox will be wrong. However, this is pretty unlikely
// to actually be a problem, because FODs are generally pretty fast,
// and machines with often-changing network configurations probably
// want to run resolved or some other local resolver anyway.
//
// There's also just no simple way to do this correctly, you have to manually
// inotify watch the files for changes on the outside and update the sandbox
// while the build is running (or at least that's what Flatpak does).
//
// I also just generally feel icky about modifying sandbox state under a build,
// even though it really shouldn't be a big deal. -K900
copyFile(path, chrootRootDir + path, {.followSymlinks = true});
} else if (pathExists(path)) {
// The path exist but we were not able to access it. This is not a fatal
// error, warn about this so the user can remediate.
printTaggedWarning(
"'%1%' exists but is inaccessible, it will not be copied in the "
"sandbox",
path
);
}
}
if (pathAccessible("/etc/resolv.conf", true)) {
const auto resolvConf = rewriteResolvConf(readFile("/etc/resolv.conf"));
writeFile(chrootRootDir + "/etc/resolv.conf", resolvConf);
} else if (pathExists("/etc/resolv.conf")) {
// The path exist but we were not able to access it. This is not a fatal error,
// warn about this so the user can remediate.
printTaggedWarning(
"'/etc/resolv.conf' exists but is inaccessible, it will not be rewritten "
"inside the sandbox; DNS operations inside the sandbox may be "
"non-functional."
);
}
}
for (auto & i : ss) {
pathsInChroot.emplace(i, i);
}
/* Bind-mount all the directories from the "host"
filesystem that we want in the chroot
environment. */
for (auto & i : pathsInChroot) {
if (i.second.source == "/proc") {
continue; // backwards compatibility
}
#if HAVE_EMBEDDED_SANDBOX_SHELL
if (i.second.source == "__embedded_sandbox_shell__") {
static unsigned char sh[] = {
#include "embedded-sandbox-shell.gen.hh"
};
auto dst = chrootRootDir + i.first;
createDirs(dirOf(dst));
writeFile(dst, std::string_view((const char *) sh, sizeof(sh)));
chmodPath(dst, 0555);
} else
#endif
bindPath(i.second.source, chrootRootDir + i.first, i.second.optional);
}
/* Bind a new instance of procfs on /proc. */
createDirs(chrootRootDir + "/proc");
if (sys::mount("none", chrootRootDir + "/proc", "proc", 0, 0) == -1) {
throw SysError("mounting /proc");
}
/* Mount sysfs on /sys. */
if (buildUser && buildUser->getUIDCount() != 1) {
createDirs(chrootRootDir + "/sys");
if (sys::mount("none", chrootRootDir + "/sys", "sysfs", 0, 0) == -1) {
throw SysError("mounting /sys");
}
}
/* Mount a new tmpfs on /dev/shm to ensure that whatever
the builder puts in /dev/shm is cleaned up automatically. */
if (pathExists("/dev/shm")
&& sys::mount(
"none", chrootRootDir + "/dev/shm", "tmpfs", 0, fmt("size=%s", settings.sandboxShmSize).c_str()
) == -1)
{
throw SysError("mounting /dev/shm");
}
/* Mount a new devpts on /dev/pts. Note that this
requires the kernel to be compiled with
CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
if /dev/ptx/ptmx exists). */
if (pathExists("/dev/pts/ptmx") && !pathExists(chrootRootDir + "/dev/ptmx")
&& !pathsInChroot.count("/dev/pts"))
{
if (sys::mount("none", (chrootRootDir + "/dev/pts"), "devpts", 0, "newinstance,mode=0620") == 0) {
createSymlink("/dev/pts/ptmx", chrootRootDir + "/dev/ptmx");
/* Make sure /dev/pts/ptmx is world-writable. With some
Linux versions, it is created with permissions 0. */
chmodPath(chrootRootDir + "/dev/pts/ptmx", 0666);
} else {
if (errno != EINVAL) {
throw SysError("mounting /dev/pts");
}
bindPath("/dev/pts", chrootRootDir + "/dev/pts");
bindPath("/dev/ptmx", chrootRootDir + "/dev/ptmx");
}
}
/* Make /etc unwritable */
if (!parsedDrv->useUidRange()) {
chmodPath(chrootRootDir + "/etc", 0555);
}
/* The comment below is now outdated. Recursive Nix has been removed.
* So there's no need to make path appear in the sandbox.
* TODO(Raito): cleanup before a merge.
*/
/* Unshare this mount namespace. This is necessary because
pivot_root() below changes the root of the mount
namespace. This means that the call to setns() in
addDependency() would hide the host's filesystem,
making it impossible to bind-mount paths from the host
Nix store into the sandbox. Therefore, we save the
pre-pivot_root namespace in
sandboxMountNamespace. Since we made /nix/store a
shared subtree above, this allows addDependency() to
make paths appear in the sandbox. */
if (unshare(CLONE_NEWNS) == -1) {
throw SysError("unsharing mount namespace");
}
/* Creating a new cgroup namespace is independent of whether we enabled the cgroup experimental feature.
* We always create a new cgroup namespace from a sandboxing perspective. */
/* Unshare the cgroup namespace. This means
/proc/self/cgroup will show the child's cgroup as '/'
rather than whatever it is in the parent. */
if (unshare(CLONE_NEWCGROUP) == -1) {
throw SysError("unsharing cgroup namespace");
}
/* Do the chroot(). */
if (sys::chdir(chrootRootDir) == -1) {
throw SysError("cannot change directory to '%1%'", chrootRootDir);
}
if (mkdir("real-root", 0) == -1) {
throw SysError("cannot create real-root directory");
}
if (syscall(SYS_pivot_root, ".", "real-root") == -1) {
throw SysError("cannot pivot old root directory onto '%1%'", (chrootRootDir + "/real-root"));
}
if (chroot(".") == -1) {
throw SysError("cannot change root directory to '%1%'", chrootRootDir);
}
if (umount2("real-root", MNT_DETACH) == -1) {
throw SysError("cannot unmount real root filesystem");
}
if (rmdir("real-root") == -1) {
throw SysError("cannot remove real-root directory");
}
/* Switch to the sandbox uid/gid in the user namespace,
which corresponds to the build user or calling user in
the parent namespace. */
if (setgid(sandboxGid()) == -1) {
throw SysError("setgid failed");
}
if (setuid(sandboxUid()) == -1) {
throw SysError("setuid failed");
}
if (runPasta) {
// wait for the pasta interface to appear. pasta can't signal us when
// it's done setting up the namespace, so we have to wait for a while
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (!fd) {
throw SysError("cannot open IP socket");
}
struct ifreq ifr;
strcpy(ifr.ifr_name, LinuxLocalDerivationGoal::PASTA_NS_IFNAME);
// wait two minutes for the interface to appear. if it does not do so
// we are either grossly overloaded, or pasta startup failed somehow.
static constexpr int SINGLE_WAIT_US = 1000;
static constexpr int TOTAL_WAIT_US = 120'000'000;
for (unsigned tries = 0;; tries++) {
if (tries > TOTAL_WAIT_US / SINGLE_WAIT_US) {
throw Error(
"sandbox network setup timed out, please check daemon logs for "
"possible error output."
);
} else if (ioctl(fd.get(), SIOCGIFFLAGS, &ifr) == 0) {
if ((ifr.ifr_ifru.ifru_flags & IFF_UP) != 0) {
break;
}
} else if (errno == ENODEV) {
usleep(SINGLE_WAIT_US);
} else {
throw SysError("cannot get loopback interface flags");
}
}
}
return false;
}
Pid LinuxLocalDerivationGoal::startChild(AutoCloseFD logPTY)
{
#if HAVE_SECCOMP
+7
View File
@@ -91,7 +91,14 @@ private:
return true;
}
bool prepareChildSetup() override;
std::string rewriteResolvConf(std::string fromHost) override;
/**
* Pipe for synchronising updates to the builder namespaces.
*/
Pipe userNamespaceSync;
};
}