summaryrefslogtreecommitdiff
path: root/nix/libstore
diff options
context:
space:
mode:
Diffstat (limited to 'nix/libstore')
-rw-r--r--nix/libstore/build.cc551
-rw-r--r--nix/libstore/globals.cc2
-rw-r--r--nix/libstore/globals.hh9
3 files changed, 543 insertions, 19 deletions
diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc
index 51f5aed1068..1a688f3b56c 100644
--- a/nix/libstore/build.cc
+++ b/nix/libstore/build.cc
@@ -14,6 +14,7 @@
14#include <map> 14#include <map>
15#include <sstream> 15#include <sstream>
16#include <algorithm> 16#include <algorithm>
17#include <regex>
17 18
18#include <limits.h> 19#include <limits.h>
19#include <time.h> 20#include <time.h>
@@ -73,10 +74,18 @@
73#endif 74#endif
74 75
75#if CHROOT_ENABLED 76#if CHROOT_ENABLED
76#include <sys/socket.h>
77#include <sys/ioctl.h> 77#include <sys/ioctl.h>
78#include <net/if.h> 78#include <net/if.h>
79#include <netinet/ip.h> 79#include <sys/socket.h>
80#include <netinet/in.h>
81#include <net/route.h>
82#include <arpa/inet.h>
83#if __linux__
84#include <linux/if_tun.h>
85/* This header isn't documented in 'man netdevice', but there doesn't seem to
86 be any other way to get 'struct in6_ifreq'... */
87#include <linux/ipv6.h>
88#endif
80#endif 89#endif
81 90
82#if __linux__ 91#if __linux__
@@ -661,6 +670,10 @@ private:
661 /* Whether this is a fixed-output derivation. */ 670 /* Whether this is a fixed-output derivation. */
662 bool fixedOutput; 671 bool fixedOutput;
663 672
673 /* PID of the 'slirp4netns' process in case of a fixed-output
674 derivation. */
675 Pid slirp;
676
664 typedef void (DerivationGoal::*GoalState)(); 677 typedef void (DerivationGoal::*GoalState)();
665 GoalState state; 678 GoalState state;
666 679
@@ -831,6 +844,10 @@ void DerivationGoal::killChild()
831 worker.childTerminated(hook->pid); 844 worker.childTerminated(hook->pid);
832 } 845 }
833 hook.reset(); 846 hook.reset();
847
848 if (slirp != -1)
849 /* Terminate the 'slirp4netns' process. */
850 slirp.kill();
834} 851}
835 852
836 853
@@ -1611,7 +1628,9 @@ static const gid_t guestGID = 30000;
1611/* Initialize the user namespace of CHILD. */ 1628/* Initialize the user namespace of CHILD. */
1612static void initializeUserNamespace(pid_t child, 1629static void initializeUserNamespace(pid_t child,
1613 uid_t hostUID = getuid(), 1630 uid_t hostUID = getuid(),
1614 gid_t hostGID = getgid()) 1631 gid_t hostGID = getgid(),
1632 uid_t guestUID = guestUID,
1633 gid_t guestGID = guestGID)
1615{ 1634{
1616 writeFile("/proc/" + std::to_string(child) + "/uid_map", 1635 writeFile("/proc/" + std::to_string(child) + "/uid_map",
1617 (format("%d %d 1") % guestUID % hostUID).str()); 1636 (format("%d %d 1") % guestUID % hostUID).str());
@@ -1624,12 +1643,427 @@ static void initializeUserNamespace(pid_t child,
1624 1643
1625#if CHROOT_ENABLED 1644#if CHROOT_ENABLED
1626 1645
1627void clearRootWritePermsAction(SpawnContext & sctx) 1646/* Creating TAP device for the fixed-output derivation build environment,
1647 based on how slirp4netns does it. send_fd_socket is a unix-domain socket
1648 that a file descriptor for the TAP device will be sent on along with a
1649 single null byte of regular data. */
1650static void setupTap(int send_fd_socket, bool ipv6Enabled)
1651{
1652 AutoCloseFD tapfd;
1653 struct ifreq ifr;
1654 struct in6_ifreq ifr6;
1655 char tapname[] = "tap0";
1656 int ifindex;
1657
1658 tapfd = open("/dev/net/tun", O_RDWR);
1659 if(tapfd < 0)
1660 throw SysError("opening `/dev/net/tun'");
1661
1662 memset(&ifr, 0, sizeof(ifr));
1663 ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
1664 strncpy(ifr.ifr_name, tapname, sizeof(ifr.ifr_name) - 1);
1665 if(ioctl(tapfd, TUNSETIFF, (void*)&ifr) < 0)
1666 throw SysError("TUNSETIFF");
1667
1668 /* DAD is "duplicate address detection". By default the kernel will put
1669 any ipv6 addresses that we add into the "tentative" state, and only
1670 after several seconds have been spent trying to chat with network
1671 neighbors about whether anyone is already using the address will it
1672 allow it to be bound to, whether for listening or for connecting.
1673
1674 This causes tcp connections initiated before then to bind to ::1, which
1675 obviously is not a valid address for communication between hosts. Even
1676 after the real addresses leave the "tentative" state, the source address
1677 used for the already-started connection attempt does not change.
1678
1679 In our situation we know for a fact nobody else is using the addresses
1680 we give, so there's no point in waiting the extra several seconds to
1681 perform DAD; disable it entirely instead.
1682
1683 Note: this needs to use conf/tap0/ instead of conf/all/ */
1684 writeFile("/proc/sys/net/ipv6/conf/tap0/accept_dad", "0");
1685
1686 /* By default tap0 will solicit and receive router advertisements, and
1687 * thereby obtain an ipv6 address from slirp4netns. But if the host
1688 * doesn't have a working ipv6 connection, this could mess things up for
1689 * guest programs (and really the guest network stack itself), as they
1690 * have no way of knowing that, and will therefore likely try connecting
1691 * to addresses found in AAAA records, which will fail. To prevent this,
1692 * ignore router advertisements. */
1693 writeFile("/proc/sys/net/ipv6/conf/tap0/accept_ra", "0");
1694
1695 /* Now set up:
1696 1. tap0's active flags (so it's running, up, etc)
1697 2. tap0's MTU
1698 3. tap0's ip address
1699 4. tap0's network mask
1700 5. A default route to tap0 */
1701 AutoCloseFD sockfd = socket(AF_INET, SOCK_DGRAM, 0);
1702
1703 if(sockfd < 0)
1704 throw SysError("creating socket");
1705
1706 AutoCloseFD sockfd6 = socket(AF_INET6, SOCK_DGRAM, 0);
1707
1708 if(sockfd6 < 0)
1709 throw SysError("creating ipv6 socket");
1710
1711 if(ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0)
1712 throw SysError("getting tap0 ifindex");
1713
1714 ifindex = ifr.ifr_ifindex;
1715
1716 ifr.ifr_flags = IFF_UP | IFF_RUNNING;
1717 if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0)
1718 throw SysError("setting flags for tap0");
1719
1720 /* slirp4netns default */
1721 ifr.ifr_mtu = 1500;
1722 if(ioctl(sockfd, SIOCSIFMTU, &ifr) < 0)
1723 throw SysError("setting MTU for tap0");
1724
1725 /* default network CIDR: 10.0.2.0/24, fd00::/64 */
1726 /* default recommended_vguest: 10.0.2.100, fd00::??? (we choose to use
1727 fd00::80 and fe80::80) */
1728 /* default gateway: 10.0.2.2, fd00::2 */
1729 struct sockaddr_in *sai = (struct sockaddr_in *) &ifr.ifr_addr;
1730 sai->sin_family = AF_INET;
1731 sai->sin_port = htonl(0);
1732 if(inet_pton(AF_INET, "10.0.2.100", &sai->sin_addr) != 1)
1733 throw Error("inet_pton failed");
1734
1735 if(ioctl(sockfd, SIOCSIFADDR, &ifr) < 0)
1736 throw SysError("setting tap0 address");
1737
1738 if(ipv6Enabled) {
1739 if(inet_pton(AF_INET6, "fd00::80", &ifr6.ifr6_addr) != 1)
1740 throw Error("inet_pton failed");
1741 ifr6.ifr6_prefixlen = 64;
1742 ifr6.ifr6_ifindex = ifindex;
1743
1744 if(ioctl(sockfd6, SIOCSIFADDR, &ifr6) < 0)
1745 throw SysError("setting tap0 ipv6 address");
1746 }
1747
1748 /* Always set up the link-local address so that communication with the
1749 * host loopback over ipv6 can be possible. */
1750 if(inet_pton(AF_INET6, "fe80::80", &ifr6.ifr6_addr) != 1)
1751 throw Error("inet_pton failed");
1752 ifr6.ifr6_prefixlen = 64;
1753 ifr6.ifr6_ifindex = ifindex;
1754
1755 if(ioctl(sockfd6, SIOCSIFADDR, &ifr6) < 0)
1756 throw SysError("setting tap0 link-local ipv6 address");
1757
1758 if(inet_pton(AF_INET, "255.255.255.0", &sai->sin_addr) != 1)
1759 throw Error("inet_pton failed");
1760
1761 if(ioctl(sockfd, SIOCSIFNETMASK, &ifr) < 0)
1762 throw SysError("setting tap0 network mask");
1763
1764 /* To my knowledge there is no official documentation of SIOCADDRT and
1765 struct rtentry for Linux aside from the Linux kernel source code as of
1766 the year 2025. This is therefore fully cargo-culted from
1767 slirp4netns. */
1768
1769 struct rtentry route;
1770 memset(&route, 0, sizeof(route));
1771 sai = (struct sockaddr_in *)&route.rt_gateway;
1772 sai->sin_family = AF_INET;
1773 if(inet_pton(AF_INET, "10.0.2.2", &sai->sin_addr) != 1)
1774 throw Error("inet_pton failed");
1775 sai = (struct sockaddr_in *)&route.rt_dst;
1776 sai->sin_family = AF_INET;
1777 sai->sin_addr.s_addr = htonl(INADDR_ANY);
1778 sai = (struct sockaddr_in *)&route.rt_genmask;
1779 sai->sin_family = AF_INET;
1780 sai->sin_addr.s_addr = htonl(INADDR_ANY);
1781
1782 route.rt_flags = RTF_UP | RTF_GATEWAY;
1783 route.rt_metric = 0;
1784 route.rt_dev = tapname;
1785
1786 if(ioctl(sockfd, SIOCADDRT, &route) < 0)
1787 throw SysError("setting tap0 as default route");
1788
1789 struct in6_rtmsg route6;
1790 memset(&route6, 0, sizeof(route6));
1791 if(inet_pton(AF_INET6, "fd00::2", &route6.rtmsg_gateway) != 1)
1792 throw Error("inet_pton failed");
1793
1794 if(ipv6Enabled) {
1795 /* Set up a default gateway via slirp4netns */
1796 route6.rtmsg_dst = IN6ADDR_ANY_INIT;
1797 route6.rtmsg_dst_len = 0;
1798 route6.rtmsg_flags = RTF_UP | RTF_GATEWAY;
1799 } else {
1800 /* Set up a route to slirp4netns, but only for talking to the host
1801 * loopback */
1802 if(inet_pton(AF_INET6, "fd00::2", &route6.rtmsg_dst) != 1)
1803 throw Error("inet_pton failed");
1804 route6.rtmsg_dst_len = 128;
1805 route6.rtmsg_flags = RTF_UP;
1806 }
1807 route6.rtmsg_src = IN6ADDR_ANY_INIT;
1808 route6.rtmsg_src_len = 0;
1809 route6.rtmsg_ifindex = ifindex;
1810 route6.rtmsg_metric = 1;
1811
1812 if(ioctl(sockfd6, SIOCADDRT, &route6) < 0)
1813 throw SysError("setting tap0 as default ipv6 route");
1814
1815 sendFD(send_fd_socket, tapfd);
1816}
1817
1818struct ChrootBuildSpawnContext : CloneSpawnContext {
1819 bool ipv6Enabled = false;
1820};
1821
1822static void setupTapAction(SpawnContext & sctx)
1823{
1824 ChrootBuildSpawnContext & ctx = (ChrootBuildSpawnContext &) sctx;
1825 setupTap(ctx.setupFD, ctx.ipv6Enabled);
1826}
1827
1828
1829static void waitForSlirpReadyAction(SpawnContext & sctx)
1830{
1831 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
1832 /* Wait for the parent process to get slirp4netns running */
1833 waitForMessage(ctx.setupFD, "1");
1834}
1835
1836
1837static void enableRouteLocalnetAction(SpawnContext & sctx)
1838{
1839 /* Don't treat as invalid packets received with loopback source addresses.
1840 This allows for packets to be received from the host loopback using its
1841 real address, so for example proxy settings referencing 127.0.0.1 will
1842 work both for builtin and regular fixed-output derivations. */
1843
1844 /* Note: this file is treated relative to the network namespace of the
1845 process that opens it. We aren't modifying any host settings here,
1846 provided we are in a new network namespace. */
1847 Path route_localnet4 = "/proc/sys/net/ipv4/conf/all/route_localnet";
1848 /* XXX: no such toggle exists for ipv6 */
1849 if(pathExists(route_localnet4))
1850 writeFile(route_localnet4, "1");
1851}
1852
1853
1854static void prepareSlirpChrootAction(SpawnContext & sctx)
1855{
1856 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
1857 auto mounts = tokenizeString<Strings>(readFile("/proc/self/mountinfo", true), "\n");
1858 set<string> seen;
1859 for(auto & i : mounts) {
1860 auto fields = tokenizeString<vector<string> >(i, " ");
1861 auto fs = decodeOctalEscaped(fields.at(4));
1862 if(seen.find(fs) == seen.end()) {
1863 /* slirp4netns only does a single umount of the old root ("/old")
1864 after pivot_root. Because of this, if there are multiple
1865 mounts stacked on top of each other, only the topmost one (the
1866 read-only bind mount) will be unmounted, leaving the real root
1867 in place and causing the subsequent rmdir to fail. The best we
1868 can do is to make everything immediately underneath "/" be
1869 read-only, which we do after mounting every non-/ filesystem
1870 read-only. */
1871 if(fs == "/") continue;
1872 /* Don't mount /etc or any of its subdirectories, we're only interested
1873 in mounting network stuff from it */
1874 if(fs.compare(0, 4, "/etc") == 0) continue;
1875 /* We want /run to be empty */
1876 if(fs.compare(0, 4, "/run") == 0) continue;
1877 /* Don't mount anything from under our chroot directory */
1878 if(fs.compare(0, ctx.chrootRootDir.length(), ctx.chrootRootDir) == 0) continue;
1879 struct stat st;
1880 if(stat(fs.c_str(), &st) != 0) {
1881 if(errno == EACCES) continue; /* Not accessible anyway */
1882 else throw SysError(format("stat of `%1%'") % fs);
1883 }
1884
1885 ctx.readOnlyFilesInChroot.insert(fs);
1886 ctx.filesInChroot[fs] = fs;
1887 seen.insert(fs);
1888 }
1889 }
1890
1891 /* Limit /etc to containing just /etc/resolv.conf and /etc/hosts, and
1892 read-only at that */
1893 Strings etcFiles = { "/etc/resolv.conf", "/etc/hosts" };
1894 for(auto & i : etcFiles) {
1895 if(pathExists(i)) {
1896 ctx.filesInChroot[i] = i;
1897 ctx.readOnlyFilesInChroot.insert(i);
1898 }
1899 }
1900
1901 /* Make everything immediately under "/" read-only, since we can't make /
1902 itself read-only. */
1903 DirEntries dirs = readDirectory("/");
1904 for (auto & i : dirs) {
1905 string fs = "/" + i.name;
1906 if(fs == "/etc") continue;
1907 if(fs == "/run") continue;
1908 ctx.filesInChroot[fs] = fs;
1909 ctx.readOnlyFilesInChroot.insert(fs);
1910 }
1911
1912 if(mkdir((ctx.chrootRootDir + "/run").c_str(), 0700) == -1)
1913 throw SysError("mkdir /run in chroot");
1914}
1915
1916
1917static void remapIdsTo0Action(SpawnContext & sctx)
1918{
1919 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
1920 string uid = std::to_string(ctx.setuid ? ctx.user : getuid());
1921 string gid = std::to_string(ctx.setgid ? ctx.group : getgid());
1922
1923 /* If uid != getuid(), then the process that writes to uid_map needs
1924 * capabilities in the parent user namespace. Fork a child to stay in
1925 * the parent namespace and do the write for us. */
1926 unshareAndInitUserns(CLONE_NEWUSER,
1927 "0 " + uid + " 1",
1928 "0 " + gid + " 1",
1929 ctx.lockMountsAllowSetgroups);
1930
1931 ctx.user = 0;
1932 ctx.group = 0;
1933}
1934
1935
1936/* Spawn 'slirp4netns' in separate namespaces as the given user and group;
1937 'tapfd' must correspond to a /dev/net/tun connection. Configure it to
1938 write to 'notifyReadyFD' once it's up and running. */
1939static pid_t spawnSlirp4netns(int tapfd, int notifyReadyFD,
1940 uid_t slirpUser, gid_t slirpGroup)
1941{
1942 Pipe slirpSetupPipe;
1943 CloneSpawnContext slirpCtx;
1944 AutoCloseFD devNullFd;
1945 bool amRoot = geteuid() == 0;
1946 bool newUserNS = !amRoot;
1947 slirpCtx.phases = getCloneSpawnPhases();
1948 slirpCtx.cloneFlags =
1949 /* slirp4netns will handle the chroot and pivot_root on its own, but
1950 we should ensure that whatever filesystem holds the slirp4netns
1951 executable is read-only, since otherwise it might be possible for a
1952 compromised slirp4netns to overwrite itself using /proc/self/exe,
1953 depending on who owns what. */
1954 CLONE_NEWNS |
1955 /* ptrace disregards user namespaces when the would-be tracing process
1956 and the would-be traced process have the same real, effective, and
1957 saved user ids. The only way to protect them is to make it
1958 impossible to reference them. */
1959 CLONE_NEWPID |
1960 /* need this when we're not running as root so that we have the
1961 * capabilities to create the other namespaces. */
1962 (newUserNS ? CLONE_NEWUSER : 0) |
1963 /* For good measure */
1964 CLONE_NEWIPC |
1965 CLONE_NEWUTS |
1966 /* Of course, a new network namespace would defeat the
1967 purpose. */
1968 SIGCHLD;
1969 slirpCtx.program = settings.slirp4netns;
1970 slirpCtx.args =
1971 { "slirp4netns", "--netns-type=tapfd",
1972 "--enable-sandbox",
1973 "--enable-ipv6",
1974 "--ready-fd=" + std::to_string(notifyReadyFD) };
1975 if(!settings.useHostLoopback)
1976 slirpCtx.args.push_back("--disable-host-loopback");
1977 slirpCtx.args.push_back(std::to_string(tapfd));
1978 slirpCtx.inheritEnv = true;
1979 if(newUserNS) {
1980 slirpSetupPipe.create();
1981 slirpCtx.setupFD = slirpSetupPipe.readSide;
1982 slirpCtx.earlyCloseFDs.insert(slirpSetupPipe.writeSide);
1983 }
1984 slirpCtx.closeMostFDs = true;
1985 slirpCtx.preserveFDs.insert(notifyReadyFD);
1986 slirpCtx.preserveFDs.insert(tapfd);
1987 slirpCtx.setStdin = true;
1988 slirpCtx.stdinFile = "/dev/null";
1989 slirpCtx.setsid = true;
1990 slirpCtx.dropAmbientCapabilities = true;
1991 slirpCtx.doChroot = true;
1992 slirpCtx.mountTmpfsOnChroot = true;
1993 slirpCtx.chrootRootDir = getEnv("TMPDIR", "/tmp");
1994 slirpCtx.lockMounts = true;
1995 slirpCtx.lockMountsMapAll = true; /* So that later setuid will work */
1996 slirpCtx.lockMountsAllowSetgroups = amRoot;
1997 slirpCtx.mountProc = true;
1998 slirpCtx.setuid = true;
1999 slirpCtx.user = slirpUser;
2000 slirpCtx.setgid = true;
2001 slirpCtx.group = slirpGroup;
2002 /* Dropping supplementary groups requires capabilities in current user
2003 * namespace */
2004 if(amRoot) {
2005 slirpCtx.supplementaryGroups = {};
2006 slirpCtx.setSupplementaryGroups = true;
2007 }
2008 slirpCtx.seccompFilter = slirpSeccompFilter();
2009 slirpCtx.addSeccompFilter = true;
2010
2011 /* Silence slirp4netns output unless requested */
2012 if(verbosity <= lvlInfo) {
2013 devNullFd = open("/dev/null", O_WRONLY);
2014 if(devNullFd == -1)
2015 throw SysError("cannot open `/dev/null'");
2016 slirpCtx.logFD = devNullFd;
2017 }
2018
2019 addPhaseAfter(slirpCtx.phases,
2020 "makeChrootSeparateFilesystem",
2021 "prepareSlirpChroot",
2022 prepareSlirpChrootAction);
2023
2024 /* slirp behaves differently when uid != 0 */
2025 addPhaseAfter(slirpCtx.phases,
2026 "lockMounts",
2027 "remapIdsTo0",
2028 remapIdsTo0Action);
2029
2030#if 0 /* For debugging networking issues */
2031 slirpCtx.env["SLIRP_DEBUG"] = "call,misc,error,tftp,verbose_call";
2032 slirpCtx.env["G_MESSAGES_DEBUG"] = "all";
2033#endif
2034
2035 pid_t slirpPid = cloneChild(slirpCtx);
2036
2037 if(newUserNS) {
2038 slirpSetupPipe.readSide.close();
2039 initializeUserNamespace(slirpPid, getuid(), getgid(), getuid(), getgid());
2040 writeFull(slirpSetupPipe.writeSide, (unsigned char*)"go\n", 3);
2041 }
2042 return slirpPid;
2043}
2044
2045static void clearRootWritePermsAction(SpawnContext & sctx)
1628{ 2046{
1629 if(chmod("/", 0555) == -1) 2047 if(chmod("/", 0555) == -1)
1630 throw SysError("changing mode of chroot root directory"); 2048 throw SysError("changing mode of chroot root directory");
1631} 2049}
1632 2050
2051
2052/* Note: linux-only */
2053bool haveGlobalIPv6Address()
2054{
2055 if(!pathExists("/proc/net/if_inet6")) return false;
2056
2057 auto addresses = tokenizeString<Strings>(readFile("/proc/net/if_inet6", true), "\n");
2058 for(auto & i : addresses) {
2059 auto fields = tokenizeString<vector<string> >(i, " ");
2060 auto scopeHex = fields.at(3);
2061 /* 0x0 means "Global scope" */
2062 if(scopeHex == "00" || scopeHex == "40") return true;
2063 }
2064 return false;
2065}
2066
1633#endif /* CHROOT_ENABLED */ 2067#endif /* CHROOT_ENABLED */
1634 2068
1635/* Return true if the operating system kernel part of SYSTEM1 and SYSTEM2 (the 2069/* Return true if the operating system kernel part of SYSTEM1 and SYSTEM2 (the
@@ -1731,10 +2165,10 @@ void DerivationGoal::startBuilder()
1731 f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); 2165 f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
1732 startNest(nest, lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds); 2166 startNest(nest, lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds);
1733 2167
1734 /* A CloneSpawnContext reference can be passed to procedures expecting a 2168 /* A ChrootBuildSpawnContext reference can be passed to procedures
1735 SpawnContext reference */ 2169 expecting a SpawnContext reference */
1736#if CHROOT_ENABLED 2170#if CHROOT_ENABLED
1737 CloneSpawnContext ctx; 2171 ChrootBuildSpawnContext ctx;
1738#else 2172#else
1739 SpawnContext ctx; 2173 SpawnContext ctx;
1740#endif 2174#endif
@@ -1945,6 +2379,10 @@ void DerivationGoal::startBuilder()
1945 ctx.supplementaryGroups = buildUser.getSupplementaryGIDs(); 2379 ctx.supplementaryGroups = buildUser.getSupplementaryGIDs();
1946 } 2380 }
1947 2381
2382#if CHROOT_ENABLED
2383 bool useSlirp4netns = false;
2384#endif
2385
1948 if (useChroot) { 2386 if (useChroot) {
1949#if CHROOT_ENABLED 2387#if CHROOT_ENABLED
1950 ctx.phases = getCloneSpawnPhases(); 2388 ctx.phases = getCloneSpawnPhases();
@@ -1960,14 +2398,26 @@ void DerivationGoal::startBuilder()
1960 /* Clean up the chroot directory automatically. */ 2398 /* Clean up the chroot directory automatically. */
1961 autoDelChroot = std::shared_ptr<AutoDelete>(new AutoDelete(chrootRootTop)); 2399 autoDelChroot = std::shared_ptr<AutoDelete>(new AutoDelete(chrootRootTop));
1962 2400
2401 if(fixedOutput) {
2402 if(findProgram(settings.slirp4netns) == "")
2403 printMsg(lvlError, format("`%1%' can't be found in PATH, network access disabled") % settings.slirp4netns);
2404 else {
2405 if(!pathExists("/dev/net/tun"))
2406 printMsg(lvlError, "`/dev/net/tun' is missing, network access disabled");
2407 else {
2408 useSlirp4netns = true;
2409 ctx.ipv6Enabled = haveGlobalIPv6Address();
2410 }
2411 }
2412 }
2413
1963 ctx.doChroot = true; 2414 ctx.doChroot = true;
1964 ctx.chrootRootDir = chrootRootDir; 2415 ctx.chrootRootDir = chrootRootDir;
1965 ctx.cloneFlags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD; 2416 ctx.cloneFlags = CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD;
1966 2417
1967 if(!fixedOutput) { 2418 if(!fixedOutput || /* redundant but shows the cases clearly */
2419 (fixedOutput && !settings.useHostLoopback))
1968 ctx.initLoopback = true; 2420 ctx.initLoopback = true;
1969 ctx.cloneFlags |= CLONE_NEWNET;
1970 }
1971 2421
1972 if(!buildUser.enabled()) 2422 if(!buildUser.enabled())
1973 ctx.cloneFlags |= CLONE_NEWUSER; 2423 ctx.cloneFlags |= CLONE_NEWUSER;
@@ -2014,18 +2464,42 @@ void DerivationGoal::startBuilder()
2014 if (fixedOutput) { 2464 if (fixedOutput) {
2015 /* Fixed-output derivations typically need to access the network, 2465 /* Fixed-output derivations typically need to access the network,
2016 so give them access to /etc/resolv.conf and so on. */ 2466 so give them access to /etc/resolv.conf and so on. */
2017 auto files = { "/etc/resolv.conf", "/etc/nsswitch.conf", 2467 std::vector<Path> files = { "/etc/services", "/etc/nsswitch.conf" };
2018 "/etc/services", "/etc/hosts" }; 2468 if (useSlirp4netns) {
2019 for (auto & file: files) { 2469 if (settings.useHostLoopback) {
2470 string hosts;
2471 if(pathExists("/etc/hosts")) {
2472 hosts = readFile("/etc/hosts");
2473 hosts = std::regex_replace(hosts, std::regex("127\\.0\\.0\\.1"), "10.0.2.2");
2474 hosts = std::regex_replace(hosts, std::regex("::1"), "fd00::2");
2475 } else {
2476 hosts =
2477 "10.0.2.2 localhost\n"
2478 "fd00::2 localhost\n";
2479 }
2480 writeFile(chrootRootDir + "/etc/hosts", hosts);
2481 }
2482 else {
2483 files.push_back("/etc/hosts");
2484 }
2485 writeFile(chrootRootDir + "/etc/resolv.conf", "nameserver 10.0.2.3");
2486 }
2487 else {
2488 files.push_back("/etc/hosts");
2489 files.push_back("/etc/resolv.conf");
2490 }
2491 for (auto & file : files) {
2020 if (pathExists(file)) { 2492 if (pathExists(file)) {
2021 ctx.filesInChroot[file] = file; 2493 ctx.filesInChroot[file] = file;
2022 ctx.readOnlyFilesInChroot.insert(file); 2494 ctx.readOnlyFilesInChroot.insert(file);
2023 } 2495 }
2024 } 2496 }
2025 } else {
2026 /* Create /etc/hosts with localhost entry. */
2027 writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n");
2028 } 2497 }
2498 else
2499 /* Create /etc/hosts with localhost entry. */
2500 writeFile(chrootRootDir + "/etc/hosts",
2501 "127.0.0.1 localhost\n"
2502 "::1 localhost\n");
2029 2503
2030 /* Bind-mount a user-configurable set of directories from the 2504 /* Bind-mount a user-configurable set of directories from the
2031 host file system. */ 2505 host file system. */
@@ -2175,7 +2649,9 @@ void DerivationGoal::startBuilder()
2175 2649
2176 - The private network namespace ensures that the builder cannot 2650 - The private network namespace ensures that the builder cannot
2177 talk to the outside world (or vice versa). It only has a 2651 talk to the outside world (or vice versa). It only has a
2178 private loopback interface. 2652 private loopback interface. As an exception, fixed-output
2653 derivations may talk to the outside world through slirp4netns, but
2654 still in a separate network namespace.
2179 2655
2180 - The IPC namespace prevents the builder from communicating 2656 - The IPC namespace prevents the builder from communicating
2181 with outside processes using SysV IPC mechanisms (shared 2657 with outside processes using SysV IPC mechanisms (shared
@@ -2191,7 +2667,7 @@ void DerivationGoal::startBuilder()
2191 AutoCloseFD parentSetupSocket; 2667 AutoCloseFD parentSetupSocket;
2192 AutoCloseFD childSetupSocket; 2668 AutoCloseFD childSetupSocket;
2193 2669
2194 if(((ctx.cloneFlags & CLONE_NEWUSER) != 0)) { 2670 if(((ctx.cloneFlags & CLONE_NEWUSER) != 0) || useSlirp4netns) {
2195 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)) 2671 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds))
2196 throw SysError("creating setup socket"); 2672 throw SysError("creating setup socket");
2197 parentSetupSocket = fds[0]; 2673 parentSetupSocket = fds[0];
@@ -2202,6 +2678,15 @@ void DerivationGoal::startBuilder()
2202 ctx.setupFD = childSetupSocket; 2678 ctx.setupFD = childSetupSocket;
2203 } 2679 }
2204 2680
2681 if(useSlirp4netns) {
2682 addPhaseAfter(ctx.phases, "initLoopback", "setupTap", setupTapAction);
2683 addPhaseAfter(ctx.phases, "setupTap", "waitForSlirpReady",
2684 waitForSlirpReadyAction);
2685 if(settings.useHostLoopback)
2686 addPhaseAfter(ctx.phases, "waitForSlirpReady", "enableRouteLocalnet",
2687 enableRouteLocalnetAction);
2688 }
2689
2205 pid = cloneChild(ctx); 2690 pid = cloneChild(ctx);
2206 2691
2207 if(childSetupSocket >= 0) childSetupSocket.close(); 2692 if(childSetupSocket >= 0) childSetupSocket.close();
@@ -2211,6 +2696,34 @@ void DerivationGoal::startBuilder()
2211 initializeUserNamespace(pid); 2696 initializeUserNamespace(pid);
2212 writeFull(parentSetupSocket, (unsigned char*)"go\n", 3); 2697 writeFull(parentSetupSocket, (unsigned char*)"go\n", 3);
2213 } 2698 }
2699
2700 try {
2701 if(useSlirp4netns) {
2702 AutoCloseFD tapfd = receiveFD(parentSetupSocket);
2703 /* Start 'slirp4netns' to provide networking in the child process;
2704 running the builder in the global network namespace would give
2705 it access to the global namespace of abstract sockets, which
2706 could be used to grant write access to the store to an external
2707 process. */
2708 slirp = spawnSlirp4netns(
2709 tapfd,
2710 parentSetupSocket,
2711 /* Do whatever we can to run slirp4netns as some user
2712 other than root - run it as the build user if
2713 necessary */
2714 buildUser.enabled() ? buildUser.getUID() : getuid(),
2715 buildUser.enabled() ? buildUser.getGID() : getgid());
2716 }
2717 } catch(std::exception & e) {
2718 if(slirp != -1) {
2719 slirp.kill(true);
2720 }
2721 if(pid != -1) {
2722 pid.kill(true);
2723 }
2724 throw e;
2725 }
2726
2214 } else 2727 } else
2215#endif 2728#endif
2216 { 2729 {
diff --git a/nix/libstore/globals.cc b/nix/libstore/globals.cc
index 10c60f6106d..31da8d4769d 100644
--- a/nix/libstore/globals.cc
+++ b/nix/libstore/globals.cc
@@ -56,6 +56,8 @@ Settings::Settings()
56 envKeepDerivations = false; 56 envKeepDerivations = false;
57 lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1"; 57 lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1";
58 showTrace = false; 58 showTrace = false;
59 useHostLoopback = true;
60 slirp4netns = SLIRP4NETNS;
59} 61}
60 62
61 63
diff --git a/nix/libstore/globals.hh b/nix/libstore/globals.hh
index 27616a22834..7cfa06e76c1 100644
--- a/nix/libstore/globals.hh
+++ b/nix/libstore/globals.hh
@@ -206,6 +206,15 @@ struct Settings {
206 /* Whether to show a stack trace if Nix evaluation fails. */ 206 /* Whether to show a stack trace if Nix evaluation fails. */
207 bool showTrace; 207 bool showTrace;
208 208
209 /* Whether fixed-output chroot builds should be able to use the host
210 loopback, for example to access a socks proxy. Note that while using
211 "localhost" and 127.0.0.1 to access the host loopback will work, using
212 ::1 will not, due to a limitation in Linux. */
213 bool useHostLoopback;
214
215 /* The filename to use for executing slirp4netns when it is needed. */
216 Path slirp4netns;
217
209private: 218private:
210 SettingsMap settings, overrides; 219 SettingsMap settings, overrides;
211 220