In chroot builds, use a private network namespace

On Linux it's possible to run a process in its own network namespace,
meaning that it gets its own set of network interfaces, disjunct from
the rest of the system.  We use this to completely remove network
access to chroot builds, except that they get a private loopback
interface.  This means that:

- Builders cannot connect to the outside network or to other processes
  on the same machine, except processes within the same build.

- Vice versa, other processes cannot connect to processes in a chroot
  build, and open ports/connections do not show up in "netstat".

- If two concurrent builders try to listen on the same port (e.g. as
  part of a test), they no longer conflict with each other.

This was inspired by the "PrivateNetwork" flag in systemd.
This commit is contained in:
Eelco Dolstra 2012-06-23 00:28:35 -04:00
parent 2f3f413e91
commit df716c98d2

View file

@ -47,6 +47,13 @@
#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(CLONE_NEWNS)
#if CHROOT_ENABLED
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/ip.h>
#endif
#if HAVE_SYS_PERSONALITY_H
#include <sys/personality.h>
@ -1769,12 +1776,30 @@ void DerivationGoal::startBuilder()
#if CHROOT_ENABLED
if (useChroot) {
/* Create our own mount namespace. This means that
all the bind mounts we do will only show up in this
process and its children, and will disappear
automatically when we're done. */
if (unshare(CLONE_NEWNS) == -1)
throw SysError(format("cannot set up a private mount namespace"));
/* Create our own mount and network namespace. This
means that all the bind mounts we do will only show
up in this process and its children, and will
disappear automatically when we're done.
Similarly, this process will not have any network
interface except "lo" created below. */
if (unshare(CLONE_NEWNS | CLONE_NEWNET) == -1)
throw SysError("cannot set up a private mount namespace");
/* Initialise the loopback interface. Note that this
loopback device is unique to this process and its
children. Thus they won't be able to open
connections to the rest of the system, or vice
versa. */
AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (fd == -1) throw SysError("cannot open IP socket");
struct ifreq ifr;
strcpy(ifr.ifr_name, "lo");
ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1)
throw SysError("cannot set loopback interface flags");
fd.close();
/* Bind-mount all the directories from the "host"
filesystem that we want in the chroot