summaryrefslogtreecommitdiff
path: root/nix/libutil
diff options
context:
space:
mode:
authorReepca Russelstein <reepca@russelstein.xyz>2025-04-18 01:35:31 -0500
committerJohn Kehayias <john.kehayias@protonmail.com>2025-06-24 10:07:57 -0400
commitfb42611b8f27960304db5a1c0d33b8371dcde2a8 (patch)
treee4331b4b340c3304684914044d543ecd0e653fb7 /nix/libutil
parentbe8aca065118aa4485c02f991c51bea89034defa (diff)
daemon: Use slirp4netns to provide networking to fixed-output derivations.
Previously, the builder of a fixed-output derivation could communicate with an external process via an abstract Unix-domain socket. In particular, it could send an open file descriptor to the store, granting write access to some of its output files in the store provided the derivation build fails—the fix for CVE-2024-27297 did not address this specific case. It could also send an open file descriptor to a setuid program, which could then be executed using execveat to gain the privileges of the build user. With this change, fixed-output derivations other than “builtin:download” and “builtin:git-download” always run in a separate network namespace and have network access provided by a TAP device backed by slirp4netns, thereby closing the abstract Unix-domain socket channel. * nix/libstore/globals.hh (Settings)[useHostLoopback, slirp4netns]: new fields. * config-daemon.ac (SLIRP4NETNS): new C preprocessor definition. * nix/libstore/globals.cc (Settings::Settings): initialize them to defaults. * nix/nix-daemon/guix-daemon.cc (options): add --isolate-host-loopback option. * doc/guix.texi: document it. * nix/libstore/build.cc (DerivationGoal)[slirp]: New field. (setupTap, setupTapAction, waitForSlirpReadyAction, enableRouteLocalnetAction, prepareSlirpChrootAction, spawnSlirp4netns, haveGlobalIPv6Address, remapIdsTo0Action): New functions. (initializeUserNamespace): allow the guest UID and GID to be specified. (DerivationGoal::killChild): When ‘slirp’ is not -1, call ‘kill’. (DerivationGoal::startBuilder): Unconditionally add CLONE_NEWNET to FLAGS. When ‘fixedOutput’ is true, spawn ‘slirp4netns’. When ‘fixedOutput’ and ‘useChroot’ are true, add setupTapAction, waitForSlirpReadyAction, and enableRouteLocalnetAction to builder setup phases. Create a /etc/resolv.conf for fixed-output derivations that directs them to slirp4netns's dns address. When settings.useHostLoopback is true, supply fixed-output derivations with a /etc/hosts that resolves "localhost" to slirp4netns's address for accessing the host loopback. * nix/libutil/util.cc (keepOnExec, decodeOctalEscaped, sendFD, receiveFD, findProgram): New functions. * nix/libutil/util.hh (keepOnExec, decodeOctalEscaped, sendFD, receiveFD, findProgram): New declarations. * gnu/packages/package-management.scm (guix): add slirp4netns input for linux targets. * tests/derivations.scm (builder-network-isolated?): new variable. ("fixed-output derivation, network access, localhost", "fixed-output derivation, network access, external host"): skip test case if fixed output derivations are isolated from the network. Change-Id: Ia3fea2ab7add56df66800071cf15cdafe7bfab96 Signed-off-by: John Kehayias <john.kehayias@protonmail.com>
Diffstat (limited to 'nix/libutil')
-rw-r--r--nix/libutil/util.cc101
-rw-r--r--nix/libutil/util.hh16
2 files changed, 117 insertions, 0 deletions
diff --git a/nix/libutil/util.cc b/nix/libutil/util.cc
index e71e6c170a..327edf471f 100644
--- a/nix/libutil/util.cc
+++ b/nix/libutil/util.cc
@@ -14,6 +14,7 @@
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
+#include <sys/socket.h>
#ifdef __APPLE__
#include <sys/syscall.h>
@@ -62,6 +63,27 @@ string getEnv(const string & key, const string & def)
}
+string findProgram(const string & program)
+{
+ if(program.empty()) return "";
+
+ if(program[0] == '/') return pathExists(program) ? program : "";
+
+ char *path_ = getenv("PATH");
+ if(path_ == NULL) return "";
+ string path = path_;
+
+ Strings dirs = tokenizeString<Strings>(path, ":");
+ for (const auto& i : dirs) {
+ if(i == "") continue;
+ string f = i + "/" + program;
+ if(pathExists(f)) return f;
+ }
+
+ return "";
+}
+
+
Path absPath(Path path, Path dir)
{
if (path[0] != '/') {
@@ -857,6 +879,67 @@ void Pipe::create()
}
+void sendFD(int sock, int fd)
+{
+ ssize_t rc;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ char cmsgbuf[CMSG_SPACE(sizeof(fd))];
+ struct iovec iov;
+ char dummy = '\0';
+ memset(&msg, 0, sizeof(msg));
+ iov.iov_base = &dummy;
+ iov.iov_len = 1;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+ memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
+ msg.msg_controllen = cmsg->cmsg_len;
+ do
+ {
+ rc = sendmsg(sock, &msg, 0);
+ } while(rc < 0 && errno == EINTR);
+ if(rc < 0)
+ throw SysError("sending fd");
+}
+
+
+int receiveFD(int sock)
+{
+ int fd;
+ ssize_t rc;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ char cmsgbuf[CMSG_SPACE(sizeof(fd))];
+ struct iovec iov;
+ char dummy = '\0';
+ memset(&msg, 0, sizeof(msg));
+ iov.iov_base = &dummy;
+ iov.iov_len = 1;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+ do
+ {
+ rc = recvmsg(sock, &msg, 0);
+ } while(rc < 0 && errno == EINTR);
+ if (rc < 0)
+ throw SysError("receiving fd");
+ if (rc == 0)
+ throw Error("received EOF (empty message) while receiving fd");
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg == NULL || cmsg->cmsg_type != SCM_RIGHTS)
+ throw Error("received message without an fd");
+ memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd));
+ return fd;
+}
//////////////////////////////////////////////////////////////////////
@@ -1301,6 +1384,24 @@ bool endOfList(std::istream & str)
return false;
}
+string decodeOctalEscaped(const string & s)
+{
+ string r;
+ for (string::const_iterator i = s.begin(); i != s.end(); ) {
+ if (*i != '\\') { r += *(i++); continue; }
+ unsigned char c = 0;
+ ++i;
+ for(int j = 0; j < 3; j++) {
+ if(i == s.end() || *i < '0' || *i >= '8')
+ throw Error("malformed octal escape");
+ c = c * 8 + (*i - '0');
+ ++i;
+ }
+ r += c;
+ }
+ return r;
+}
+
void ignoreException()
{
diff --git a/nix/libutil/util.hh b/nix/libutil/util.hh
index ab2395e959..648d6f19a4 100644
--- a/nix/libutil/util.hh
+++ b/nix/libutil/util.hh
@@ -19,6 +19,12 @@ namespace nix {
/* Return an environment variable. */
string getEnv(const string & key, const string & def = "");
+/* Find the absolute filename corresponding to PROGRAM, searching PATH if
+ PROGRAM is a relative filename. If PROGRAM is an absolute filename for a
+ file that doesn't exist, or it can't be found in PATH, then return the
+ empty string. */
+string findProgram(const string & program);
+
/* Return an absolutized path, resolving paths relative to the
specified directory, or the current directory otherwise. The path
is also canonicalised. */
@@ -207,6 +213,10 @@ public:
int borrow();
};
+/* Send and receive an FD on a unix-domain socket, along with a single null
+ byte of regular data. */
+void sendFD(int sock, int fd);
+int receiveFD(int sock);
class Pipe
{
@@ -370,6 +380,12 @@ string parseString(std::istream & str);
bool endOfList(std::istream & str);
+/* Escape a string that contains octal-encoded escape codes such as
+ used in /etc/fstab and /proc/mounts (e.g. "foo\040bar" decodes to
+ "foo bar"). */
+string decodeOctalEscaped(const string & s);
+
+
/* Exception handling in destructors: print an error message, then
ignore the exception. */
void ignoreException();