summaryrefslogtreecommitdiff
path: root/nix
diff options
context:
space:
mode:
Diffstat (limited to 'nix')
-rw-r--r--nix/libstore/build.cc788
-rw-r--r--nix/libutil/spawn.cc829
-rw-r--r--nix/libutil/spawn.hh164
-rw-r--r--nix/libutil/util.cc19
-rw-r--r--nix/libutil/util.hh5
-rw-r--r--nix/local.mk6
6 files changed, 1303 insertions, 508 deletions
diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc
index afdcd9518b3..51f5aed1068 100644
--- a/nix/libstore/build.cc
+++ b/nix/libstore/build.cc
@@ -9,6 +9,7 @@
9#include "archive.hh" 9#include "archive.hh"
10#include "affinity.hh" 10#include "affinity.hh"
11#include "builtins.hh" 11#include "builtins.hh"
12#include "spawn.hh"
12 13
13#include <map> 14#include <map>
14#include <sstream> 15#include <sstream>
@@ -402,22 +403,6 @@ void Goal::trace(const format & f)
402} 403}
403 404
404 405
405
406//////////////////////////////////////////////////////////////////////
407
408
409/* Restore default handling of SIGPIPE, otherwise some programs will
410 randomly say "Broken pipe". */
411static void restoreSIGPIPE()
412{
413 struct sigaction act, oact;
414 act.sa_handler = SIG_DFL;
415 act.sa_flags = 0;
416 sigemptyset(&act.sa_mask);
417 if (sigaction(SIGPIPE, &act, &oact)) throw SysError("resetting SIGPIPE");
418}
419
420
421////////////////////////////////////////////////////////////////////// 406//////////////////////////////////////////////////////////////////////
422 407
423 408
@@ -679,12 +664,6 @@ private:
679 typedef void (DerivationGoal::*GoalState)(); 664 typedef void (DerivationGoal::*GoalState)();
680 GoalState state; 665 GoalState state;
681 666
682 /* Stuff we need to pass to runChild(). */
683 typedef map<Path, Path> DirsInChroot; // maps target path to source path
684 DirsInChroot dirsInChroot;
685 typedef map<string, string> Environment;
686 Environment env;
687
688 /* Hash rewriting. */ 667 /* Hash rewriting. */
689 HashRewrites rewritesToTmp, rewritesFromTmp; 668 HashRewrites rewritesToTmp, rewritesFromTmp;
690 typedef map<Path, Path> RedirectedOutputs; 669 typedef map<Path, Path> RedirectedOutputs;
@@ -753,14 +732,9 @@ private:
753 /* Start building a derivation. */ 732 /* Start building a derivation. */
754 void startBuilder(); 733 void startBuilder();
755 734
756 /* Run the builder's process. */ 735 void execBuilderOrBuiltin(SpawnContext &);
757 void runChild();
758 736
759 friend int childEntry(void *); 737 friend void execBuilderOrBuiltinAction(SpawnContext &);
760
761 /* Pipe to notify readiness to the child process when using unprivileged
762 user namespaces. */
763 Pipe readiness;
764 738
765 /* Check that the derivation outputs all exist and register them 739 /* Check that the derivation outputs all exist and register them
766 as valid. */ 740 as valid. */
@@ -1630,13 +1604,6 @@ void chmod_(const Path & path, mode_t mode)
1630} 1604}
1631 1605
1632 1606
1633int childEntry(void * arg)
1634{
1635 ((DerivationGoal *) arg)->runChild();
1636 return 1;
1637}
1638
1639
1640/* UID and GID of the build user inside its own user namespace. */ 1607/* UID and GID of the build user inside its own user namespace. */
1641static const uid_t guestUID = 30001; 1608static const uid_t guestUID = 30001;
1642static const gid_t guestGID = 30000; 1609static const gid_t guestGID = 30000;
@@ -1655,6 +1622,105 @@ static void initializeUserNamespace(pid_t child,
1655 (format("%d %d 1") % guestGID % hostGID).str()); 1622 (format("%d %d 1") % guestGID % hostGID).str());
1656} 1623}
1657 1624
1625#if CHROOT_ENABLED
1626
1627void clearRootWritePermsAction(SpawnContext & sctx)
1628{
1629 if(chmod("/", 0555) == -1)
1630 throw SysError("changing mode of chroot root directory");
1631}
1632
1633#endif /* CHROOT_ENABLED */
1634
1635/* Return true if the operating system kernel part of SYSTEM1 and SYSTEM2 (the
1636 bit that comes after the hyphen in system types such as "i686-linux") is
1637 the same. */
1638static bool sameOperatingSystemKernel(const std::string& system1, const std::string& system2)
1639{
1640 auto os1 = system1.substr(system1.find("-"));
1641 auto os2 = system2.substr(system2.find("-"));
1642 return os1 == os2;
1643}
1644
1645
1646void DerivationGoal::execBuilderOrBuiltin(SpawnContext & ctx)
1647{
1648 if(isBuiltin(drv)) {
1649 /* Note: must not return from this block */
1650 try {
1651 logType = ltFlat;
1652
1653 auto buildDrv = lookupBuiltinBuilder(drv.builder);
1654 if (buildDrv != NULL) {
1655 /* Check what the output file name is. When doing a 'bmCheck'
1656 build, the output file name is different from that
1657 specified in DRV due to hash rewriting. */
1658 Path output = drv.outputs["out"].path;
1659 auto redirected = redirectedOutputs.find(output);
1660 if (redirected != redirectedOutputs.end())
1661 output = redirected->second;
1662
1663 buildDrv(drv, drvPath, output);
1664 }
1665 else
1666 throw Error(format("unsupported builtin function '%1%'") % string(drv.builder, 8));
1667 _exit(0);
1668 } catch (std::exception & e) {
1669 writeFull(STDERR_FILENO, "error: " + string(e.what()) + "\n");
1670 _exit(1);
1671 }
1672 }
1673 /* Ensure that the builder is within the store. This prevents users from
1674 using /proc/self/exe (or a symlink to it) as their builder, which could
1675 allow them to overwrite the guix-daemon binary (CVE-2019-5736).
1676
1677 This attack is possible even if the target of /proc/self/exe is outside
1678 the chroot (it's as if it were a hard link), though it requires that
1679 its ELF interpreter and dependencies be in the chroot.
1680
1681 Note: 'canonPath' throws if 'ctx.program' cannot be resolved within the
1682 chroot. */
1683 ctx.program = canonPath(ctx.program, true);
1684 if(!isInStore(ctx.program))
1685 throw Error(format("derivation builder `%1' is outside the store") % ctx.program);
1686 /* If DRV targets the same operating system kernel, try to execute it:
1687 there might be binfmt_misc set up for user-land emulation of other
1688 architectures. However, if it targets a different operating
1689 system--e.g., "i586-gnu" vs. "x86_64-linux"--do not try executing it:
1690 the ELF file for that OS is likely indistinguishable from a native ELF
1691 binary and it would just crash at run time. */
1692 int error;
1693 if (sameOperatingSystemKernel(drv.platform, settings.thisSystem)) {
1694 try {
1695 execAction(ctx);
1696 error = errno;
1697 } catch(SysError & e) {
1698 error = e.errNo;
1699 }
1700 } else {
1701 error = ENOEXEC;
1702 }
1703 /* Right platform? Check this after we've tried 'execve' to allow for
1704 transparent emulation of different platforms with binfmt_misc handlers
1705 that invoke QEMU. */
1706 if (error == ENOEXEC && !canBuildLocally(drv.platform)) {
1707 if (settings.printBuildTrace)
1708 printMsg(lvlError, format("@ unsupported-platform %1% %2%") % drvPath % drv.platform);
1709 throw Error(format("a `%1%' is required to build `%3%', but I am a `%2%'")
1710 % drv.platform % settings.thisSystem % drvPath);
1711 }
1712
1713 errno = error;
1714 throw SysError(format("executing `%1%'") % drv.builder);
1715}
1716
1717
1718void execBuilderOrBuiltinAction(SpawnContext & ctx)
1719{
1720 ((DerivationGoal *)ctx.extraData)->execBuilderOrBuiltin(ctx);
1721}
1722
1723
1658void DerivationGoal::startBuilder() 1724void DerivationGoal::startBuilder()
1659{ 1725{
1660 auto f = format( 1726 auto f = format(
@@ -1665,6 +1731,57 @@ void DerivationGoal::startBuilder()
1665 f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); 1731 f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
1666 startNest(nest, lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds); 1732 startNest(nest, lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds);
1667 1733
1734 /* A CloneSpawnContext reference can be passed to procedures expecting a
1735 SpawnContext reference */
1736#if CHROOT_ENABLED
1737 CloneSpawnContext ctx;
1738#else
1739 SpawnContext ctx;
1740#endif
1741
1742 ctx.extraData = (void *) this;
1743 ctx.setsid = true;
1744 ctx.oomSacrifice = true;
1745 ctx.signalSetupSuccess = true;
1746 ctx.setStdin = true;
1747 ctx.stdinFile = "/dev/null";
1748 ctx.closeMostFDs = true;
1749 ctx.program = drv.builder;
1750 ctx.args = drv.args;
1751 if(!isBuiltin(drv))
1752 ctx.args.insert(ctx.args.begin(), baseNameOf(drv.builder));
1753
1754#if __linux__
1755 ctx.dropAmbientCapabilities = true;
1756 ctx.persona = PER_LINUX; /* default */
1757 ctx.setPersona = true;
1758 /* Change the personality to 32-bit if we're doing an
1759 i686-linux build on an x86_64-linux machine. */
1760 struct utsname utsbuf;
1761 uname(&utsbuf);
1762 if (drv.platform == "i686-linux" &&
1763 (settings.thisSystem == "x86_64-linux" ||
1764 (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) {
1765 ctx.persona = PER_LINUX32;
1766 }
1767
1768 if (drv.platform == "armhf-linux" &&
1769 (settings.thisSystem == "aarch64-linux" ||
1770 (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "aarch64")))) {
1771 ctx.persona = PER_LINUX32;
1772 }
1773
1774 /* Impersonate a Linux 2.6 machine to get some determinism in
1775 builds that depend on the kernel version. */
1776 if ((drv.platform == "i686-linux" || drv.platform == "x86_64-linux") && settings.impersonateLinux26) {
1777 ctx.persona |= 0x0020000; /* == UNAME26 */
1778 }
1779
1780 /* Disable address space randomization for improved determinism. */
1781 ctx.persona |= ADDR_NO_RANDOMIZE;
1782
1783#endif
1784
1668 /* Note: built-in builders are *not* running in a chroot environment so 1785 /* Note: built-in builders are *not* running in a chroot environment so
1669 that we can easily implement them in Guile without having it as a 1786 that we can easily implement them in Guile without having it as a
1670 derivation input (they are running under a separate build user, 1787 derivation input (they are running under a separate build user,
@@ -1672,12 +1789,13 @@ void DerivationGoal::startBuilder()
1672 useChroot = settings.useChroot && !isBuiltin(drv); 1789 useChroot = settings.useChroot && !isBuiltin(drv);
1673 1790
1674 /* Construct the environment passed to the builder. */ 1791 /* Construct the environment passed to the builder. */
1675 env.clear(); 1792 ctx.env.clear();
1793 ctx.inheritEnv = false;
1676 1794
1677 /* Most shells initialise PATH to some default (/bin:/usr/bin:...) when 1795 /* Most shells initialise PATH to some default (/bin:/usr/bin:...) when
1678 PATH is not set. We don't want this, so we fill it in with some dummy 1796 PATH is not set. We don't want this, so we fill it in with some dummy
1679 value. */ 1797 value. */
1680 env["PATH"] = "/path-not-set"; 1798 ctx.env["PATH"] = "/path-not-set";
1681 1799
1682 /* Set HOME to a non-existing path to prevent certain programs from using 1800 /* Set HOME to a non-existing path to prevent certain programs from using
1683 /etc/passwd (or NIS, or whatever) to locate the home directory (for 1801 /etc/passwd (or NIS, or whatever) to locate the home directory (for
@@ -1686,20 +1804,20 @@ void DerivationGoal::startBuilder()
1686 they are looking for does not exist if HOME is set but points to some 1804 they are looking for does not exist if HOME is set but points to some
1687 non-existing path. */ 1805 non-existing path. */
1688 Path homeDir = "/homeless-shelter"; 1806 Path homeDir = "/homeless-shelter";
1689 env["HOME"] = homeDir; 1807 ctx.env["HOME"] = homeDir;
1690 1808
1691 /* Tell the builder where the store is. Usually they 1809 /* Tell the builder where the store is. Usually they
1692 shouldn't care, but this is useful for purity checking (e.g., 1810 shouldn't care, but this is useful for purity checking (e.g.,
1693 the compiler or linker might only want to accept paths to files 1811 the compiler or linker might only want to accept paths to files
1694 in the store or in the build directory). */ 1812 in the store or in the build directory). */
1695 env["NIX_STORE"] = settings.nixStore; 1813 ctx.env["NIX_STORE"] = settings.nixStore;
1696 1814
1697 /* The maximum number of cores to utilize for parallel building. */ 1815 /* The maximum number of cores to utilize for parallel building. */
1698 env["NIX_BUILD_CORES"] = (format("%d") % settings.buildCores).str(); 1816 ctx.env["NIX_BUILD_CORES"] = (format("%d") % settings.buildCores).str();
1699 1817
1700 /* Add all bindings specified in the derivation. */ 1818 /* Add all bindings specified in the derivation. */
1701 for (auto& i : drv.env) 1819 for (auto& i : drv.env)
1702 env[i.first] = i.second; 1820 ctx.env[i.first] = i.second;
1703 1821
1704 /* Create a temporary directory where the build will take 1822 /* Create a temporary directory where the build will take
1705 place. */ 1823 place. */
@@ -1728,18 +1846,20 @@ void DerivationGoal::startBuilder()
1728 directory. */ 1846 directory. */
1729 tmpDirInSandbox = useChroot ? canonPath("/tmp", true) + "/guix-build-" + drvName + "-0" : tmpDir; 1847 tmpDirInSandbox = useChroot ? canonPath("/tmp", true) + "/guix-build-" + drvName + "-0" : tmpDir;
1730 1848
1849 ctx.setcwd = true;
1850 ctx.cwd = tmpDirInSandbox;
1731 /* For convenience, set an environment pointing to the top build 1851 /* For convenience, set an environment pointing to the top build
1732 directory. */ 1852 directory. */
1733 env["NIX_BUILD_TOP"] = tmpDirInSandbox; 1853 ctx.env["NIX_BUILD_TOP"] = tmpDirInSandbox;
1734 1854
1735 /* Also set TMPDIR and variants to point to this directory. */ 1855 /* Also set TMPDIR and variants to point to this directory. */
1736 env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = tmpDirInSandbox; 1856 ctx.env["TMPDIR"] = ctx.env["TEMPDIR"] = ctx.env["TMP"] = ctx.env["TEMP"] = tmpDirInSandbox;
1737 1857
1738 /* Explicitly set PWD to prevent problems with chroot builds. In 1858 /* Explicitly set PWD to prevent problems with chroot builds. In
1739 particular, dietlibc cannot figure out the cwd because the 1859 particular, dietlibc cannot figure out the cwd because the
1740 inode of the current directory doesn't appear in .. (because 1860 inode of the current directory doesn't appear in .. (because
1741 getdents returns the inode of the mount point). */ 1861 getdents returns the inode of the mount point). */
1742 env["PWD"] = tmpDirInSandbox; 1862 ctx.env["PWD"] = tmpDirInSandbox;
1743 1863
1744 /* *Only* if this is a fixed-output derivation, propagate the 1864 /* *Only* if this is a fixed-output derivation, propagate the
1745 values of the environment variables specified in the 1865 values of the environment variables specified in the
@@ -1752,7 +1872,7 @@ void DerivationGoal::startBuilder()
1752 already know the cryptographic hash of the output). */ 1872 already know the cryptographic hash of the output). */
1753 if (fixedOutput) { 1873 if (fixedOutput) {
1754 Strings varNames = tokenizeString<Strings>(get(drv.env, "impureEnvVars")); 1874 Strings varNames = tokenizeString<Strings>(get(drv.env, "impureEnvVars"));
1755 for (auto& i : varNames) env[i] = getEnv(i); 1875 for (auto& i : varNames) ctx.env[i] = getEnv(i);
1756 } 1876 }
1757 1877
1758 /* The `exportReferencesGraph' feature allows the references graph 1878 /* The `exportReferencesGraph' feature allows the references graph
@@ -1816,10 +1936,20 @@ void DerivationGoal::startBuilder()
1816 /* Change ownership of the temporary build directory. */ 1936 /* Change ownership of the temporary build directory. */
1817 if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1) 1937 if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1)
1818 throw SysError(format("cannot change ownership of '%1%'") % tmpDir); 1938 throw SysError(format("cannot change ownership of '%1%'") % tmpDir);
1939
1940 ctx.setuid = true;
1941 ctx.user = buildUser.getUID();
1942 ctx.setgid = true;
1943 ctx.group = buildUser.getGID();
1944 ctx.setSupplementaryGroups = true;
1945 ctx.supplementaryGroups = buildUser.getSupplementaryGIDs();
1819 } 1946 }
1820 1947
1821 if (useChroot) { 1948 if (useChroot) {
1822#if CHROOT_ENABLED 1949#if CHROOT_ENABLED
1950 ctx.phases = getCloneSpawnPhases();
1951 addPhaseAfter(ctx.phases, "chroot", "clearRootWritePerms",
1952 clearRootWritePermsAction);
1823 /* Create a temporary directory in which we set up the chroot 1953 /* Create a temporary directory in which we set up the chroot
1824 environment using bind-mounts. Put it in the store to ensure it 1954 environment using bind-mounts. Put it in the store to ensure it
1825 can be atomically moved to the store. */ 1955 can be atomically moved to the store. */
@@ -1830,6 +1960,22 @@ void DerivationGoal::startBuilder()
1830 /* Clean up the chroot directory automatically. */ 1960 /* Clean up the chroot directory automatically. */
1831 autoDelChroot = std::shared_ptr<AutoDelete>(new AutoDelete(chrootRootTop)); 1961 autoDelChroot = std::shared_ptr<AutoDelete>(new AutoDelete(chrootRootTop));
1832 1962
1963 ctx.doChroot = true;
1964 ctx.chrootRootDir = chrootRootDir;
1965 ctx.cloneFlags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD;
1966
1967 if(!fixedOutput) {
1968 ctx.initLoopback = true;
1969 ctx.cloneFlags |= CLONE_NEWNET;
1970 }
1971
1972 if(!buildUser.enabled())
1973 ctx.cloneFlags |= CLONE_NEWUSER;
1974
1975 /* Set the hostname etc. to fixed values. */
1976 ctx.hostname = "localhost";
1977 ctx.domainname = "(none)"; /* kernel default */
1978
1833 printMsg(lvlChatty, format("setting up chroot environment in `%1%'") % chrootRootDir); 1979 printMsg(lvlChatty, format("setting up chroot environment in `%1%'") % chrootRootDir);
1834 1980
1835 if (mkdir(chrootRootTop.c_str(), 0750) == -1) 1981 if (mkdir(chrootRootTop.c_str(), 0750) == -1)
@@ -1865,9 +2011,21 @@ void DerivationGoal::startBuilder()
1865 (format("nixbld:!:%1%:\n") 2011 (format("nixbld:!:%1%:\n")
1866 % (buildUser.enabled() ? buildUser.getGID() : guestGID)).str()); 2012 % (buildUser.enabled() ? buildUser.getGID() : guestGID)).str());
1867 2013
1868 /* Create /etc/hosts with localhost entry. */ 2014 if (fixedOutput) {
1869 if (!fixedOutput) 2015 /* Fixed-output derivations typically need to access the network,
2016 so give them access to /etc/resolv.conf and so on. */
2017 auto files = { "/etc/resolv.conf", "/etc/nsswitch.conf",
2018 "/etc/services", "/etc/hosts" };
2019 for (auto & file: files) {
2020 if (pathExists(file)) {
2021 ctx.filesInChroot[file] = file;
2022 ctx.readOnlyFilesInChroot.insert(file);
2023 }
2024 }
2025 } else {
2026 /* Create /etc/hosts with localhost entry. */
1870 writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n"); 2027 writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n");
2028 }
1871 2029
1872 /* Bind-mount a user-configurable set of directories from the 2030 /* Bind-mount a user-configurable set of directories from the
1873 host file system. */ 2031 host file system. */
@@ -1877,11 +2035,11 @@ void DerivationGoal::startBuilder()
1877 for (auto & i : dirs) { 2035 for (auto & i : dirs) {
1878 size_t p = i.find('='); 2036 size_t p = i.find('=');
1879 if (p == string::npos) 2037 if (p == string::npos)
1880 dirsInChroot[i] = i; 2038 ctx.filesInChroot[i] = i;
1881 else 2039 else
1882 dirsInChroot[string(i, 0, p)] = string(i, p + 1); 2040 ctx.filesInChroot[string(i, 0, p)] = string(i, p + 1);
1883 } 2041 }
1884 dirsInChroot[tmpDirInSandbox] = tmpDir; 2042 ctx.filesInChroot[tmpDirInSandbox] = tmpDir;
1885 2043
1886 /* Create the fake store. */ 2044 /* Create the fake store. */
1887 Path chrootStoreDir = chrootRootDir + settings.nixStore; 2045 Path chrootStoreDir = chrootRootDir + settings.nixStore;
@@ -1897,22 +2055,8 @@ void DerivationGoal::startBuilder()
1897 the whole store. This prevents any access to undeclared 2055 the whole store. This prevents any access to undeclared
1898 dependencies. */ 2056 dependencies. */
1899 for (auto& i : inputPaths) { 2057 for (auto& i : inputPaths) {
1900 struct stat st; 2058 ctx.filesInChroot[i] = i;
1901 if (lstat(i.c_str(), &st)) 2059 ctx.readOnlyFilesInChroot.insert(i);
1902 throw SysError(format("getting attributes of path `%1%'") % i);
1903
1904 if (S_ISLNK(st.st_mode)) {
1905 /* Since bind-mounts follow symlinks, thus representing their
1906 target and not the symlink itself, special-case
1907 symlinks. XXX: When running unprivileged, TARGET can be
1908 deleted by the build process. Use 'open_tree' & co. when
1909 it's more widely available. */
1910 Path target = chrootRootDir + i;
1911 if (symlink(readLink(i).c_str(), target.c_str()) == -1)
1912 throw SysError(format("failed to create symlink '%1%' to '%2%'") % target % readLink(i));
1913 }
1914 else
1915 dirsInChroot[i] = i;
1916 } 2060 }
1917 2061
1918 /* If we're repairing, checking or rebuilding part of a 2062 /* If we're repairing, checking or rebuilding part of a
@@ -1921,14 +2065,56 @@ void DerivationGoal::startBuilder()
1921 (typically the dependencies of /bin/sh). Throw them 2065 (typically the dependencies of /bin/sh). Throw them
1922 out. */ 2066 out. */
1923 for (auto & i : drv.outputs) 2067 for (auto & i : drv.outputs)
1924 dirsInChroot.erase(i.second.path); 2068 ctx.filesInChroot.erase(i.second.path);
2069
2070 /* Set up a nearly empty /dev, unless the user asked to bind-mount the
2071 host /dev. */
2072 Strings ss;
2073 if(ctx.filesInChroot.find("/dev") == ctx.filesInChroot.end()) {
2074 createDirs(chrootRootDir + "/dev/shm");
2075 createDirs(chrootRootDir + "/dev/pts");
2076 ss.push_back("/dev/full");
2077#ifdef __linux__
2078 if (pathExists("/dev/kvm"))
2079 ss.push_back("/dev/kvm");
2080#endif
2081 ss.push_back("/dev/null");
2082 ss.push_back("/dev/random");
2083 ss.push_back("/dev/tty");
2084 ss.push_back("/dev/urandom");
2085 ss.push_back("/dev/zero");
2086 createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd");
2087 createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin");
2088 createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout");
2089 createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr");
2090 }
1925 2091
2092 for (auto & i : ss) ctx.filesInChroot[i] = i;
2093
2094 ctx.mountProc = true;
2095 ctx.mountDevshm = true;
2096 /* Mount a new devpts on /dev/pts. Note that this requires the kernel
2097 to be compiled with CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is
2098 the case if /dev/ptx/ptmx exists). */
2099 ctx.maybeMountDevpts =
2100 pathExists("/dev/pts/ptmx") &&
2101 ctx.filesInChroot.find("/dev/pts") == ctx.filesInChroot.end();
2102 ctx.lockMounts = !buildUser.enabled();
2103
2104 for (auto & i : ctx.filesInChroot) {
2105 /* Failsafe: If the source is in the store, it should be
2106 read-only */
2107 if(i.second.compare(0, settings.nixStore.length(), settings.nixStore) == 0) {
2108 ctx.readOnlyFilesInChroot.insert(i.first);
2109 }
2110 }
1926#else 2111#else
1927 throw Error("chroot builds are not supported on this platform"); 2112 throw Error("chroot builds are not supported on this platform");
1928#endif 2113#endif
1929 } 2114 }
1930 2115
1931 else { 2116 else {
2117 ctx.phases = getBasicSpawnPhases();
1932 2118
1933 if (pathExists(homeDir)) 2119 if (pathExists(homeDir))
1934 throw Error(format("directory `%1%' exists; please remove it") % homeDir); 2120 throw Error(format("directory `%1%' exists; please remove it") % homeDir);
@@ -1956,6 +2142,7 @@ void DerivationGoal::startBuilder()
1956 } 2142 }
1957 } 2143 }
1958 2144
2145 replacePhase(ctx.phases, "exec", execBuilderOrBuiltinAction);
1959 2146
1960 /* Run the builder. */ 2147 /* Run the builder. */
1961 printMsg(lvlChatty, format("executing builder `%1%'") % drv.builder); 2148 printMsg(lvlChatty, format("executing builder `%1%'") % drv.builder);
@@ -1966,6 +2153,9 @@ void DerivationGoal::startBuilder()
1966 /* Create a pipe to get the output of the builder. */ 2153 /* Create a pipe to get the output of the builder. */
1967 builderOut.create(); 2154 builderOut.create();
1968 2155
2156 ctx.logFD = builderOut.writeSide;
2157 ctx.earlyCloseFDs.insert(builderOut.readSide);
2158
1969 /* Fork a child to build the package. Note that while we 2159 /* Fork a child to build the package. Note that while we
1970 currently use forks to run and wait for the children, it 2160 currently use forks to run and wait for the children, it
1971 shouldn't be hard to use threads for this on systems where 2161 shouldn't be hard to use threads for this on systems where
@@ -1997,43 +2187,35 @@ void DerivationGoal::startBuilder()
1997 */ 2187 */
1998#if __linux__ 2188#if __linux__
1999 if (useChroot) { 2189 if (useChroot) {
2000 char stack[32 * 1024]; 2190 int fds[2];
2001 int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD; 2191 AutoCloseFD parentSetupSocket;
2002 if (!fixedOutput) { 2192 AutoCloseFD childSetupSocket;
2003 flags |= CLONE_NEWNET; 2193
2004 } 2194 if(((ctx.cloneFlags & CLONE_NEWUSER) != 0)) {
2005 if (!buildUser.enabled() || getuid() != 0) { 2195 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds))
2006 flags |= CLONE_NEWUSER; 2196 throw SysError("creating setup socket");
2007 readiness.create(); 2197 parentSetupSocket = fds[0];
2008 } 2198 closeOnExec(parentSetupSocket);
2199 ctx.earlyCloseFDs.insert(parentSetupSocket);
2200 childSetupSocket = fds[1];
2201 closeOnExec(childSetupSocket);
2202 ctx.setupFD = childSetupSocket;
2203 }
2009 2204
2010 /* Ensure proper alignment on the stack. On aarch64, it has to be 16 2205 pid = cloneChild(ctx);
2011 bytes. */
2012 pid = clone(childEntry,
2013 (char *)(((uintptr_t)stack + sizeof(stack) - 8) & ~(uintptr_t)0xf),
2014 flags, this);
2015 if (pid == -1) {
2016 if ((flags & CLONE_NEWUSER) != 0 && getuid() != 0)
2017 /* 'clone' fails with EPERM on distros where unprivileged user
2018 namespaces are disabled. Error out instead of giving up on
2019 isolation. */
2020 throw SysError("cannot create process in unprivileged user namespace");
2021 else
2022 throw SysError("cloning builder process");
2023 }
2024 2206
2025 readiness.readSide.close(); 2207 if(childSetupSocket >= 0) childSetupSocket.close();
2026 if ((flags & CLONE_NEWUSER) != 0) { 2208
2027 /* Initialize the UID/GID mapping of the child process. */ 2209 if ((ctx.cloneFlags & CLONE_NEWUSER) != 0) {
2028 initializeUserNamespace(pid); 2210 /* Initialize the UID/GID mapping of the builder. */
2029 writeFull(readiness.writeSide, (unsigned char*)"go\n", 3); 2211 initializeUserNamespace(pid);
2030 } 2212 writeFull(parentSetupSocket, (unsigned char*)"go\n", 3);
2031 readiness.writeSide.close(); 2213 }
2032 } else 2214 } else
2033#endif 2215#endif
2034 { 2216 {
2035 pid = fork(); 2217 pid = fork();
2036 if (pid == 0) runChild(); 2218 if (pid == 0) runChildSetup(ctx);
2037 } 2219 }
2038 2220
2039 if (pid == -1) throw SysError("unable to fork"); 2221 if (pid == -1) throw SysError("unable to fork");
@@ -2049,415 +2231,9 @@ void DerivationGoal::startBuilder()
2049 2231
2050 if (settings.printBuildTrace) { 2232 if (settings.printBuildTrace) {
2051 printMsg(lvlError, format("@ build-started %1% - %2% %3% %4%") 2233 printMsg(lvlError, format("@ build-started %1% - %2% %3% %4%")
2052 % drvPath % drv.platform % logFile % pid); 2234 % drvPath % drv.platform % logFile % pid);
2053 }
2054
2055}
2056
2057/* Return true if the operating system kernel part of SYSTEM1 and SYSTEM2 (the
2058 bit that comes after the hyphen in system types such as "i686-linux") is
2059 the same. */
2060static bool sameOperatingSystemKernel(const std::string& system1, const std::string& system2)
2061{
2062 auto os1 = system1.substr(system1.find("-"));
2063 auto os2 = system2.substr(system2.find("-"));
2064 return os1 == os2;
2065}
2066
2067void DerivationGoal::runChild()
2068{
2069 /* Warning: in the child we should absolutely not make any SQLite
2070 calls! */
2071
2072 try { /* child */
2073
2074 _writeToStderr = 0;
2075
2076 if (readiness.writeSide >= 0) readiness.writeSide.close();
2077
2078 if (readiness.readSide >= 0) {
2079 /* Wait for the parent process to initialize the UID/GID mapping
2080 of our user namespace. */
2081 char str[20] = { '\0' };
2082 readFull(readiness.readSide, (unsigned char*)str, 3);
2083 readiness.readSide.close();
2084 if (strcmp(str, "go\n") != 0)
2085 throw Error("failed to initialize process in unprivileged user namespace");
2086 }
2087
2088 restoreAffinity();
2089
2090 commonChildInit(builderOut);
2091
2092#if CHROOT_ENABLED
2093 if (useChroot) {
2094# if HAVE_SYS_PRCTL_H
2095 /* Drop ambient capabilities such as CAP_CHOWN that might have
2096 been granted when starting guix-daemon. */
2097 prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
2098# endif
2099
2100 if (!fixedOutput) {
2101 /* Initialise the loopback interface. */
2102 AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
2103 if (fd == -1) throw SysError("cannot open IP socket");
2104
2105 struct ifreq ifr;
2106 strcpy(ifr.ifr_name, "lo");
2107 ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
2108 if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1)
2109 throw SysError("cannot set loopback interface flags");
2110
2111 fd.close();
2112 }
2113
2114 /* Set the hostname etc. to fixed values. */
2115 char hostname[] = "localhost";
2116 if (sethostname(hostname, sizeof(hostname)) == -1)
2117 throw SysError("cannot set host name");
2118 char domainname[] = "(none)"; // kernel default
2119 if (setdomainname(domainname, sizeof(domainname)) == -1)
2120 throw SysError("cannot set domain name");
2121
2122 /* Make all filesystems private. This is necessary
2123 because subtrees may have been mounted as "shared"
2124 (MS_SHARED). (Systemd does this, for instance.) Even
2125 though we have a private mount namespace, mounting
2126 filesystems on top of a shared subtree still propagates
2127 outside of the namespace. Making a subtree private is
2128 local to the namespace, though, so setting MS_PRIVATE
2129 does not affect the outside world. */
2130 if (mount(0, "/", 0, MS_REC|MS_PRIVATE, 0) == -1) {
2131 throw SysError("unable to make ‘/’ private mount");
2132 }
2133
2134 /* Bind-mount chroot directory to itself, to treat it as a
2135 different filesystem from /, as needed for pivot_root. */
2136 if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
2137 throw SysError(format("unable to bind mount ‘%1%’") % chrootRootDir);
2138
2139 /* Set up a nearly empty /dev, unless the user asked to
2140 bind-mount the host /dev. */
2141 Strings ss;
2142 if (dirsInChroot.find("/dev") == dirsInChroot.end()) {
2143 createDirs(chrootRootDir + "/dev/shm");
2144 createDirs(chrootRootDir + "/dev/pts");
2145 ss.push_back("/dev/full");
2146#ifdef __linux__
2147 if (pathExists("/dev/kvm"))
2148 ss.push_back("/dev/kvm");
2149#endif
2150 ss.push_back("/dev/null");
2151 ss.push_back("/dev/random");
2152 ss.push_back("/dev/tty");
2153 ss.push_back("/dev/urandom");
2154 ss.push_back("/dev/zero");
2155 createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd");
2156 createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin");
2157 createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout");
2158 createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr");
2159 }
2160
2161 /* Fixed-output derivations typically need to access the
2162 network, so give them access to /etc/resolv.conf and so
2163 on. */
2164 if (fixedOutput) {
2165 auto files = { "/etc/resolv.conf", "/etc/nsswitch.conf",
2166 "/etc/services", "/etc/hosts" };
2167 for (auto & file: files) {
2168 if (pathExists(file)) ss.push_back(file);
2169 }
2170 }
2171
2172 for (auto & i : ss) dirsInChroot[i] = i;
2173
2174 /* Make new mounts for the store and for /tmp. That way, when
2175 'chrootRootDir' is made read-only below, these two mounts will
2176 remain writable (the store needs to be writable so derivation
2177 outputs can be written to it, and /tmp is writable by
2178 convention). */
2179 auto chrootStoreDir = chrootRootDir + settings.nixStore;
2180 if (mount(chrootStoreDir.c_str(), chrootStoreDir.c_str(), 0, MS_BIND, 0) == -1)
2181 throw SysError(format("read-write mount of store '%1%' failed") % chrootStoreDir);
2182 auto chrootTmpDir = chrootRootDir + "/tmp";
2183 if (mount(chrootTmpDir.c_str(), chrootTmpDir.c_str(), 0, MS_BIND, 0) == -1)
2184 throw SysError(format("read-write mount of temporary directory '%1%' failed") % chrootTmpDir);
2185
2186 /* Bind-mount all the directories from the "host"
2187 filesystem that we want in the chroot
2188 environment. */
2189 for (auto& i : dirsInChroot) {
2190 struct stat st;
2191 Path source = i.second;
2192 Path target = chrootRootDir + i.first;
2193 if (source == "/proc") continue; // backwards compatibility
2194 if (stat(source.c_str(), &st) == -1)
2195 throw SysError(format("getting attributes of path `%1%'") % source);
2196 if (S_ISDIR(st.st_mode))
2197 createDirs(target);
2198 else {
2199 createDirs(dirOf(target));
2200 writeFile(target, "");
2201 }
2202
2203 /* Extra flags passed with MS_BIND are ignored, hence the
2204 extra MS_REMOUNT. */
2205 if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1)
2206 throw SysError(format("bind mount from `%1%' to `%2%' failed") % source % target);
2207 if (source.compare(0, settings.nixStore.length(), settings.nixStore) == 0) {
2208 if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REMOUNT | MS_RDONLY, 0) == -1)
2209 throw SysError(format("read-only remount of `%1%' failed") % target);
2210 }
2211 }
2212
2213 /* Bind a new instance of procfs on /proc to reflect our
2214 private PID namespace. */
2215 createDirs(chrootRootDir + "/proc");
2216 if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1)
2217 throw SysError("mounting /proc");
2218
2219 /* Mount a new tmpfs on /dev/shm to ensure that whatever
2220 the builder puts in /dev/shm is cleaned up automatically. */
2221 if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0, 0) == -1)
2222 throw SysError("mounting /dev/shm");
2223
2224 /* Mount a new devpts on /dev/pts. Note that this
2225 requires the kernel to be compiled with
2226 CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
2227 if /dev/ptx/ptmx exists). */
2228 if (pathExists("/dev/pts/ptmx") &&
2229 !pathExists(chrootRootDir + "/dev/ptmx")
2230 && dirsInChroot.find("/dev/pts") == dirsInChroot.end())
2231 {
2232 if (mount("none", (chrootRootDir + "/dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == -1)
2233 throw SysError("mounting /dev/pts");
2234 createSymlink("/dev/pts/ptmx", chrootRootDir + "/dev/ptmx");
2235
2236 /* Make sure /dev/pts/ptmx is world-writable. With some
2237 Linux versions, it is created with permissions 0. */
2238 chmod_(chrootRootDir + "/dev/pts/ptmx", 0666);
2239 }
2240
2241 /* Do the chroot(). */
2242 if (chdir(chrootRootDir.c_str()) == -1)
2243 throw SysError(format("cannot change directory to '%1%'") % chrootRootDir);
2244
2245 if (mkdir("real-root", 0) == -1)
2246 throw SysError("cannot create real-root directory");
2247
2248 if (pivot_root(".", "real-root") == -1)
2249 throw SysError(format("cannot pivot old root directory onto '%1%'") % (chrootRootDir + "/real-root"));
2250
2251 if (chroot(".") == -1)
2252 throw SysError(format("cannot change root directory to '%1%'") % chrootRootDir);
2253
2254 if (umount2("real-root", MNT_DETACH) == -1)
2255 throw SysError("cannot unmount real root filesystem");
2256
2257 if (rmdir("real-root") == -1)
2258 throw SysError("cannot remove real-root directory");
2259
2260 /* Make the root read-only.
2261
2262 When build users are disabled, the build process could make it
2263 world-accessible, but that's OK: since 'chrootRootTop' is *not*
2264 world-accessible, a world-accessible 'chrootRootDir' cannot be
2265 used to grant access to the build environment to external
2266 processes.
2267
2268 Remounting the root as read-only was rejected because it makes
2269 write access fail with EROFS instead of EACCES, which goes
2270 against what some test suites expect (Go, Ruby, SCons,
2271 Shepherd, to name a few). */
2272 chmod_("/", 0555);
2273
2274 if (getuid() != 0) {
2275 /* Create a new mount namespace to "lock" previous mounts.
2276 See mount_namespaces(7). */
2277 auto uid = getuid();
2278 auto gid = getgid();
2279
2280 if (unshare(CLONE_NEWNS | CLONE_NEWUSER) == -1)
2281 throw SysError(format("creating new user and mount namespaces"));
2282
2283 initializeUserNamespace(getpid(), uid, gid);
2284
2285 /* Check that mounts within the build environment are "locked"
2286 together and cannot be separated from within the build
2287 environment namespace. Since
2288 umount(2) is documented to fail with EINVAL when attempting
2289 to unmount one of the mounts that are locked together,
2290 check that this is what we get. */
2291 int ret = umount(tmpDirInSandbox.c_str());
2292 assert(ret == -1 && errno == EINVAL);
2293 }
2294 }
2295#endif
2296
2297 if (chdir(tmpDirInSandbox.c_str()) == -1)
2298 throw SysError(format("changing into `%1%'") % tmpDir);
2299
2300 /* Close all other file descriptors. */
2301 closeMostFDs(set<int>());
2302
2303#if __linux__
2304 /* Change the personality to 32-bit if we're doing an
2305 i686-linux build on an x86_64-linux machine. */
2306 struct utsname utsbuf;
2307 uname(&utsbuf);
2308 if (drv.platform == "i686-linux" &&
2309 (settings.thisSystem == "x86_64-linux" ||
2310 (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) {
2311 if (personality(PER_LINUX32) == -1)
2312 throw SysError("cannot set i686-linux personality");
2313 }
2314
2315 if (drv.platform == "armhf-linux" &&
2316 (settings.thisSystem == "aarch64-linux" ||
2317 (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "aarch64")))) {
2318 if (personality(PER_LINUX32) == -1)
2319 throw SysError("cannot set armhf-linux personality");
2320 }
2321
2322 /* Impersonate a Linux 2.6 machine to get some determinism in
2323 builds that depend on the kernel version. */
2324 if ((drv.platform == "i686-linux" || drv.platform == "x86_64-linux") && settings.impersonateLinux26) {
2325 int cur = personality(0xffffffff);
2326 if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
2327 }
2328
2329 /* Disable address space randomization for improved
2330 determinism. */
2331 int cur = personality(0xffffffff);
2332 if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
2333
2334 /* Ask the kernel to eagerly kill us & our children if it runs out of
2335 memory, regardless of blame, to preserve ‘real’ user data & state. */
2336 try {
2337 writeFile("/proc/self/oom_score_adj", "1000"); // 100%
2338 } catch (...) { ignoreException(); }
2339#endif
2340
2341 /* Fill in the environment. */
2342 Strings envStrs;
2343 for (const auto& i : env)
2344 envStrs.push_back(rewriteHashes(i.first + "=" + i.second, rewritesToTmp));
2345
2346 /* If we are running in `build-users' mode, then switch to the
2347 user we allocated above. Make sure that we drop all root
2348 privileges. Note that above we have closed all file
2349 descriptors except std*, so that's safe. Also note that
2350 setuid() when run as root sets the real, effective and
2351 saved UIDs. */
2352 if (buildUser.enabled()) {
2353 /* Preserve supplementary groups of the build user, to allow
2354 admins to specify groups such as "kvm". */
2355 if (setgroups(buildUser.getSupplementaryGIDs().size(),
2356 buildUser.getSupplementaryGIDs().data()) == -1)
2357 throw SysError("cannot set supplementary groups of build user");
2358
2359 if (setgid(buildUser.getGID()) == -1 ||
2360 getgid() != buildUser.getGID() ||
2361 getegid() != buildUser.getGID())
2362 throw SysError("setgid failed");
2363
2364 if (setuid(buildUser.getUID()) == -1 ||
2365 getuid() != buildUser.getUID() ||
2366 geteuid() != buildUser.getUID())
2367 throw SysError("setuid failed");
2368 }
2369
2370 restoreSIGPIPE();
2371
2372 /* Indicate that we managed to set up the build environment. */
2373 writeFull(STDERR_FILENO, "\n");
2374
2375 /* Execute the program. This should not return. */
2376 string builderBasename;
2377 if (isBuiltin(drv)) {
2378 try {
2379 logType = ltFlat;
2380
2381 auto buildDrv = lookupBuiltinBuilder(drv.builder);
2382 if (buildDrv != NULL) {
2383 /* Check what the output file name is. When doing a
2384 'bmCheck' build, the output file name is different from
2385 that specified in DRV due to hash rewriting. */
2386 Path output = drv.outputs["out"].path;
2387 auto redirected = redirectedOutputs.find(output);
2388 if (redirected != redirectedOutputs.end())
2389 output = redirected->second;
2390
2391 buildDrv(drv, drvPath, output);
2392 }
2393 else
2394 throw Error(format("unsupported builtin function '%1%'") % string(drv.builder, 8));
2395 _exit(0);
2396 } catch (std::exception & e) {
2397 writeFull(STDERR_FILENO, "error: " + string(e.what()) + "\n");
2398 _exit(1);
2399 }
2400 } else {
2401 /* Ensure that the builder is within the store. This prevents
2402 users from using /proc/self/exe (or a symlink to it) as their
2403 builder, which could allow them to overwrite the guix-daemon
2404 binary (CVE-2019-5736).
2405
2406 This attack is possible even if the target of /proc/self/exe is
2407 outside the chroot (it's as if it were a hard link), though it
2408 requires that its ELF interpreter and dependencies be in the
2409 chroot.
2410
2411 Note: 'canonPath' throws if 'drv.builder' cannot be resolved
2412 within the chroot. */
2413 builderBasename = baseNameOf(drv.builder);
2414 drv.builder = canonPath(drv.builder, true);
2415
2416 if (!isInStore(drv.builder))
2417 throw Error(format("derivation builder '%1%' is outside the store") % drv.builder);
2418 }
2419
2420 /* Fill in the arguments. */
2421 Strings args;
2422 args.push_back(builderBasename);
2423 for (auto& i : drv.args)
2424 args.push_back(rewriteHashes(i, rewritesToTmp));
2425
2426 /* If DRV targets the same operating system kernel, try to execute it:
2427 there might be binfmt_misc set up for user-land emulation of other
2428 architectures. However, if it targets a different operating
2429 system--e.g., "i586-gnu" vs. "x86_64-linux"--do not try executing
2430 it: the ELF file for that OS is likely indistinguishable from a
2431 native ELF binary and it would just crash at run time. */
2432 int error;
2433 if (sameOperatingSystemKernel(drv.platform, settings.thisSystem)) {
2434 execve(drv.builder.c_str(), stringsToCharPtrs(args).data(),
2435 stringsToCharPtrs(envStrs).data());
2436 error = errno;
2437 } else {
2438 error = ENOEXEC;
2439 }
2440
2441 /* Right platform? Check this after we've tried 'execve' to allow for
2442 transparent emulation of different platforms with binfmt_misc
2443 handlers that invoke QEMU. */
2444 if (error == ENOEXEC && !canBuildLocally(drv.platform)) {
2445 if (settings.printBuildTrace)
2446 printMsg(lvlError, format("@ unsupported-platform %1% %2%") % drvPath % drv.platform);
2447 throw Error(
2448 format("a `%1%' is required to build `%3%', but I am a `%2%'")
2449 % drv.platform % settings.thisSystem % drvPath);
2450 }
2451
2452 errno = error;
2453 throw SysError(format("executing `%1%'") % drv.builder);
2454
2455 } catch (std::exception & e) {
2456 writeFull(STDERR_FILENO, "while setting up the build environment: " + string(e.what()) + "\n");
2457 _exit(1);
2458 } 2235 }
2459 2236
2460 abort(); /* never reached */
2461} 2237}
2462 2238
2463 2239
diff --git a/nix/libutil/spawn.cc b/nix/libutil/spawn.cc
new file mode 100644
index 00000000000..93bab9f59e4
--- /dev/null
+++ b/nix/libutil/spawn.cc
@@ -0,0 +1,829 @@
1/* GNU Guix --- Functional package management for GNU
2 Copyright (C) 2025 Caleb Ristvedt <reepca@russelstein.xyz>
3
4 This file is part of GNU Guix.
5
6 GNU Guix is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or (at
9 your option) any later version.
10
11 GNU Guix is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. */
18
19/* Process spawning and setup code. */
20
21#include <spawn.hh>
22#include <util.hh>
23#include <affinity.hh>
24#include <stddef.h>
25#include <unistd.h>
26#include <grp.h>
27#include <limits.h>
28#include <sys/wait.h>
29#include <cstring>
30#include <cstdlib>
31
32#if HAVE_SYS_MOUNT_H
33#include <sys/mount.h>
34#endif
35
36#if HAVE_SCHED_H
37#include <sched.h>
38#endif
39
40#if HAVE_STATVFS
41#include <sys/statvfs.h>
42#endif
43
44#if HAVE_SYS_SYSCALL_H
45#include <sys/syscall.h>
46#endif
47
48#if HAVE_SYS_PRCTL_H
49#include <sys/prctl.h>
50#endif
51
52#ifdef __linux__
53#include <sys/personality.h>
54#endif
55
56#if defined(SYS_pivot_root)
57#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root,put_old))
58#endif
59
60
61#define CLONE_ENABLED defined(CLONE_NEWNS)
62
63#if CLONE_ENABLED
64#include <sys/ioctl.h>
65#include <net/if.h>
66#include <netinet/in.h>
67#endif
68
69namespace nix {
70
71
72void addPhaseAfter(Phases & phases, string afterLabel, string addLabel, Action addAction)
73{
74 for(auto i = phases.begin(); i != phases.end(); i++)
75 if((*i).label == afterLabel) {
76 i++; /* std::vector::insert inserts before, not after */
77 Phase p;
78 p.label = addLabel;
79 p.action = addAction;
80 phases.insert(i, p);
81 return;
82 }
83 throw Error(format("label `%1%' not found in phases") % afterLabel);
84}
85
86
87void addPhaseBefore(Phases & phases, string beforeLabel, string addLabel, Action addAction)
88{
89 for(auto i = phases.begin(); i != phases.end(); i++)
90 if((*i).label == beforeLabel) {
91 Phase p;
92 p.label = addLabel;
93 p.action = addAction;
94 phases.insert(i, p);
95 return;
96 }
97 throw Error(format("label `%1%' not found in phases") % beforeLabel);
98}
99
100
101void prependPhase(Phases & phases, string addLabel, Action addAction)
102{
103 Phase p;
104 p.label = addLabel;
105 p.action = addAction;
106 phases.insert(phases.begin(), p);
107}
108
109
110void appendPhase(Phases & phases, string addLabel, Action addAction)
111{
112 Phase p;
113 p.label = addLabel;
114 p.action = addAction;
115 phases.push_back(p);
116}
117
118
119void deletePhase(Phases & phases, string delLabel)
120{
121 for(auto i = phases.begin(); i != phases.end(); i++)
122 if((*i).label == delLabel) {
123 phases.erase(i);
124 return;
125 }
126 throw Error(format("label `%1%' not found in phases") % delLabel);
127}
128
129
130void replacePhase(Phases & phases, string replaceLabel, Action newAction)
131{
132 for(auto i = phases.begin(); i != phases.end(); i++)
133 if((*i).label == replaceLabel) {
134 (*i).action = newAction;
135 return;
136 }
137 throw Error(format("label `%1' not found in phases") % replaceLabel);
138}
139
140
141/* A curated selection of predefined actions */
142
143void reset_writeToStderrAction(SpawnContext & ctx)
144{
145 _writeToStderr = 0;
146}
147
148
149void restoreAffinityAction(SpawnContext & ctx)
150{
151 restoreAffinity();
152}
153
154
155void setsidAction(SpawnContext & ctx)
156{
157 /* Puts the current process in a separate session, which implies a
158 separate process group, so it doesn't receive group-directed signals
159 sent at the parent. The new session initially has no controlling
160 terminal, so it also doesn't receive terminal signals and can't open
161 /dev/tty. */
162 if(ctx.setsid && setsid() == (pid_t)-1)
163 throw SysError("creating a new session");
164}
165
166
167void earlyIOSetupAction(SpawnContext & ctx)
168{
169 for(auto i = ctx.earlyCloseFDs.begin(); i != ctx.earlyCloseFDs.end(); i++)
170 if(close(*i) == -1)
171 throw SysError("closing fd");
172
173 if(ctx.logFD != -1) {
174 if(dup2(ctx.logFD, STDOUT_FILENO) == -1)
175 throw SysError("cannot dup2 log fd into stdout fd");
176 if(dup2(ctx.logFD, STDERR_FILENO) == -1)
177 throw SysError("cannot dup2 log fd into stderr fd");
178 }
179
180 if(ctx.setStdin) {
181 if(ctx.stdinFD != -1) {
182 if(dup2(ctx.stdinFD, STDIN_FILENO) == -1)
183 throw SysError("cannot dup2 fd into stdin fd");
184 }
185 else {
186 /* Doesn't make sense for it to be writable, but compatibility... */
187 AutoCloseFD fd = open(ctx.stdinFile.c_str(), O_RDWR);
188 if(fd == -1)
189 throw SysError(format("cannot open `%1%'") % ctx.stdinFile);
190 if(dup2(fd, STDIN_FILENO) == -1)
191 throw SysError("cannot dup2 fd into stdin fd");
192 }
193 }
194}
195
196
197void dropAmbientCapabilitiesAction(SpawnContext & ctx)
198{
199 /* Drop ambient capabilities such as CAP_CHOWN that might have been granted
200 when starting guix-daemon. */
201 if(ctx.dropAmbientCapabilities)
202#if HAVE_SYS_PRCTL_H
203 prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
204#else
205 throw Error("dropping ambient capabilities is not supported on this system");
206#endif
207}
208
209
210void chrootAction(SpawnContext & ctx)
211{
212 if(ctx.doChroot)
213#if HAVE_CHROOT
214 if(chroot(ctx.chrootRootDir.c_str()) == -1)
215 throw SysError(format("cannot change root directory to '%1%'") % ctx.chrootRootDir);
216#else
217 throw Error("chroot is not supported on this system");
218#endif
219}
220
221
222void chdirAction(SpawnContext & ctx)
223{
224 if(ctx.setcwd)
225 if(chdir(ctx.cwd.c_str()) == -1)
226 throw SysError(format("changing into `%1%'") % ctx.cwd);
227}
228
229
230void closeMostFDsAction(SpawnContext & ctx)
231{
232 if(ctx.closeMostFDs) closeMostFDs(ctx.preserveFDs);
233 for(auto i = ctx.preserveFDs.begin(); i != ctx.preserveFDs.end(); i++)
234 keepOnExec(*i);
235}
236
237
238void setPersonalityAction(SpawnContext & ctx)
239{
240 if(ctx.setPersona)
241#ifdef __linux__
242 if(personality(ctx.persona) == -1)
243 throw SysError("cannot set personality");
244#else
245 throw Error("setting the personality is not supported on this system");
246#endif
247}
248
249
250void oomSacrificeAction(SpawnContext & ctx)
251{
252#ifdef __linux__
253 if(ctx.oomSacrifice)
254 /* Ask the kernel to eagerly kill us & our children if it runs out of
255 memory, regardless of blame, to preserve ‘real’ user data &
256 state. */
257 try {
258 writeFile("/proc/self/oom_score_adj", "1000"); // 100%
259 } catch(...) { ignoreException(); }
260#endif
261}
262
263
264void setIDsAction(SpawnContext & ctx)
265{
266 if(ctx.setSupplementaryGroups)
267 if(setgroups(ctx.supplementaryGroups.size(),
268 ctx.supplementaryGroups.data()) == -1)
269 throw SysError("cannot set supplementary groups");
270
271 if(ctx.setgid)
272 if(setgid(ctx.group) == -1 ||
273 getgid() != ctx.group ||
274 getegid() != ctx.group)
275 throw SysError("setgid failed");
276
277 if(ctx.setuid)
278 if(setuid(ctx.user) == -1 ||
279 getuid() != ctx.user ||
280 geteuid() != ctx.user)
281 throw SysError("setuid failed");
282}
283
284
285void restoreSIGPIPEAction(SpawnContext & ctx)
286{
287 /* Restore default handling of SIGPIPE, otherwise some programs will
288 randomly say "Broken pipe". */
289 struct sigaction act, oact;
290 act.sa_handler = SIG_DFL;
291 act.sa_flags = 0;
292 sigemptyset(&act.sa_mask);
293 if (sigaction(SIGPIPE, &act, &oact)) throw SysError("resetting SIGPIPE");
294}
295
296
297void setupSuccessAction(SpawnContext & ctx)
298{
299 if(ctx.signalSetupSuccess)
300 writeFull(STDERR_FILENO, "\n");
301}
302
303
304void execAction(SpawnContext & ctx)
305{
306 Strings envStrs;
307 std::vector<char *> envPtrs;
308 char **env;
309 if(ctx.inheritEnv) {
310 for(auto i = ctx.env.begin(); i != ctx.env.end(); i++)
311 if(setenv(i->first.c_str(), i->second.c_str(), 1) == -1)
312 throw SysError("setenv");
313 env = environ;
314 } else {
315 for(auto i = ctx.env.begin(); i != ctx.env.end(); i++)
316 envStrs.push_back(i->first + "=" + i->second);
317 /* Need to keep the envPtrs vector alive as long as its .data()! */
318 envPtrs = stringsToCharPtrs(envStrs);
319 env = envPtrs.data();
320 }
321 if(execvpe(ctx.program.c_str(), stringsToCharPtrs(ctx.args).data(), env) == -1)
322 throw SysError(format("executing `%1%'") % ctx.program);
323}
324
325
326Phases getBasicSpawnPhases()
327{
328 return { { "reset_writeToStderr", reset_writeToStderrAction },
329 { "restoreAffinity", restoreAffinityAction },
330 { "setsid", setsidAction },
331 { "earlyIOSetup", earlyIOSetupAction },
332 { "dropAmbientCapabilities", dropAmbientCapabilitiesAction },
333 { "chroot", chrootAction },
334 { "chdir", chdirAction },
335 { "closeMostFDs", closeMostFDsAction },
336 { "setPersonality", setPersonalityAction },
337 { "oomSacrifice", oomSacrificeAction },
338 { "setIDs", setIDsAction },
339 { "restoreSIGPIPE", restoreSIGPIPEAction },
340 { "setupSuccess", setupSuccessAction },
341 { "exec", execAction } };
342}
343
344
345void usernsInitSyncAction(SpawnContext & sctx)
346{
347#if CLONE_ENABLED
348 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
349 if((ctx.cloneFlags & CLONE_NEWUSER) != 0) {
350 /* Close the earlyCloseFDs before we try reading anything */
351 for(auto i = ctx.earlyCloseFDs.begin(); i != ctx.earlyCloseFDs.end(); i++)
352 if(close(*i) == -1)
353 throw SysError("closing fd");
354 /* Don't try closing them again later */
355 ctx.earlyCloseFDs.clear();
356 /* Wait for the parent process to initialize the UID/GID mapping of
357 our user namespace. */
358 waitForMessage(ctx.setupFD, "go\n");
359 }
360#endif
361}
362
363
364void usernsSetIDsAction(SpawnContext & sctx)
365{
366#if CLONE_ENABLED
367 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
368 if((ctx.cloneFlags & CLONE_NEWUSER) != 0) {
369 /* Note: 'man capabilities' says that a transition from zero to
370 nonzero uids causes capabilities to be lost, but doesn't say what
371 happens when a transition from an unmapped (possibly zero) uid to a
372 nonzero uid happens. */
373 if(ctx.usernsSetuid)
374 /* Since we presumably have CAP_SETUID, this sets the real,
375 effective, saved, and filesystem uids */
376 if(setuid(ctx.usernsUser) != 0)
377 throw SysError("setuid");
378 if(ctx.usernsSetgid)
379 /* Ditto but with gids */
380 if(setgid(ctx.usernsGroup) != 0)
381 throw SysError("setgid");
382 }
383#endif
384}
385
386
387void initLoopbackAction(SpawnContext & sctx)
388{
389#if CLONE_ENABLED
390 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
391 if(((ctx.cloneFlags & CLONE_NEWNET) != 0) && ctx.initLoopback) {
392 AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
393 if (fd == -1) throw SysError("cannot open IP socket");
394
395 struct ifreq ifr;
396 strcpy(ifr.ifr_name, "lo");
397 ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
398 if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1)
399 throw SysError("cannot set loopback interface flags");
400
401 fd.close();
402 }
403#endif
404}
405
406
407void setHostAndDomainAction(SpawnContext & sctx)
408{
409#if CLONE_ENABLED
410 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
411 if((ctx.cloneFlags & CLONE_NEWUTS) != 0) {
412 if (sethostname(ctx.hostname.c_str(),
413 strlen(ctx.hostname.c_str())) == -1)
414 throw SysError("cannot set host name");
415 if (setdomainname(ctx.domainname.c_str(),
416 strlen(ctx.domainname.c_str())) == -1)
417 throw SysError("cannot set domain name");
418 }
419#endif
420}
421
422
423void makeFilesystemsPrivateAction(SpawnContext & sctx)
424{
425#if CLONE_ENABLED && HAVE_SYS_MOUNT_H && defined(MS_REC) && defined(MS_PRIVATE)
426 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
427 if((ctx.cloneFlags & CLONE_NEWNS) != 0) {
428 if(mount(0, "/", 0, MS_REC|MS_PRIVATE, 0) == -1)
429 throw SysError("unable to make `/' private mount");
430 }
431#endif
432}
433
434
435void makeChrootSeparateFilesystemAction(SpawnContext & sctx)
436{
437#if CLONE_ENABLED && HAVE_SYS_MOUNT_H && defined(MS_BIND)
438 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
439 if(((ctx.cloneFlags & CLONE_NEWNS) != 0) && ctx.doChroot) {
440 /* Bind-mount chroot directory to itself, to treat it as a different
441 filesystem from /, as needed for pivot_root. Alternatively, mount
442 a tmpfs on it. */
443 if(ctx.mountTmpfsOnChroot) {
444 if(mount("none", ctx.chrootRootDir.c_str(), "tmpfs", 0, 0) == -1)
445 throw SysError(format("unable to mount tmpfs on `%1%'") % ctx.chrootRootDir);
446 }
447 else {
448 if(mount(ctx.chrootRootDir.c_str(), ctx.chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
449 throw SysError(format("unable to bind mount ‘%1%’") % ctx.chrootRootDir);
450 }
451 }
452#endif
453}
454
455
456static int statfsToMountFlags(int f_flags)
457{
458#if HAVE_SYS_MOUNT_H && HAVE_STATVFS
459 int ret = 0;
460#if defined(ST_RDONLY) && defined(MS_RDONLY)
461 if((f_flags & ST_RDONLY) != 0) ret |= MS_RDONLY;
462#endif
463#if defined(ST_NOSUID) && defined(MS_NOSUID)
464 if((f_flags & ST_NOSUID) != 0) ret |= MS_NOSUID;
465#endif
466#if defined(ST_NODEV) && defined(MS_NODEV)
467 if((f_flags & ST_NODEV) != 0) ret |= MS_NODEV;
468#endif
469#if defined(ST_NOEXEC) && defined(MS_NOEXEC)
470 if((f_flags & ST_NOEXEC) != 0) ret |= MS_NOEXEC;
471#endif
472#if defined(ST_NOATIME) && defined(MS_NOATIME)
473 if((f_flags & ST_NOATIME) != 0) ret |= MS_NOATIME;
474#endif
475#if defined(ST_NODIRATIME) && defined(MS_NODIRATIME)
476 if((f_flags & ST_NODIRATIME) != 0) ret |= MS_NODIRATIME;
477#endif
478#if defined(ST_RELATIME) && defined(MS_RELATIME)
479 if((f_flags & ST_RELATIME) != 0) ret |= MS_RELATIME;
480#endif
481 return ret;
482#else
483 throw Error("statfsToMountFlags not supported on this platform");
484#endif
485}
486
487
488void bindMount(Path source, Path target, bool readOnly)
489{
490#if HAVE_SYS_MOUNT_H && defined(MS_BIND)
491 struct stat st;
492 if (lstat(source.c_str(), &st) == -1)
493 throw SysError(format("getting attributes of path `%1%'") % source);
494
495 if(S_ISDIR(st.st_mode))
496 createDirs(target);
497 else if(S_ISLNK(st.st_mode)) {
498 /* bind-mounts follow symlinks, thus representing their target and not
499 the symlink itself. Create a copy of the symlink instead.*/
500 createDirs(dirOf(target));
501 createSymlink(readLink(source), target);
502 return;
503 }
504 else {
505 createDirs(dirOf(target));
506 writeFile(target, "");
507 }
508
509 /* This may fail with EINVAL unless we specify MS_REC, specifically if we
510 are in an unprivileged mount namespace and not specifying MS_REC would
511 reveal subtrees that had been covered up. */
512 if (mount(source.c_str(), target.c_str(), 0, MS_BIND|MS_REC, 0) == -1)
513 throw SysError(format("bind mount from `%1%' to `%2%' failed") % source % target);
514 if(readOnly) {
515#if defined(MS_REMOUNT) && defined(MS_RDONLY)
516 /* Extra flags passed with MS_BIND are ignored, hence the extra
517 MS_REMOUNT. */
518 unsigned long mount_flags = MS_BIND | MS_REMOUNT | MS_RDONLY;
519 /* MS_BIND | MS_REMOUNT sets all mountpoint flags, so we may get EPERM
520 unless we preserve the other flags (for example because it would
521 result in trying to clear the nosuid flag). */
522#if HAVE_STATVFS
523 struct statvfs stvfs;
524 if(statvfs(target.c_str(), &stvfs) == -1)
525 throw SysError(format("statvfs of `%1%'") % target);
526 mount_flags |= statfsToMountFlags(stvfs.f_flag);
527#endif
528
529 if (mount(source.c_str(), target.c_str(), 0, mount_flags, 0) == -1)
530 throw SysError(format("read-only remount of `%1%' failed") % target);
531#else
532 throw Error("remounting read-only is not supported on this platform");
533#endif
534 }
535#endif
536}
537
538
539void mountIntoChroot(std::map<Path, Path> filesInChroot,
540 set<Path> readOnlyFiles,
541 Path chrootRootDir)
542{
543#if HAVE_SYS_MOUNT_H && defined(MS_BIND)
544 for(auto i = filesInChroot.begin(); i != filesInChroot.end(); i++) {
545 Path source = i->second;
546 Path target = chrootRootDir + i->first;
547 bool readOnly = readOnlyFiles.find(i->first) != readOnlyFiles.end();
548 bindMount(source, target, readOnly);
549 }
550#else
551 throw Error("bind mounting not supported on this platform");
552#endif
553}
554
555
556void mountIntoChrootAction(SpawnContext & sctx)
557{
558#if CLONE_ENABLED && HAVE_SYS_MOUNT_H && defined(MS_BIND)
559 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
560 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.doChroot) {
561 mountIntoChroot(ctx.filesInChroot, ctx.readOnlyFilesInChroot, ctx.chrootRootDir);
562 }
563#endif
564}
565
566
567void mountProcAction(SpawnContext & sctx)
568{
569#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
570 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
571 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.mountProc) {
572 Path target = (ctx.doChroot ? ctx.chrootRootDir : "") + "/proc";
573 createDirs(target);
574 if(mount("none", target.c_str(), "proc", 0, 0) == -1)
575 throw SysError(format("mounting `%1%'") % target);
576 }
577#endif
578}
579
580
581void mountDevshmAction(SpawnContext & sctx)
582{
583#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
584 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
585 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.mountDevshm) {
586 Path target = (ctx.doChroot ? ctx.chrootRootDir : "") + "/dev/shm";
587 createDirs(target);
588 if(mount("none", target.c_str(), "tmpfs", 0, 0) == -1)
589 throw SysError(format("mounting `%1%'") % target);
590 }
591#endif
592}
593
594
595void mountDevptsAction(SpawnContext & sctx)
596{
597#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
598 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
599 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.maybeMountDevpts) {
600 Path chroot = (ctx.doChroot ? ctx.chrootRootDir : "");
601 Path target = chroot + "/dev/pts";
602 if(pathExists(chroot + "/dev/ptmx")) return;
603 createDirs(target);
604 if(mount("none", target.c_str(), "devpts", 0, "newinstance,mode=0620") == -1)
605 throw SysError(format("mounting `%1%'") % target);
606 createSymlink("/dev/pts/ptmx", chroot + "/dev/ptmx");
607 /* Make sure /dev/pts/ptmx is world-writable. With some Linux
608 versions, it is created with permissions 0. */
609 Path targetPtmx = chroot + "/dev/pts/ptmx";
610 if (chmod(targetPtmx.c_str(), 0666) == -1)
611 throw SysError(format("setting permissions on `%1%'") % targetPtmx);
612 }
613#endif
614}
615
616
617void pivotRootAction(SpawnContext & sctx)
618{
619#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
620 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
621 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.doChroot) {
622 if (chdir(ctx.chrootRootDir.c_str()) == -1)
623 throw SysError(format("cannot change directory to '%1%'") % ctx.chrootRootDir);
624
625 if (mkdir("real-root", 0) == -1)
626 throw SysError("cannot create real-root directory");
627
628 if (pivot_root(".", "real-root") == -1)
629 throw SysError(format("cannot pivot old root directory onto '%1%'") % (ctx.chrootRootDir + "/real-root"));
630
631 if (chroot(".") == -1)
632 throw SysError(format("cannot change root directory to '%1%'") % ctx.chrootRootDir);
633
634 if (umount2("real-root", MNT_DETACH) == -1)
635 throw SysError("cannot unmount real root filesystem");
636
637 if (rmdir("real-root") == -1)
638 throw SysError("cannot remove real-root directory");
639 }
640#endif
641}
642
643
644string idMapToIdentityMap(const string & map)
645{
646 std::vector<string> mapLines =
647 tokenizeString<std::vector<string> >(map, "\n");
648 string out;
649
650 for(auto & i : mapLines) {
651 std::vector<string> elements =
652 tokenizeString<std::vector<string> >(i, " ");
653 out.append(elements.at(0) + " " + elements.at(0) + " " + elements.at(2) + "\n");
654 }
655 return out;
656}
657
658
659/* Initializing a user namespace with more than one id mapped requires
660 * capabilities in the *parent* user namespace, which may not even have any
661 * processes in it after unshare is called. So fork a child and have it do
662 * the initialization. */
663void unshareAndInitUserns(int flags, const string & uidMap,
664 const string & gidMap, bool allowSetgroups)
665{
666#if CLONE_ENABLED
667 pid_t pid_ = getpid();
668 string pid = std::to_string(pid_);
669 Pipe toChild;
670 Pipe fromChild;
671 toChild.create();
672 fromChild.create();
673 pid_t child = fork();
674 if(child == -1)
675 throw SysError("creating child process");
676 if(child == 0) {
677 try {
678 toChild.writeSide.close();
679 fromChild.readSide.close();
680 waitForMessage(toChild.readSide, "ready\n");
681 writeFile("/proc/" + pid + "/uid_map", uidMap);
682 writeFile("/proc/" + pid + "/setgroups",
683 allowSetgroups ? "allow" : "deny");
684 writeFile("/proc/" + pid + "/gid_map", gidMap);
685 writeFull(fromChild.writeSide, (unsigned char*)"go\n", 3);
686 } catch(...) {
687 /* Don't unwind the stack in case of exception, halt
688 * immediately. */
689 _exit(1);
690 }
691 _exit(EXIT_SUCCESS);
692 } else {
693 toChild.readSide.close();
694 fromChild.writeSide.close();
695 if(unshare(flags) == -1)
696 throw SysError("unshare");
697 writeFull(toChild.writeSide, (unsigned char*)"ready\n", 6);
698 waitForMessage(fromChild.readSide, "go\n");
699 int status;
700 while(waitpid(child, &status, 0) == -1) {
701 if(errno != EINTR)
702 throw SysError("reaping userns init process");
703 }
704 if(!(WIFEXITED(status) != 0 && WEXITSTATUS(status) == EXIT_SUCCESS))
705 throw Error(format("userns init child exited with status %1%") % WEXITSTATUS(status));
706 }
707#endif
708}
709
710
711void lockMountsAction(SpawnContext & sctx)
712{
713#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
714 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
715 if(ctx.lockMounts) {
716 string uidMap;
717 string gidMap;
718 if(ctx.lockMountsMapAll) {
719 string oldUidMap = readFile("/proc/self/uid_map", true);
720 string oldGidMap = readFile("/proc/self/gid_map", true);
721 uidMap = idMapToIdentityMap(oldUidMap);
722 gidMap = idMapToIdentityMap(oldGidMap);
723 } else {
724 string uid = std::to_string(getuid());
725 string gid = std::to_string(getgid());
726 uidMap = uid + " " + uid + " 1";
727 gidMap = gid + " " + gid + " 1";
728 }
729 unshareAndInitUserns(CLONE_NEWNS | CLONE_NEWUSER,
730 uidMap, gidMap, ctx.lockMountsAllowSetgroups);
731 /* Check that mounts inherited in our new mount namespace are "locked"
732 together and cannot be separated from within our mount namespace.
733 Since umount(2) is documented to fail with EINVAL when attempting
734 to unmount one of the mounts that are locked together, check that
735 this is what we get. */
736 int ret = umount("/proc");
737 assert(ret == -1 && errno == EINVAL);
738 }
739#endif
740}
741
742
743Phases getCloneSpawnPhases()
744{
745#if CLONE_ENABLED
746 return { { "reset_writeToStderr", reset_writeToStderrAction },
747 { "usernsInitSync", usernsInitSyncAction },
748 { "usernsSetIDs", usernsSetIDsAction },
749 { "restoreAffinity", restoreAffinityAction },
750 { "setsid", setsidAction },
751 { "earlyIOSetup", earlyIOSetupAction },
752 { "dropAmbientCapabilities", dropAmbientCapabilitiesAction },
753 { "initLoopback", initLoopbackAction },
754 { "setHostAndDomain", setHostAndDomainAction },
755 { "makeFilesystemsPrivate", makeFilesystemsPrivateAction },
756 { "makeChrootSeparateFilesystem", makeChrootSeparateFilesystemAction },
757 { "mountIntoChroot", mountIntoChrootAction },
758 { "mountProc", mountProcAction },
759 { "mountDevshm", mountDevshmAction },
760 { "mountDevpts", mountDevptsAction },
761 { "chroot", pivotRootAction },
762 { "chdir", chdirAction },
763 { "closeMostFDs", closeMostFDsAction },
764 { "setPersonality", setPersonalityAction },
765 { "oomSacrifice", oomSacrificeAction },
766 /* Being put in a user namespace with only the current ids mapped
767 would tend to prevent switching to other ones, but if this
768 comes after setIDs then the per-process "dumpable" flag may be
769 reset, which will cause /proc/self to become root-owned,
770 making /proc/self/uid_map inaccessible. If you need
771 lockMounts to preserve the id mappings, and you have the
772 necessary capabilities in the parent user namespace, set
773 CloneSpawnContext.lockMountsMapAll = true. */
774 { "lockMounts", lockMountsAction },
775 { "setIDs", setIDsAction },
776 { "restoreSIGPIPE", restoreSIGPIPEAction },
777 { "setupSuccess", setupSuccessAction },
778 { "exec", execAction }};
779#else
780 throw Error("clone not supported on this platform");
781#endif
782}
783
784
785void runChildSetup(SpawnContext & ctx)
786{
787 ctx.currentPhase = 0;
788 try {
789 /* Should not return regularly from this */
790 while(true) {
791 ctx.phases.at(ctx.currentPhase).action(ctx);
792 ctx.currentPhase++;
793 }
794 } catch (std::exception & e) {
795 try {
796 writeFull(STDERR_FILENO,
797 "while setting up the child process: " +
798 (ctx.currentPhase < (ssize_t)ctx.phases.size() ?
799 "in phase " + ctx.phases[ctx.currentPhase].label + ": " : "") +
800 string(e.what()) + "\n");
801 } catch (std::exception & e2) {
802 _exit(1);
803 }
804 _exit(1);
805 }
806 abort(); /* Should never be reached */
807}
808
809
810int runChildSetupEntry(void *data)
811{
812 runChildSetup(* (SpawnContext *)data);
813 return 1;
814}
815
816
817int cloneChild(CloneSpawnContext & ctx)
818{
819 char stack[32 * 1024];
820 /* Ensure proper alignment on the stack. On aarch64, it has to be 16
821 bytes. */
822 char *alignedStack = (char *)(((uintptr_t)stack + sizeof(stack) - 8) & ~(uintptr_t)0xf);
823 int ret = clone(runChildSetupEntry, alignedStack, ctx.cloneFlags, (void *) &ctx);
824 if(ret == -1)
825 throw SysError("clone");
826 return ret;
827}
828
829}
diff --git a/nix/libutil/spawn.hh b/nix/libutil/spawn.hh
new file mode 100644
index 00000000000..edc528312db
--- /dev/null
+++ b/nix/libutil/spawn.hh
@@ -0,0 +1,164 @@
1#pragma once
2
3#include <util.hh>
4#include <map>
5#include <stddef.h>
6
7namespace nix {
8struct SpawnContext; /* Forward declaration */
9typedef void (Action)(SpawnContext & ctx);
10
11struct Phase {
12 string label;
13 Action * action;
14};
15
16typedef std::vector<Phase> Phases;
17
18/* Common structure read from / written to by setup phases in a newly-spawned
19 child process. Configure this to determine which per-process or
20 per-thread attributes should be set. */
21struct SpawnContext {
22 ssize_t currentPhase = 0;
23 Phases phases;
24 Strings args; /* Will be passed as-is to execve, does not implicitly add
25 * program basename as argv[0]! */
26 Path program;
27 bool inheritEnv = true; /* True to use the current environment after env
28 * has been applied to it, false to use strictly
29 * env. */
30 std::map<string, string> env;
31 bool setPersona = false;
32 int persona;
33 int logFD = -1; /* -1 to keep stdout and stderr */
34 set<int> earlyCloseFDs; /* Typically for closing inherited unused pipe or
35 * socket ends to prevent hangs when reading or
36 * writing. */
37 bool closeMostFDs = false;
38 set<int> preserveFDs; /* 0, 1, and 2 are always implicitly preserved. */
39 bool setStdin = false;
40 int stdinFD = -1; /* fd or -1 */
41 Path stdinFile; /* used if stdinFD == -1 */
42 bool setuid = false;
43 uid_t user;
44 bool setgid = false;
45 gid_t group;
46 bool setSupplementaryGroups = false;
47 std::vector<gid_t> supplementaryGroups;
48 bool setsid = false;
49 bool oomSacrifice = false; /* Whether to attempt to offer the child
50 * process to the OOM killer if possible. */
51 bool setcwd = false;
52 Path cwd;
53 bool signalSetupSuccess = false; /* Whether the parent is waiting for a
54 * message that setup succeeded. By
55 * default success is signaled by
56 * writing a single newline to stderr. */
57 bool dropAmbientCapabilities = false; /* Whether to drop ambient
58 * capabilities if on a system that
59 * supports them. */
60 bool doChroot = false;
61 Path chrootRootDir;
62 void * extraData; /* Extra user data */
63};
64
65/* Like SpawnContext, but with extra fields for setting up Linux namespaces,
66 as created by clone or unshare. */
67struct CloneSpawnContext : SpawnContext {
68 int cloneFlags = 0;
69 std::map<Path, Path> filesInChroot; /* map from path inside chroot to
70 * path outside of chroot */
71 set<Path> readOnlyFilesInChroot;
72 bool mountTmpfsOnChroot = false; /* req. CLONE_NEWNS and doChroot */
73 bool mountProc = false;
74 bool mountDevshm = false;
75 bool maybeMountDevpts = false; /* Only mounted if /dev/ptmx doesn't exist
76 * after any chroot, if applicable. */
77 bool lockMounts = false; /* Whether to lock mounts by creating a fresh
78 * user and mount namespace, see
79 * mount_namespaces(7). */
80 bool lockMountsMapAll = false; /* Whether to map all currently-mapped
81 users and groups when locking mounts or
82 only the current ones. */
83 bool lockMountsAllowSetgroups = false;
84 int setupFD = -1; /* Used for userns init sync and other stuff */
85 string hostname; /* Requires CLONE_NEWUTS */
86 string domainname; /* Same */
87 bool initLoopback = false; /* Also requires CLONE_NEWNET in cloneFlags */
88 /* These may be used if CLONE_NEWUSER in cloneFlags. These are to be
89 used when an id other than the current uid/gid has been mapped into the
90 child's user namespace, and it now needs to setuid/setgid to an id
91 that is mapped. */
92 bool usernsSetuid = false;
93 uid_t usernsUser;
94 bool usernsSetgid = false;
95 gid_t usernsGroup;
96};
97
98void addPhaseAfter(Phases & phases, string afterLabel, string addLabel, Action addAction);
99
100void addPhaseBefore(Phases & phases, string beforeLabel, string addLabel, Action addAction);
101
102void prependPhase(Phases & phases, string addLabel, Action addAction);
103
104void appendPhase(Phases & phases, string addLabel, Action addAction);
105
106void deletePhase(Phases & phases, string delLabel);
107
108void replacePhase(Phases & phases, string replaceLabel, Action newAction);
109
110Action reset_writeToStderrAction;
111Action restoreAffinityAction;
112Action setsidAction;
113Action earlyIOSetupAction;
114Action dropAmbientCapabilitiesAction;
115Action chrootAction;
116Action chdirAction;
117Action closeMostFDsAction;
118Action setPersonalityAction;
119Action oomSacrificeAction;
120Action setIDsAction;
121Action restoreSIGPIPEAction;
122Action setupSuccessAction;
123Action execAction;
124
125Phases getBasicSpawnPhases();
126
127void bindMount(Path source, Path target, bool readOnly);
128
129void mountIntoChroot(std::map<Path, Path> filesInChroot,
130 set<Path> readOnlyFiles,
131 Path chrootRootDir);
132
133Action usernsInitSyncAction;
134Action usernsSetIDsAction;
135Action initLoopbackAction;
136Action setHostAndDomainAction;
137Action makeFilesystemsPrivateAction;
138Action makeChrootSeparateFilesystemAction;
139Action mountIntoChrootAction;
140Action mountProcAction;
141Action mountDevshmAction;
142Action mountDevptsAction;
143Action pivotRootAction;
144Action lockMountsAction;
145
146Phases getCloneSpawnPhases();
147
148/* Helpers */
149string idMapToIdentityMap(const string & map);
150void unshareAndInitUserns(int flags, const string & uidMap,
151 const string & gidMap, bool allowSetgroups);
152
153/* Run the phases of ctx in order, catching and reporting any exception, and
154 * exiting in all cases. */
155void runChildSetup(SpawnContext & ctx);
156
157/* Helper to call runChildSetup that can be passed to the variant of clone
158 * that expects a callback. */
159int runChildSetupEntry(void *data);
160
161/* Create a new process using clone that will immediately call runChildSetup
162 * with the provided CloneSpawnContext. Return the pid of the new process. */
163int cloneChild(CloneSpawnContext & ctx);
164}
diff --git a/nix/libutil/util.cc b/nix/libutil/util.cc
index c406325cdcb..e71e6c170ad 100644
--- a/nix/libutil/util.cc
+++ b/nix/libutil/util.cc
@@ -724,6 +724,18 @@ string drainFD(int fd)
724} 724}
725 725
726 726
727/* Wait on FD until MESSAGE has been read. */
728void waitForMessage(int fd, const char *message)
729{
730 size_t size = strlen(message);
731 char str[size] = { '\0' };
732 readFull(fd, (unsigned char*)str, size);
733 if (strncmp(str, message, size) != 0)
734 throw Error(format("did not receive message '%1%' on file descriptor %2%")
735 % message % fd);
736}
737
738
727 739
728////////////////////////////////////////////////////////////////////// 740//////////////////////////////////////////////////////////////////////
729 741
@@ -1140,6 +1152,13 @@ void closeOnExec(int fd)
1140 throw SysError("setting close-on-exec flag"); 1152 throw SysError("setting close-on-exec flag");
1141} 1153}
1142 1154
1155void keepOnExec(int fd)
1156{
1157 int prev;
1158 if ((prev = fcntl(fd, F_GETFD, 0)) == -1 ||
1159 fcntl(fd, F_SETFD, prev & ~FD_CLOEXEC) == -1)
1160 throw SysError("clearing close-on-exec flag");
1161}
1143 1162
1144////////////////////////////////////////////////////////////////////// 1163//////////////////////////////////////////////////////////////////////
1145 1164
diff --git a/nix/libutil/util.hh b/nix/libutil/util.hh
index a07c3be6eb6..ab2395e959f 100644
--- a/nix/libutil/util.hh
+++ b/nix/libutil/util.hh
@@ -173,6 +173,8 @@ MakeError(EndOfFile, Error)
173/* Read a file descriptor until EOF occurs. */ 173/* Read a file descriptor until EOF occurs. */
174string drainFD(int fd); 174string drainFD(int fd);
175 175
176void waitForMessage(int fd, const char *message);
177
176 178
177 179
178/* Automatic cleanup of resources. */ 180/* Automatic cleanup of resources. */
@@ -300,6 +302,9 @@ void closeMostFDs(const set<int> & exceptions);
300/* Set the close-on-exec flag for the given file descriptor. */ 302/* Set the close-on-exec flag for the given file descriptor. */
301void closeOnExec(int fd); 303void closeOnExec(int fd);
302 304
305/* Clear the close-on-exec flag for the given file descriptor. */
306void keepOnExec(int fd);
307
303/* Common initialisation performed in child processes. */ 308/* Common initialisation performed in child processes. */
304void commonChildInit(Pipe & logPipe); 309void commonChildInit(Pipe & logPipe);
305 310
diff --git a/nix/local.mk b/nix/local.mk
index 54976a5741a..9f21550af2d 100644
--- a/nix/local.mk
+++ b/nix/local.mk
@@ -56,7 +56,8 @@ libutil_a_SOURCES = \
56 %D%/libutil/affinity.cc \ 56 %D%/libutil/affinity.cc \
57 %D%/libutil/serialise.cc \ 57 %D%/libutil/serialise.cc \
58 %D%/libutil/util.cc \ 58 %D%/libutil/util.cc \
59 %D%/libutil/hash.cc 59 %D%/libutil/hash.cc \
60 %D%/libutil/spawn.cc
60 61
61libutil_headers = \ 62libutil_headers = \
62 %D%/libutil/affinity.hh \ 63 %D%/libutil/affinity.hh \
@@ -64,7 +65,8 @@ libutil_headers = \
64 %D%/libutil/serialise.hh \ 65 %D%/libutil/serialise.hh \
65 %D%/libutil/util.hh \ 66 %D%/libutil/util.hh \
66 %D%/libutil/archive.hh \ 67 %D%/libutil/archive.hh \
67 %D%/libutil/types.hh 68 %D%/libutil/types.hh \
69 %D%/libutil/spawn.hh
68 70
69libutil_a_CPPFLAGS = \ 71libutil_a_CPPFLAGS = \
70 -I$(top_builddir)/nix \ 72 -I$(top_builddir)/nix \