diff options
Diffstat (limited to 'nix/libstore')
| -rw-r--r-- | nix/libstore/build.cc | 551 | ||||
| -rw-r--r-- | nix/libstore/globals.cc | 2 | ||||
| -rw-r--r-- | nix/libstore/globals.hh | 9 |
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. */ |
| 1612 | static void initializeUserNamespace(pid_t child, | 1629 | static 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 | ||
| 1627 | void 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. */ | ||
| 1650 | static 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 | |||
| 1818 | struct ChrootBuildSpawnContext : CloneSpawnContext { | ||
| 1819 | bool ipv6Enabled = false; | ||
| 1820 | }; | ||
| 1821 | |||
| 1822 | static void setupTapAction(SpawnContext & sctx) | ||
| 1823 | { | ||
| 1824 | ChrootBuildSpawnContext & ctx = (ChrootBuildSpawnContext &) sctx; | ||
| 1825 | setupTap(ctx.setupFD, ctx.ipv6Enabled); | ||
| 1826 | } | ||
| 1827 | |||
| 1828 | |||
| 1829 | static 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 | |||
| 1837 | static 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 | |||
| 1854 | static 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 | |||
| 1917 | static 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. */ | ||
| 1939 | static 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 | |||
| 2045 | static 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 */ | ||
| 2053 | bool 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 | |||
| 209 | private: | 218 | private: |
| 210 | SettingsMap settings, overrides; | 219 | SettingsMap settings, overrides; |
| 211 | 220 | ||
