diff options
Diffstat (limited to 'nix/libstore/build.cc')
-rw-r--r-- | nix/libstore/build.cc | 551 |
1 files changed, 532 insertions, 19 deletions
diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc index 51f5aed106..1a688f3b56 100644 --- a/nix/libstore/build.cc +++ b/nix/libstore/build.cc @@ -14,6 +14,7 @@ #include <map> #include <sstream> #include <algorithm> +#include <regex> #include <limits.h> #include <time.h> @@ -73,10 +74,18 @@ #endif #if CHROOT_ENABLED -#include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> -#include <netinet/ip.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <net/route.h> +#include <arpa/inet.h> +#if __linux__ +#include <linux/if_tun.h> +/* This header isn't documented in 'man netdevice', but there doesn't seem to + be any other way to get 'struct in6_ifreq'... */ +#include <linux/ipv6.h> +#endif #endif #if __linux__ @@ -661,6 +670,10 @@ private: /* Whether this is a fixed-output derivation. */ bool fixedOutput; + /* PID of the 'slirp4netns' process in case of a fixed-output + derivation. */ + Pid slirp; + typedef void (DerivationGoal::*GoalState)(); GoalState state; @@ -831,6 +844,10 @@ void DerivationGoal::killChild() worker.childTerminated(hook->pid); } hook.reset(); + + if (slirp != -1) + /* Terminate the 'slirp4netns' process. */ + slirp.kill(); } @@ -1611,7 +1628,9 @@ static const gid_t guestGID = 30000; /* Initialize the user namespace of CHILD. */ static void initializeUserNamespace(pid_t child, uid_t hostUID = getuid(), - gid_t hostGID = getgid()) + gid_t hostGID = getgid(), + uid_t guestUID = guestUID, + gid_t guestGID = guestGID) { writeFile("/proc/" + std::to_string(child) + "/uid_map", (format("%d %d 1") % guestUID % hostUID).str()); @@ -1624,12 +1643,427 @@ static void initializeUserNamespace(pid_t child, #if CHROOT_ENABLED -void clearRootWritePermsAction(SpawnContext & sctx) +/* Creating TAP device for the fixed-output derivation build environment, + based on how slirp4netns does it. send_fd_socket is a unix-domain socket + that a file descriptor for the TAP device will be sent on along with a + single null byte of regular data. */ +static void setupTap(int send_fd_socket, bool ipv6Enabled) +{ + AutoCloseFD tapfd; + struct ifreq ifr; + struct in6_ifreq ifr6; + char tapname[] = "tap0"; + int ifindex; + + tapfd = open("/dev/net/tun", O_RDWR); + if(tapfd < 0) + throw SysError("opening `/dev/net/tun'"); + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strncpy(ifr.ifr_name, tapname, sizeof(ifr.ifr_name) - 1); + if(ioctl(tapfd, TUNSETIFF, (void*)&ifr) < 0) + throw SysError("TUNSETIFF"); + + /* DAD is "duplicate address detection". By default the kernel will put + any ipv6 addresses that we add into the "tentative" state, and only + after several seconds have been spent trying to chat with network + neighbors about whether anyone is already using the address will it + allow it to be bound to, whether for listening or for connecting. + + This causes tcp connections initiated before then to bind to ::1, which + obviously is not a valid address for communication between hosts. Even + after the real addresses leave the "tentative" state, the source address + used for the already-started connection attempt does not change. + + In our situation we know for a fact nobody else is using the addresses + we give, so there's no point in waiting the extra several seconds to + perform DAD; disable it entirely instead. + + Note: this needs to use conf/tap0/ instead of conf/all/ */ + writeFile("/proc/sys/net/ipv6/conf/tap0/accept_dad", "0"); + + /* By default tap0 will solicit and receive router advertisements, and + * thereby obtain an ipv6 address from slirp4netns. But if the host + * doesn't have a working ipv6 connection, this could mess things up for + * guest programs (and really the guest network stack itself), as they + * have no way of knowing that, and will therefore likely try connecting + * to addresses found in AAAA records, which will fail. To prevent this, + * ignore router advertisements. */ + writeFile("/proc/sys/net/ipv6/conf/tap0/accept_ra", "0"); + + /* Now set up: + 1. tap0's active flags (so it's running, up, etc) + 2. tap0's MTU + 3. tap0's ip address + 4. tap0's network mask + 5. A default route to tap0 */ + AutoCloseFD sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + if(sockfd < 0) + throw SysError("creating socket"); + + AutoCloseFD sockfd6 = socket(AF_INET6, SOCK_DGRAM, 0); + + if(sockfd6 < 0) + throw SysError("creating ipv6 socket"); + + if(ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) + throw SysError("getting tap0 ifindex"); + + ifindex = ifr.ifr_ifindex; + + ifr.ifr_flags = IFF_UP | IFF_RUNNING; + if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) + throw SysError("setting flags for tap0"); + + /* slirp4netns default */ + ifr.ifr_mtu = 1500; + if(ioctl(sockfd, SIOCSIFMTU, &ifr) < 0) + throw SysError("setting MTU for tap0"); + + /* default network CIDR: 10.0.2.0/24, fd00::/64 */ + /* default recommended_vguest: 10.0.2.100, fd00::??? (we choose to use + fd00::80 and fe80::80) */ + /* default gateway: 10.0.2.2, fd00::2 */ + struct sockaddr_in *sai = (struct sockaddr_in *) &ifr.ifr_addr; + sai->sin_family = AF_INET; + sai->sin_port = htonl(0); + if(inet_pton(AF_INET, "10.0.2.100", &sai->sin_addr) != 1) + throw Error("inet_pton failed"); + + if(ioctl(sockfd, SIOCSIFADDR, &ifr) < 0) + throw SysError("setting tap0 address"); + + if(ipv6Enabled) { + if(inet_pton(AF_INET6, "fd00::80", &ifr6.ifr6_addr) != 1) + throw Error("inet_pton failed"); + ifr6.ifr6_prefixlen = 64; + ifr6.ifr6_ifindex = ifindex; + + if(ioctl(sockfd6, SIOCSIFADDR, &ifr6) < 0) + throw SysError("setting tap0 ipv6 address"); + } + + /* Always set up the link-local address so that communication with the + * host loopback over ipv6 can be possible. */ + if(inet_pton(AF_INET6, "fe80::80", &ifr6.ifr6_addr) != 1) + throw Error("inet_pton failed"); + ifr6.ifr6_prefixlen = 64; + ifr6.ifr6_ifindex = ifindex; + + if(ioctl(sockfd6, SIOCSIFADDR, &ifr6) < 0) + throw SysError("setting tap0 link-local ipv6 address"); + + if(inet_pton(AF_INET, "255.255.255.0", &sai->sin_addr) != 1) + throw Error("inet_pton failed"); + + if(ioctl(sockfd, SIOCSIFNETMASK, &ifr) < 0) + throw SysError("setting tap0 network mask"); + + /* To my knowledge there is no official documentation of SIOCADDRT and + struct rtentry for Linux aside from the Linux kernel source code as of + the year 2025. This is therefore fully cargo-culted from + slirp4netns. */ + + struct rtentry route; + memset(&route, 0, sizeof(route)); + sai = (struct sockaddr_in *)&route.rt_gateway; + sai->sin_family = AF_INET; + if(inet_pton(AF_INET, "10.0.2.2", &sai->sin_addr) != 1) + throw Error("inet_pton failed"); + sai = (struct sockaddr_in *)&route.rt_dst; + sai->sin_family = AF_INET; + sai->sin_addr.s_addr = htonl(INADDR_ANY); + sai = (struct sockaddr_in *)&route.rt_genmask; + sai->sin_family = AF_INET; + sai->sin_addr.s_addr = htonl(INADDR_ANY); + + route.rt_flags = RTF_UP | RTF_GATEWAY; + route.rt_metric = 0; + route.rt_dev = tapname; + + if(ioctl(sockfd, SIOCADDRT, &route) < 0) + throw SysError("setting tap0 as default route"); + + struct in6_rtmsg route6; + memset(&route6, 0, sizeof(route6)); + if(inet_pton(AF_INET6, "fd00::2", &route6.rtmsg_gateway) != 1) + throw Error("inet_pton failed"); + + if(ipv6Enabled) { + /* Set up a default gateway via slirp4netns */ + route6.rtmsg_dst = IN6ADDR_ANY_INIT; + route6.rtmsg_dst_len = 0; + route6.rtmsg_flags = RTF_UP | RTF_GATEWAY; + } else { + /* Set up a route to slirp4netns, but only for talking to the host + * loopback */ + if(inet_pton(AF_INET6, "fd00::2", &route6.rtmsg_dst) != 1) + throw Error("inet_pton failed"); + route6.rtmsg_dst_len = 128; + route6.rtmsg_flags = RTF_UP; + } + route6.rtmsg_src = IN6ADDR_ANY_INIT; + route6.rtmsg_src_len = 0; + route6.rtmsg_ifindex = ifindex; + route6.rtmsg_metric = 1; + + if(ioctl(sockfd6, SIOCADDRT, &route6) < 0) + throw SysError("setting tap0 as default ipv6 route"); + + sendFD(send_fd_socket, tapfd); +} + +struct ChrootBuildSpawnContext : CloneSpawnContext { + bool ipv6Enabled = false; +}; + +static void setupTapAction(SpawnContext & sctx) +{ + ChrootBuildSpawnContext & ctx = (ChrootBuildSpawnContext &) sctx; + setupTap(ctx.setupFD, ctx.ipv6Enabled); +} + + +static void waitForSlirpReadyAction(SpawnContext & sctx) +{ + CloneSpawnContext & ctx = (CloneSpawnContext &) sctx; + /* Wait for the parent process to get slirp4netns running */ + waitForMessage(ctx.setupFD, "1"); +} + + +static void enableRouteLocalnetAction(SpawnContext & sctx) +{ + /* Don't treat as invalid packets received with loopback source addresses. + This allows for packets to be received from the host loopback using its + real address, so for example proxy settings referencing 127.0.0.1 will + work both for builtin and regular fixed-output derivations. */ + + /* Note: this file is treated relative to the network namespace of the + process that opens it. We aren't modifying any host settings here, + provided we are in a new network namespace. */ + Path route_localnet4 = "/proc/sys/net/ipv4/conf/all/route_localnet"; + /* XXX: no such toggle exists for ipv6 */ + if(pathExists(route_localnet4)) + writeFile(route_localnet4, "1"); +} + + +static void prepareSlirpChrootAction(SpawnContext & sctx) +{ + CloneSpawnContext & ctx = (CloneSpawnContext &) sctx; + auto mounts = tokenizeString<Strings>(readFile("/proc/self/mountinfo", true), "\n"); + set<string> seen; + for(auto & i : mounts) { + auto fields = tokenizeString<vector<string> >(i, " "); + auto fs = decodeOctalEscaped(fields.at(4)); + if(seen.find(fs) == seen.end()) { + /* slirp4netns only does a single umount of the old root ("/old") + after pivot_root. Because of this, if there are multiple + mounts stacked on top of each other, only the topmost one (the + read-only bind mount) will be unmounted, leaving the real root + in place and causing the subsequent rmdir to fail. The best we + can do is to make everything immediately underneath "/" be + read-only, which we do after mounting every non-/ filesystem + read-only. */ + if(fs == "/") continue; + /* Don't mount /etc or any of its subdirectories, we're only interested + in mounting network stuff from it */ + if(fs.compare(0, 4, "/etc") == 0) continue; + /* We want /run to be empty */ + if(fs.compare(0, 4, "/run") == 0) continue; + /* Don't mount anything from under our chroot directory */ + if(fs.compare(0, ctx.chrootRootDir.length(), ctx.chrootRootDir) == 0) continue; + struct stat st; + if(stat(fs.c_str(), &st) != 0) { + if(errno == EACCES) continue; /* Not accessible anyway */ + else throw SysError(format("stat of `%1%'") % fs); + } + + ctx.readOnlyFilesInChroot.insert(fs); + ctx.filesInChroot[fs] = fs; + seen.insert(fs); + } + } + + /* Limit /etc to containing just /etc/resolv.conf and /etc/hosts, and + read-only at that */ + Strings etcFiles = { "/etc/resolv.conf", "/etc/hosts" }; + for(auto & i : etcFiles) { + if(pathExists(i)) { + ctx.filesInChroot[i] = i; + ctx.readOnlyFilesInChroot.insert(i); + } + } + + /* Make everything immediately under "/" read-only, since we can't make / + itself read-only. */ + DirEntries dirs = readDirectory("/"); + for (auto & i : dirs) { + string fs = "/" + i.name; + if(fs == "/etc") continue; + if(fs == "/run") continue; + ctx.filesInChroot[fs] = fs; + ctx.readOnlyFilesInChroot.insert(fs); + } + + if(mkdir((ctx.chrootRootDir + "/run").c_str(), 0700) == -1) + throw SysError("mkdir /run in chroot"); +} + + +static void remapIdsTo0Action(SpawnContext & sctx) +{ + CloneSpawnContext & ctx = (CloneSpawnContext &) sctx; + string uid = std::to_string(ctx.setuid ? ctx.user : getuid()); + string gid = std::to_string(ctx.setgid ? ctx.group : getgid()); + + /* If uid != getuid(), then the process that writes to uid_map needs + * capabilities in the parent user namespace. Fork a child to stay in + * the parent namespace and do the write for us. */ + unshareAndInitUserns(CLONE_NEWUSER, + "0 " + uid + " 1", + "0 " + gid + " 1", + ctx.lockMountsAllowSetgroups); + + ctx.user = 0; + ctx.group = 0; +} + + +/* Spawn 'slirp4netns' in separate namespaces as the given user and group; + 'tapfd' must correspond to a /dev/net/tun connection. Configure it to + write to 'notifyReadyFD' once it's up and running. */ +static pid_t spawnSlirp4netns(int tapfd, int notifyReadyFD, + uid_t slirpUser, gid_t slirpGroup) +{ + Pipe slirpSetupPipe; + CloneSpawnContext slirpCtx; + AutoCloseFD devNullFd; + bool amRoot = geteuid() == 0; + bool newUserNS = !amRoot; + slirpCtx.phases = getCloneSpawnPhases(); + slirpCtx.cloneFlags = + /* slirp4netns will handle the chroot and pivot_root on its own, but + we should ensure that whatever filesystem holds the slirp4netns + executable is read-only, since otherwise it might be possible for a + compromised slirp4netns to overwrite itself using /proc/self/exe, + depending on who owns what. */ + CLONE_NEWNS | + /* ptrace disregards user namespaces when the would-be tracing process + and the would-be traced process have the same real, effective, and + saved user ids. The only way to protect them is to make it + impossible to reference them. */ + CLONE_NEWPID | + /* need this when we're not running as root so that we have the + * capabilities to create the other namespaces. */ + (newUserNS ? CLONE_NEWUSER : 0) | + /* For good measure */ + CLONE_NEWIPC | + CLONE_NEWUTS | + /* Of course, a new network namespace would defeat the + purpose. */ + SIGCHLD; + slirpCtx.program = settings.slirp4netns; + slirpCtx.args = + { "slirp4netns", "--netns-type=tapfd", + "--enable-sandbox", + "--enable-ipv6", + "--ready-fd=" + std::to_string(notifyReadyFD) }; + if(!settings.useHostLoopback) + slirpCtx.args.push_back("--disable-host-loopback"); + slirpCtx.args.push_back(std::to_string(tapfd)); + slirpCtx.inheritEnv = true; + if(newUserNS) { + slirpSetupPipe.create(); + slirpCtx.setupFD = slirpSetupPipe.readSide; + slirpCtx.earlyCloseFDs.insert(slirpSetupPipe.writeSide); + } + slirpCtx.closeMostFDs = true; + slirpCtx.preserveFDs.insert(notifyReadyFD); + slirpCtx.preserveFDs.insert(tapfd); + slirpCtx.setStdin = true; + slirpCtx.stdinFile = "/dev/null"; + slirpCtx.setsid = true; + slirpCtx.dropAmbientCapabilities = true; + slirpCtx.doChroot = true; + slirpCtx.mountTmpfsOnChroot = true; + slirpCtx.chrootRootDir = getEnv("TMPDIR", "/tmp"); + slirpCtx.lockMounts = true; + slirpCtx.lockMountsMapAll = true; /* So that later setuid will work */ + slirpCtx.lockMountsAllowSetgroups = amRoot; + slirpCtx.mountProc = true; + slirpCtx.setuid = true; + slirpCtx.user = slirpUser; + slirpCtx.setgid = true; + slirpCtx.group = slirpGroup; + /* Dropping supplementary groups requires capabilities in current user + * namespace */ + if(amRoot) { + slirpCtx.supplementaryGroups = {}; + slirpCtx.setSupplementaryGroups = true; + } + slirpCtx.seccompFilter = slirpSeccompFilter(); + slirpCtx.addSeccompFilter = true; + + /* Silence slirp4netns output unless requested */ + if(verbosity <= lvlInfo) { + devNullFd = open("/dev/null", O_WRONLY); + if(devNullFd == -1) + throw SysError("cannot open `/dev/null'"); + slirpCtx.logFD = devNullFd; + } + + addPhaseAfter(slirpCtx.phases, + "makeChrootSeparateFilesystem", + "prepareSlirpChroot", + prepareSlirpChrootAction); + + /* slirp behaves differently when uid != 0 */ + addPhaseAfter(slirpCtx.phases, + "lockMounts", + "remapIdsTo0", + remapIdsTo0Action); + +#if 0 /* For debugging networking issues */ + slirpCtx.env["SLIRP_DEBUG"] = "call,misc,error,tftp,verbose_call"; + slirpCtx.env["G_MESSAGES_DEBUG"] = "all"; +#endif + + pid_t slirpPid = cloneChild(slirpCtx); + + if(newUserNS) { + slirpSetupPipe.readSide.close(); + initializeUserNamespace(slirpPid, getuid(), getgid(), getuid(), getgid()); + writeFull(slirpSetupPipe.writeSide, (unsigned char*)"go\n", 3); + } + return slirpPid; +} + +static void clearRootWritePermsAction(SpawnContext & sctx) { if(chmod("/", 0555) == -1) throw SysError("changing mode of chroot root directory"); } + +/* Note: linux-only */ +bool haveGlobalIPv6Address() +{ + if(!pathExists("/proc/net/if_inet6")) return false; + + auto addresses = tokenizeString<Strings>(readFile("/proc/net/if_inet6", true), "\n"); + for(auto & i : addresses) { + auto fields = tokenizeString<vector<string> >(i, " "); + auto scopeHex = fields.at(3); + /* 0x0 means "Global scope" */ + if(scopeHex == "00" || scopeHex == "40") return true; + } + return false; +} + #endif /* CHROOT_ENABLED */ /* Return true if the operating system kernel part of SYSTEM1 and SYSTEM2 (the @@ -1731,10 +2165,10 @@ void DerivationGoal::startBuilder() f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); startNest(nest, lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds); - /* A CloneSpawnContext reference can be passed to procedures expecting a - SpawnContext reference */ + /* A ChrootBuildSpawnContext reference can be passed to procedures + expecting a SpawnContext reference */ #if CHROOT_ENABLED - CloneSpawnContext ctx; + ChrootBuildSpawnContext ctx; #else SpawnContext ctx; #endif @@ -1945,6 +2379,10 @@ void DerivationGoal::startBuilder() ctx.supplementaryGroups = buildUser.getSupplementaryGIDs(); } +#if CHROOT_ENABLED + bool useSlirp4netns = false; +#endif + if (useChroot) { #if CHROOT_ENABLED ctx.phases = getCloneSpawnPhases(); @@ -1960,14 +2398,26 @@ void DerivationGoal::startBuilder() /* Clean up the chroot directory automatically. */ autoDelChroot = std::shared_ptr<AutoDelete>(new AutoDelete(chrootRootTop)); + if(fixedOutput) { + if(findProgram(settings.slirp4netns) == "") + printMsg(lvlError, format("`%1%' can't be found in PATH, network access disabled") % settings.slirp4netns); + else { + if(!pathExists("/dev/net/tun")) + printMsg(lvlError, "`/dev/net/tun' is missing, network access disabled"); + else { + useSlirp4netns = true; + ctx.ipv6Enabled = haveGlobalIPv6Address(); + } + } + } + ctx.doChroot = true; ctx.chrootRootDir = chrootRootDir; - ctx.cloneFlags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD; + ctx.cloneFlags = CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD; - if(!fixedOutput) { + if(!fixedOutput || /* redundant but shows the cases clearly */ + (fixedOutput && !settings.useHostLoopback)) ctx.initLoopback = true; - ctx.cloneFlags |= CLONE_NEWNET; - } if(!buildUser.enabled()) ctx.cloneFlags |= CLONE_NEWUSER; @@ -2014,18 +2464,42 @@ void DerivationGoal::startBuilder() if (fixedOutput) { /* Fixed-output derivations typically need to access the network, so give them access to /etc/resolv.conf and so on. */ - auto files = { "/etc/resolv.conf", "/etc/nsswitch.conf", - "/etc/services", "/etc/hosts" }; - for (auto & file: files) { + std::vector<Path> files = { "/etc/services", "/etc/nsswitch.conf" }; + if (useSlirp4netns) { + if (settings.useHostLoopback) { + string hosts; + if(pathExists("/etc/hosts")) { + hosts = readFile("/etc/hosts"); + hosts = std::regex_replace(hosts, std::regex("127\\.0\\.0\\.1"), "10.0.2.2"); + hosts = std::regex_replace(hosts, std::regex("::1"), "fd00::2"); + } else { + hosts = + "10.0.2.2 localhost\n" + "fd00::2 localhost\n"; + } + writeFile(chrootRootDir + "/etc/hosts", hosts); + } + else { + files.push_back("/etc/hosts"); + } + writeFile(chrootRootDir + "/etc/resolv.conf", "nameserver 10.0.2.3"); + } + else { + files.push_back("/etc/hosts"); + files.push_back("/etc/resolv.conf"); + } + for (auto & file : files) { if (pathExists(file)) { ctx.filesInChroot[file] = file; ctx.readOnlyFilesInChroot.insert(file); } } - } else { - /* Create /etc/hosts with localhost entry. */ - writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n"); } + else + /* Create /etc/hosts with localhost entry. */ + writeFile(chrootRootDir + "/etc/hosts", + "127.0.0.1 localhost\n" + "::1 localhost\n"); /* Bind-mount a user-configurable set of directories from the host file system. */ @@ -2175,7 +2649,9 @@ void DerivationGoal::startBuilder() - The private network namespace ensures that the builder cannot talk to the outside world (or vice versa). It only has a - private loopback interface. + private loopback interface. As an exception, fixed-output + derivations may talk to the outside world through slirp4netns, but + still in a separate network namespace. - The IPC namespace prevents the builder from communicating with outside processes using SysV IPC mechanisms (shared @@ -2191,7 +2667,7 @@ void DerivationGoal::startBuilder() AutoCloseFD parentSetupSocket; AutoCloseFD childSetupSocket; - if(((ctx.cloneFlags & CLONE_NEWUSER) != 0)) { + if(((ctx.cloneFlags & CLONE_NEWUSER) != 0) || useSlirp4netns) { if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)) throw SysError("creating setup socket"); parentSetupSocket = fds[0]; @@ -2202,6 +2678,15 @@ void DerivationGoal::startBuilder() ctx.setupFD = childSetupSocket; } + if(useSlirp4netns) { + addPhaseAfter(ctx.phases, "initLoopback", "setupTap", setupTapAction); + addPhaseAfter(ctx.phases, "setupTap", "waitForSlirpReady", + waitForSlirpReadyAction); + if(settings.useHostLoopback) + addPhaseAfter(ctx.phases, "waitForSlirpReady", "enableRouteLocalnet", + enableRouteLocalnetAction); + } + pid = cloneChild(ctx); if(childSetupSocket >= 0) childSetupSocket.close(); @@ -2211,6 +2696,34 @@ void DerivationGoal::startBuilder() initializeUserNamespace(pid); writeFull(parentSetupSocket, (unsigned char*)"go\n", 3); } + + try { + if(useSlirp4netns) { + AutoCloseFD tapfd = receiveFD(parentSetupSocket); + /* Start 'slirp4netns' to provide networking in the child process; + running the builder in the global network namespace would give + it access to the global namespace of abstract sockets, which + could be used to grant write access to the store to an external + process. */ + slirp = spawnSlirp4netns( + tapfd, + parentSetupSocket, + /* Do whatever we can to run slirp4netns as some user + other than root - run it as the build user if + necessary */ + buildUser.enabled() ? buildUser.getUID() : getuid(), + buildUser.enabled() ? buildUser.getGID() : getgid()); + } + } catch(std::exception & e) { + if(slirp != -1) { + slirp.kill(true); + } + if(pid != -1) { + pid.kill(true); + } + throw e; + } + } else #endif { |