CVE-2021-43784: Integer Overflow or Wraparound (CWE-190)
value: runc is a CLI tool for spawning and running containers on Linux according to the OCI specification. In runc, netlink is used internally as a serialization system for specifying the relevant container configuration to the `C` portion of the code (responsible for the based namespace setup of containers). In all versions of runc prior to 1.0.3, the encoder did not handle the possibility of an integer overflow in the 16-bit length field for the byte array attribute type, meaning that a large enough malicious byte array attribute could result in the length overflowing and the attribute contents being parsed as netlink messages for container configuration. This vulnerability requires the attacker to have some control over the configuration of the container and would allow the attacker to bypass the namespace restrictions of the container by simply adding their own netlink payload which disables all namespaces. The main users impacted are those who allow untrusted images with untrusted configurations to run on their machines (such as with shared cloud infrastructure). runc version 1.0.3 contains a fix for this bug. As a workaround, one may try disallowing untrusted namespace paths from your container. It should be noted that untrusted namespace paths would allow the attacker to disable namespace protections entirely even in the absence of this bug.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 5
Published: 2021-12-06T18:15Z
Repository: https://github.com/opencontainers/runc
Language: Go
Files changed: libcontainer/configs/mount.go, libcontainer/container_linux.go, libcontainer/factory_linux.go, libcontainer/init_linux.go, libcontainer/message_linux.go, libcontainer/nsenter/nsexec.c, libcontainer/rootfs_linux.go, libcontainer/standard_init_linux.go
Commit date: 2021-10-12 15:13:45 +0200
Commit message: Open bind mount sources from the host userns

The source of the bind mount might not be accessible in a different user
namespace because a component of the source path might not be traversed
under the users and groups mapped inside the user namespace. This caused
errors such as the following:

  # time="2020-06-22T13:48:26Z" level=error msg="container_linux.go:367:
  starting container process caused: process_linux.go:459:
  container init caused: rootfs_linux.go:58:
  mounting \"/tmp/busyboxtest/source-inaccessible/dir\"
  to rootfs at \"/tmp/inaccessible\" caused:
  stat /tmp/busyboxtest/source-inaccessible/dir: permission denied"

To solve this problem, this patch performs the following:

1. in nsexec.c, it opens the source path in the host userns (so we have
   the right permissions to open it) but in the container mntns (so the
   kernel cross mntns mount check let us mount it later:
   https://github.com/torvalds/linux/blob/v5.8/fs/namespace.c#L2312).

2. in nsexec.c, it passes the file descriptors of the source to the
   child process with SCM_RIGHTS.

3. In runc-init in Golang, it finishes the mounts while inside the
   userns even without access to the some components of the source
   paths.

Passing the fds with SCM_RIGHTS is necessary because once the child
process is in the container mntns, it is already in the container userns
so it cannot temporarily join the host mntns.

This patch uses the existing mechanism with _LIBCONTAINER_* environment
variables to pass the file descriptors from runc to runc init.

This patch uses the existing mechanism with the Netlink-style bootstrap
to pass information about the list of source mounts to nsexec.c.

Rootless containers don't use this bind mount sources fdpassing
mechanism because we can't setns() to the target mntns in a rootless
container (we don't have the privileges when we are in the host userns).

This patch takes care of using O_CLOEXEC on mount fds, and close them
early.

Fixes: #2484.

Signed-off-by: Alban Crequy <alban@kinvolk.io>
Signed-off-by: Rodrigo Campos <rodrigo@kinvolk.io>
Co-authored-by: Rodrigo Campos <rodrigo@kinvolk.io>

Diff: diff --git a/libcontainer/configs/mount.go b/libcontainer/configs/mount.go
index a75ff10ec..3f315f718 100644
--- a/libcontainer/configs/mount.go
+++ b/libcontainer/configs/mount.go
@@ -1,7 +1,9 @@
 package configs
 
+import "golang.org/x/sys/unix"
+
 const (
 	// EXT_COPYUP is a directive to copy up the contents of a directory when
 	// a tmpfs is mounted over it.
 	EXT_COPYUP = 1 << iota //nolint:golint // ignore "don't use ALL_CAPS" warning
 )
@@ -35,5 +37,9 @@ type Mount struct {
 	PremountCmds []Command `json:"premount_cmds"`
 
 	// Optional Command to be run after Source is mounted.
 	PostmountCmds []Command `json:"postmount_cmds"`
 }
+
+func (m *Mount) IsBind() bool {
+	return m.Flags&unix.MS_BIND != 0
+}
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index df78a950f..9ceb7869d 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -519,23 +519,80 @@ func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, chi
 		cmd.SysProcAttr.Pdeathsig = unix.Signal(c.config.ParentDeathSignal)
 	}
 	return cmd
 }
 
+// shouldSendMountSources says whether the child process must setup bind mounts with
+// the source pre-opened (O_PATH) in the host user namespace.
+// See https://github.com/opencontainers/runc/issues/2484
+func (c *linuxContainer) shouldSendMountSources() bool {
+	// Passing the mount sources via SCM_RIGHTS is only necessary when
+	// both userns and mntns are active.
+	if !c.config.Namespaces.Contains(configs.NEWUSER) ||
+		!c.config.Namespaces.Contains(configs.NEWNS) {
+		return false
+	}
+
+	// nsexec.c send_mountsources() requires setns(mntns) capabilities
+	// CAP_SYS_CHROOT and CAP_SYS_ADMIN.
+	if c.config.RootlessEUID {
+		return false
+	}
+
+	// We need to send sources if there are bind-mounts.
+	for _, m := range c.config.Mounts {
+		if m.IsBind() {
+			return true
+		}
+	}
+
+	return false
+}
+
 func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*initProcess, error) {
 	cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard))
 	nsMaps := make(map[configs.NamespaceType]string)
 	for _, ns := range c.config.Namespaces {
 		if ns.Path != "" {
 			nsMaps[ns.Type] = ns.Path
 		}
 	}
 	_, sharePidns := nsMaps[configs.NEWPID]
-	data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps)
+	data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps, initStandard)
 	if err != nil {
 		return nil, err
 	}
+
+	if c.shouldSendMountSources() {
+		// Elements on this slice will be paired with mounts (see StartInitialization() and
+		// prepareRootfs()). This slice MUST have the same size as c.config.Mounts.
+		mountFds := make([]int, len(c.config.Mounts))
+		for i, m := range c.config.Mounts {
+			if !m.IsBind() {
+				// Non bind-mounts do not use an fd.
+				mountFds[i] = -1
+				continue
+			}
+
+			// The fd passed here will not be used: nsexec.c will overwrite it with dup3(). We just need
+			// to allocate a fd so that we know the number to pass in the environment variable. The fd
+			// must not be closed before cmd.Start(), so we reuse messageSockPair.child because the
+			// lifecycle of that fd is already taken care of.
+			cmd.ExtraFiles = append(cmd.ExtraFiles, messageSockPair.child)
+			mountFds[i] = stdioFdCount + len(cmd.ExtraFiles) - 1
+		}
+
+		mountFdsJson, err := json.Marshal(mountFds)
+		if err != nil {
+			return nil, fmt.Errorf("Error creating _LIBCONTAINER_MOUNT_FDS: %w", err)
+		}
+
+		cmd.Env = append(cmd.Env,
+			"_LIBCONTAINER_MOUNT_FDS="+string(mountFdsJson),
+		)
+	}
+
 	init := &initProcess{
 		cmd:             cmd,
 		messageSockPair: messageSockPair,
 		logFilePair:     logFilePair,
 		manager:         c.cgroupManager,
@@ -556,11 +613,11 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, messageSockP
 	if err != nil {
 		return nil, fmt.Errorf("unable to get container state: %w", err)
 	}
 	// for setns process, we don't have to set cloneflags as the process namespaces
 	// will only be set via setns syscall
-	data, err := c.bootstrapData(0, state.NamespacePaths)
+	data, err := c.bootstrapData(0, state.NamespacePaths, initSetns)
 	if err != nil {
 		return nil, err
 	}
 	proc := &setnsProcess{
 		cmd:             cmd,
@@ -1211,11 +1268,13 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
 		//   the mountpoint appears as soon as /sys is mounted
 		return nil
 	case "bind":
 		// The prepareBindMount() function checks if source
 		// exists. So it cannot be used for other filesystem types.
-		if err := prepareBindMount(m, c.config.Rootfs); err != nil {
+		// TODO: pass something else than nil? Not sure if criu is
+		// impacted by issue #2484
+		if err := prepareBindMount(m, c.config.Rootfs, nil); err != nil {
 			return err
 		}
 	default:
 		// for all other filesystems just create the mountpoints
 		dest, err := securejoin.SecureJoin(c.config.Rootfs, m.Destination)
@@ -2048,11 +2107,11 @@ func encodeIDMapping(idMap []configs.IDMap) ([]byte, error) {
 // as a io.Reader.
 // Consumer can write the data to a bootstrap program
 // such as one that uses nsenter package to bootstrap the container's
 // init process correctly, i.e. with correct namespaces, uid/gid
 // mapping etc.
-func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string) (io.Reader, error) {
+func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string, it initType) (io.Reader, error) {
 	// create the netlink message
 	r := nl.NewNetlinkRequest(int(InitMsg), 0)
 
 	// write cloneFlags
 	r.AddData(&Int32msg{
@@ -2130,10 +2189,26 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
 	r.AddData(&Boolmsg{
 		Type:  RootlessEUIDAttr,
 		Value: c.config.RootlessEUID,
 	})
 
+	// Bind mount source to open.
+	if it == initStandard && c.shouldSendMountSources() {
+		var mounts []byte
+		for _, m := range c.config.Mounts {
+			if m.IsBind() {
+				mounts = append(mounts, []byte(m.Source)...)
+			}
+			mounts = append(mounts, byte(0))
+		}
+
+		r.AddData(&Bytemsg{
+			Type:  MountSourcesAttr,
+			Value: mounts,
+		})
+	}
+
 	return bytes.NewReader(r.Serialize()), nil
 }
 
 // ignoreTerminateErrors returns nil if the given err matches an error known
 // to indicate that the terminate occurred successfully or err was nil, otherwise
diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go
index 4c5bf67be..6c4a65c73 100644
--- a/libcontainer/factory_linux.go
+++ b/libcontainer/factory_linux.go
@@ -293,21 +293,27 @@ func (l *LinuxFactory) StartInitialization() (err error) {
 	logPipeFd, err := strconv.Atoi(logPipeFdStr)
 	if err != nil {
 		return fmt.Errorf("unable to convert _LIBCONTAINER_LOGPIPE: %w", err)
 	}
 
+	// Get mount files (O_PATH).
+	mountFds, err := parseMountFds()
+	if err != nil {
+		return err
+	}
+
 	// clear the current process's environment to clean any libcontainer
 	// specific env vars.
 	os.Clearenv()
 
 	defer func() {
 		if e := recover(); e != nil {
 			err = fmt.Errorf("panic from initialization: %w, %v", e, string(debug.Stack()))
 		}
 	}()
 
-	i, err := newContainerInit(it, pipe, consoleSocket, fifofd, logPipeFd)
+	i, err := newContainerInit(it, pipe, consoleSocket, fifofd, logPipeFd, mountFds)
 	if err != nil {
 		return err
 	}
 
 	// If Init succeeds, syscall.Exec will not return, hence none of the defers will be called.
@@ -357,5 +363,20 @@ func NewgidmapPath(newgidmapPath string) func(*LinuxFactory) error {
 	return func(l *LinuxFactory) error {
 		l.NewgidmapPath = newgidmapPath
 		return nil
 	}
 }
+
+func parseMountFds() ([]int, error) {
+	fdsJson := os.Getenv("_LIBCONTAINER_MOUNT_FDS")
+	if fdsJson == "" {
+		// Always return the nil slice if no fd is present.
+		return nil, nil
+	}
+
+	var mountFds []int
+	if err := json.Unmarshal([]byte(fdsJson), &mountFds); err != nil {
+		return nil, fmt.Errorf("Error unmarshalling _LIBCONTAINER_MOUNT_FDS: %w", err)
+	}
+
+	return mountFds, nil
+}
diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go
index 9f7dc75f2..1eb4ca0bf 100644
--- a/libcontainer/init_linux.go
+++ b/libcontainer/init_linux.go
@@ -74,20 +74,25 @@ type initConfig struct {
 
 type initer interface {
 	Init() error
 }
 
-func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd, logFd int) (initer, error) {
+func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd, logFd int, mountFds []int) (initer, error) {
 	var config *initConfig
 	if err := json.NewDecoder(pipe).Decode(&config); err != nil {
 		return nil, err
 	}
 	if err := populateProcessEnvironment(config.Env); err != nil {
 		return nil, err
 	}
 	switch t {
 	case initSetns:
+		// mountFds must be nil in this case. We don't mount while doing runc exec.
+		if mountFds != nil {
+			return nil, errors.New("mountFds must be nil. Can't mount while doing runc exec.")
+		}
+
 		return &linuxSetnsInit{
 			pipe:          pipe,
 			consoleSocket: consoleSocket,
 			config:        config,
 			logFd:         logFd,
@@ -98,10 +103,11 @@ func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd,
 			consoleSocket: consoleSocket,
 			parentPid:     unix.Getppid(),
 			config:        config,
 			fifoFd:        fifoFd,
 			logFd:         logFd,
+			mountFds:      mountFds,
 		}, nil
 	}
 	return nil, fmt.Errorf("unknown init type %q", t)
 }
 
diff --git a/libcontainer/message_linux.go b/libcontainer/message_linux.go
index f10efa366..7d0b62950 100644
--- a/libcontainer/message_linux.go
+++ b/libcontainer/message_linux.go
@@ -16,10 +16,11 @@ const (
 	SetgroupAttr     uint16 = 27285
 	OomScoreAdjAttr  uint16 = 27286
 	RootlessEUIDAttr uint16 = 27287
 	UidmapPathAttr   uint16 = 27288
 	GidmapPathAttr   uint16 = 27289
+	MountSourcesAttr uint16 = 27290
 )
 
 type Int32msg struct {
 	Type  uint16
 	Value uint32
diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c
index 7e79ca0eb..c53fb3d79 100644
--- a/libcontainer/nsenter/nsexec.c
+++ b/libcontainer/nsenter/nsexec.c
@@ -2,10 +2,11 @@
 #define _GNU_SOURCE
 #include <endian.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <grp.h>
+#include <limits.h>
 #include <sched.h>
 #include <setjmp.h>
 #include <signal.h>
 #include <stdarg.h>
 #include <stdbool.h>
@@ -37,10 +38,12 @@ enum sync_t {
 	SYNC_USERMAP_ACK = 0x41,	/* Mapping finished by the parent. */
 	SYNC_RECVPID_PLS = 0x42,	/* Tell parent we're sending the PID. */
 	SYNC_RECVPID_ACK = 0x43,	/* PID was correctly received by parent. */
 	SYNC_GRANDCHILD = 0x44,	/* The grandchild is ready to run. */
 	SYNC_CHILD_FINISH = 0x45,	/* The child or grandchild has finished. */
+	SYNC_MOUNTSOURCES_PLS = 0x46,	/* Tell parent to send mount sources by SCM_RIGHTS. */
+	SYNC_MOUNTSOURCES_ACK = 0x47,	/* All mount sources have been sent. */
 };
 
 #define STAGE_SETUP  -1
 /* longjmp() arguments. */
 #define STAGE_PARENT  0
@@ -85,10 +88,14 @@ struct nlconfig_t {
 	uint8_t is_rootless_euid;	/* boolean */
 	char *uidmappath;
 	size_t uidmappath_len;
 	char *gidmappath;
 	size_t gidmappath_len;
+
+	/* Mount sources opened outside the container userns. */
+	char *mountsources;
+	size_t mountsources_len;
 };
 
 /*
  * Log levels are the same as in logrus.
  */
@@ -117,10 +124,11 @@ static int loglevel = DEBUG;
 #define SETGROUP_ATTR		27285
 #define OOM_SCORE_ADJ_ATTR	27286
 #define ROOTLESS_EUID_ATTR	27287
 #define UIDMAPPATH_ATTR		27288
 #define GIDMAPPATH_ATTR		27289
+#define MOUNT_SOURCES_ATTR	27290
 
 /*
  * Use the raw syscall for versions of glibc which don't include a function for
  * it, namely (glibc 2.12).
  */
@@ -540,10 +548,14 @@ static void nl_parse(int fd, struct nlconfig_t *config)
 			config->gidmappath_len = payload_len;
 			break;
 		case SETGROUP_ATTR:
 			config->is_setgroup = readint8(current);
 			break;
+		case MOUNT_SOURCES_ATTR:
+			config->mountsources = current;
+			config->mountsources_len = payload_len;
+			break;
 		default:
 			bail("unknown netlink message type %d", nlattr->nla_type);
 		}
 
 		current += NLA_ALIGN(payload_len);
@@ -631,10 +643,197 @@ static inline int sane_kill(pid_t pid, int signum)
 		return kill(pid, signum);
 	else
 		return 0;
 }
 
+void receive_fd(int sockfd, int new_fd)
+{
+	int bytes_read;
+	struct msghdr msg = { };
+	struct cmsghdr *cmsg;
+	struct iovec iov = { };
+	char null_byte = '\0';
+	int ret;
+	int fd_count;
+	int *fd_payload;
+
+	iov.iov_base = &null_byte;
+	iov.iov_len = 1;
+
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	msg.msg_controllen = CMSG_SPACE(sizeof(int));
+	msg.msg_control = malloc(msg.msg_controllen);
+	if (msg.msg_control == NULL) {
+		bail("Can't allocate memory to receive fd.");
+	}
+
+	memset(msg.msg_control, 0, msg.msg_controllen);
+
+	bytes_read = recvmsg(sockfd, &msg, 0);
+	if (bytes_read != 1)
+		bail("failed to receive fd from unix socket %d", sockfd);
+	if (msg.msg_flags & MSG_CTRUNC)
+		bail("received truncated control message from unix socket %d", sockfd);
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	if (!cmsg)
+		bail("received message from unix socket %d without control message", sockfd);
+
+	if (cmsg->cmsg_level != SOL_SOCKET)
+		bail("received unknown control message from unix socket %d: cmsg_level=%d", sockfd, cmsg->cmsg_level);
+
+	if (cmsg->cmsg_type != SCM_RIGHTS)
+		bail("received unknown control message from unix socket %d: cmsg_type=%d", sockfd, cmsg->cmsg_type);
+
+	fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+	if (fd_count != 1)
+		bail("received control message from unix socket %d with too many fds: %d", sockfd, fd_count);
+
+	fd_payload = (int *)CMSG_DATA(cmsg);
+	ret = dup3(*fd_payload, new_fd, O_CLOEXEC);
+	if (ret < 0)
+		bail("cannot dup3 fd %d to %d", *fd_payload, new_fd);
+
+	free(msg.msg_control);
+
+	ret = close(*fd_payload);
+	if (ret < 0)
+		bail("cannot close fd %d", *fd_payload);
+}
+
+void send_fd(int sockfd, int fd)
+{
+	int bytes_written;
+	struct msghdr msg = { };
+	struct cmsghdr *cmsg;
+	struct iovec iov[1] = { };
+	char null_byte = '\0';
+
+	iov[0].iov_base = &null_byte;
+	iov[0].iov_len = 1;
+
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 1;
+
+	/* We send only one fd as specified by cmsg->cmsg_len below, even
+	 * though msg.msg_controllen might have more space due to alignment. */
+	msg.msg_controllen = CMSG_SPACE(sizeof(int));
+	msg.msg_control = malloc(msg.msg_controllen);
+	if (msg.msg_control == NULL) {
+		bail("Can't allocate memory to send fd.");
+	}
+
+	memset(msg.msg_control, 0, msg.msg_controllen);
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+	memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+	bytes_written = sendmsg(sockfd, &msg, 0);
+
+	free(msg.msg_control);
+
+	if (bytes_written != 1)
+		bail("failed to send fd %d via unix socket %d", fd, sockfd);
+}
+
+void receive_mountsources(int sockfd)
+{
+	char *mount_fds, *endp;
+	long new_fd;
+
+	// This env var must be a json array of ints.
+	mount_fds = getenv("_LIBCONTAINER_MOUNT_FDS");
+
+	if (mount_fds[0] != '[') {
+		bail("malformed _LIBCONTAINER_MOUNT_FDS env var: missing '['");
+	}
+	mount_fds++;
+
+	for (endp = mount_fds; *endp != ']'; mount_fds = endp + 1) {
+		new_fd = strtol(mount_fds, &endp, 10);
+		if (endp == mount_fds) {
+			bail("malformed _LIBCONTAINER_MOUNT_FDS env var: not a number");
+		}
+		if (*endp == '\0') {
+			bail("malformed _LIBCONTAINER_MOUNT_FDS env var: missing ]");
+		}
+		// The list contains -1 when no fd is needed. Ignore them.
+		if (new_fd == -1) {
+			continue;
+		}
+
+		if (new_fd == LONG_MAX || new_fd < 0 || new_fd > INT_MAX) {
+			bail("malformed _LIBCONTAINER_MOUNT_FDS env var: fds out of range");
+		}
+
+		receive_fd(sockfd, new_fd);
+	}
+}
+
+void send_mountsources(int sockfd, pid_t child, char *mountsources, size_t mountsources_len)
+{
+	char proc_path[PATH_MAX];
+	int host_mntns_fd;
+	int container_mntns_fd;
+	int fd;
+	int ret;
+
+	// container_linux.go shouldSendMountSources() decides if mount sources
+	// should be pre-opened (O_PATH) and passed via SCM_RIGHTS
+	if (mountsources == NULL)
+		return;
+
+	host_mntns_fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+	if (host_mntns_fd == -1)
+		bail("failed to get current mount namespace");
+
+	if (snprintf(proc_path, PATH_MAX, "/proc/%d/ns/mnt", child) < 0)
+		bail("failed to get mount namespace path");
+
+	container_mntns_fd = open(proc_path, O_RDONLY | O_CLOEXEC);
+	if (container_mntns_fd == -1)
+		bail("failed to get container mount namespace");
+
+	if (setns(container_mntns_fd, CLONE_NEWNS) < 0)
+		bail("failed to setns to container mntns");
+
+	char *mountsources_end = mountsources + mountsources_len;
+	while (mountsources < mountsources_end) {
+		if (mountsources[0] == '\0') {
+			mountsources++;
+			continue;
+		}
+
+		fd = open(mountsources, O_PATH | O_CLOEXEC);
+		if (fd < 0)
+			bail("failed to open mount source %s", mountsources);
+
+		send_fd(sockfd, fd);
+
+		ret = close(fd);
+		if (ret != 0)
+			bail("failed to close mount source fd %d", fd);
+
+		mountsources += strlen(mountsources) + 1;
+	}
+
+	if (setns(host_mntns_fd, CLONE_NEWNS) < 0)
+		bail("failed to setns to host mntns");
+
+	ret = close(host_mntns_fd);
+	if (ret != 0)
+		bail("failed to close host mount namespace fd %d", host_mntns_fd);
+	ret = close(container_mntns_fd);
+	if (ret != 0)
+		bail("failed to close container mount namespace fd %d", container_mntns_fd);
+}
+
 void nsexec(void)
 {
 	int pipenum;
 	jmp_buf env;
 	int sync_child_pipe[2], sync_grandchild_pipe[2];
@@ -863,10 +1062,20 @@ void nsexec(void)
 						sane_kill(stage1_pid, SIGKILL);
 						sane_kill(stage2_pid, SIGKILL);
 						bail("failed to sync with runc: write(pid-JSON)");
 					}
 					break;
+				case SYNC_MOUNTSOURCES_PLS:
+					send_mountsources(syncfd, stage1_pid, config.mountsources,
+							  config.mountsources_len);
+
+					s = SYNC_MOUNTSOURCES_ACK;
+					if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {
+						kill(stage1_pid, SIGKILL);
+						bail("failed to sync with child: write(SYNC_MOUNTSOURCES_ACK)");
+					}
+					break;
 				case SYNC_CHILD_FINISH:
 					write_log(DEBUG, "stage-1 complete");
 					stage1_complete = true;
 					break;
 				default:
@@ -1017,10 +1226,32 @@ void nsexec(void)
 			 */
 			write_log(DEBUG, "unshare remaining namespace (except cgroupns)");
 			if (unshare(config.cloneflags & ~CLONE_NEWCGROUP) < 0)
 				bail("failed to unshare remaining namespaces (except cgroupns)");
 
+			/* Ask our parent to send the mount sources fds. */
+			if (config.mountsources) {
+				s = SYNC_MOUNTSOURCES_PLS;
+				if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {
+					kill(stage2_pid, SIGKILL);
+					bail("failed to sync with parent: write(SYNC_MOUNTSOURCES_PLS)");
+				}
+
+				/* Receive and install all mount sources fds. */
+				receive_mountsources(syncfd);
+
+				/* Parent finished to send the mount sources fds. */
+				if (read(syncfd, &s, sizeof(s)) != sizeof(s)) {
+					kill(stage2_pid, SIGKILL);
+					bail("failed to sync with parent: read(SYNC_MOUNTSOURCES_ACK)");
+				}
+				if (s != SYNC_MOUNTSOURCES_ACK) {
+					kill(stage2_pid, SIGKILL);
+					bail("failed to sync with parent: SYNC_MOUNTSOURCES_ACK: got %u", s);
+				}
+			}
+
 			/*
 			 * TODO: What about non-namespace clone flags that we're dropping here?
 			 *
 			 * We fork again because of PID namespace, setns(2) or unshare(2) don't
 			 * change the PID namespace of the calling process, because doing so
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
index 19bc96d55..781fe9247 100644
--- a/libcontainer/rootfs_linux.go
+++ b/libcontainer/rootfs_linux.go
@@ -34,10 +34,11 @@ type mountConfig struct {
 	root            string
 	label           string
 	cgroup2Path     string
 	rootlessCgroups bool
 	cgroupns        bool
+	fd              *int
 }
 
 // needsSetupDev returns true if /dev needs to be set up.
 func needsSetupDev(config *configs.Config) bool {
 	for _, m := range config.Mounts {
@@ -49,30 +50,41 @@ func needsSetupDev(config *configs.Config) bool {
 }
 
 // prepareRootfs sets up the devices, mount points, and filesystems for use
 // inside a new mount namespace. It doesn't set anything as ro. You must call
 // finalizeRootfs after this function to finish setting up the rootfs.
-func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
+func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig, mountFds []int) (err error) {
 	config := iConfig.Config
 	if err := prepareRoot(config); err != nil {
 		return fmt.Errorf("error preparing rootfs: %w", err)
 	}
 
+	if mountFds != nil && len(mountFds) != len(config.Mounts) {
+		return fmt.Errorf("malformed mountFds slice. Expected size: %v, got: %v. Slice: %v", len(config.Mounts), len(mountFds), mountFds)
+	}
+
 	mountConfig := &mountConfig{
 		root:            config.Rootfs,
 		label:           config.MountLabel,
 		cgroup2Path:     iConfig.Cgroup2Path,
 		rootlessCgroups: iConfig.RootlessCgroups,
 		cgroupns:        config.Namespaces.Contains(configs.NEWCGROUP),
 	}
 	setupDev := needsSetupDev(config)
-	for _, m := range config.Mounts {
+	for i, m := range config.Mounts {
 		for _, precmd := range m.PremountCmds {
 			if err := mountCmd(precmd); err != nil {
 				return fmt.Errorf("error running premount command: %w", err)
 			}
 		}
+
+		// Just before the loop we checked that if not empty, len(mountFds) == len(config.Mounts).
+		// Therefore, we can access mountFds[i] without any concerns.
+		if mountFds != nil && mountFds[i] != -1 {
+			mountConfig.fd = &mountFds[i]
+		}
+
 		if err := mountToRootfs(m, mountConfig); err != nil {
 			return fmt.Errorf("error mounting %q to rootfs at %q: %w", m.Source, m.Destination, err)
 		}
 
 		for _, postcmd := range m.PostmountCmds {
@@ -208,12 +220,17 @@ func mountCmd(cmd configs.Command) error {
 		return fmt.Errorf("%#v failed: %s: %w", cmd, string(out), err)
 	}
 	return nil
 }
 
-func prepareBindMount(m *configs.Mount, rootfs string) error {
-	stat, err := os.Stat(m.Source)
+func prepareBindMount(m *configs.Mount, rootfs string, mountFd *int) error {
+	source := m.Source
+	if mountFd != nil {
+		source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
+	}
+
+	stat, err := os.Stat(source)
 	if err != nil {
 		// error out if the source of a bind mount does not exist as we will be
 		// unable to bind anything to it.
 		return err
 	}
@@ -223,11 +240,11 @@ func prepareBindMount(m *configs.Mount, rootfs string) error {
 	// evil stuff to try to escape the container's rootfs.
 	var dest string
 	if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
 		return err
 	}
-	if err := checkProcMount(rootfs, dest, m.Source); err != nil {
+	if err := checkProcMount(rootfs, dest, source); err != nil {
 		return err
 	}
 	if err := createIfNotExists(dest, stat.IsDir()); err != nil {
 		return err
 	}
@@ -253,13 +270,15 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
 		Destination:      m.Destination,
 		Flags:            defaultMountFlags,
 		Data:             "mode=755",
 		PropagationFlags: m.PropagationFlags,
 	}
+
 	if err := mountToRootfs(tmpfs, c); err != nil {
 		return err
 	}
+
 	for _, b := range binds {
 		if c.cgroupns {
 			subsystemPath := filepath.Join(c.root, b.Destination)
 			if err := os.MkdirAll(subsystemPath, 0o755); err != nil {
 				return err
@@ -345,11 +364,11 @@ func doTmpfsCopyUp(m *configs.Mount, rootfs, mountLabel string) (Err error) {
 
 	// Configure the *host* tmpdir as if it's the container mount. We change
 	// m.Destination since we are going to mount *on the host*.
 	oldDest := m.Destination
 	m.Destination = tmpDir
-	err = mountPropagate(m, "/", mountLabel)
+	err = mountPropagate(m, "/", mountLabel, nil)
 	m.Destination = oldDest
 	if err != nil {
 		return err
 	}
 	defer func() {
@@ -376,10 +395,11 @@ func doTmpfsCopyUp(m *configs.Mount, rootfs, mountLabel string) (Err error) {
 }
 
 func mountToRootfs(m *configs.Mount, c *mountConfig) error {
 	rootfs := c.root
 	mountLabel := c.label
+	mountFd := c.fd
 	dest, err := securejoin.SecureJoin(rootfs, m.Destination)
 	if err != nil {
 		return err
 	}
 
@@ -399,16 +419,16 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
 		}
 		if err := os.MkdirAll(dest, 0o755); err != nil {
 			return err
 		}
 		// Selinux kernels do not support labeling of /proc or /sys
-		return mountPropagate(m, rootfs, "")
+		return mountPropagate(m, rootfs, "", nil)
 	case "mqueue":
 		if err := os.MkdirAll(dest, 0o755); err != nil {
 			return err
 		}
-		if err := mountPropagate(m, rootfs, ""); err != nil {
+		if err := mountPropagate(m, rootfs, "", nil); err != nil {
 			return err
 		}
 		return label.SetFileLabel(dest, mountLabel)
 	case "tmpfs":
 		stat, err := os.Stat(dest)
@@ -419,39 +439,41 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
 		}
 
 		if m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP {
 			err = doTmpfsCopyUp(m, rootfs, mountLabel)
 		} else {
-			err = mountPropagate(m, rootfs, mountLabel)
+			err = mountPropagate(m, rootfs, mountLabel, nil)
 		}
+
 		if err != nil {
 			return err
 		}
+
 		if stat != nil {
 			if err = os.Chmod(dest, stat.Mode()); err != nil {
 				return err
 			}
 		}
 		// Initially mounted rw in mountPropagate, remount to ro if flag set.
 		if m.Flags&unix.MS_RDONLY != 0 {
-			if err := remount(m, rootfs); err != nil {
+			if err := remount(m, rootfs, mountFd); err != nil {
 				return err
 			}
 		}
 		return nil
 	case "bind":
-		if err := prepareBindMount(m, rootfs); err != nil {
+		if err := prepareBindMount(m, rootfs, mountFd); err != nil {
 			return err
 		}
-		if err := mountPropagate(m, rootfs, mountLabel); err != nil {
+		if err := mountPropagate(m, rootfs, mountLabel, mountFd); err != nil {
 			return err
 		}
 		// bind mount won't change mount options, we need remount to make mount options effective.
 		// first check that we have non-default options required before attempting a remount
 		if m.Flags&^(unix.MS_REC|unix.MS_REMOUNT|unix.MS_BIND) != 0 {
 			// only remount if unique mount options are set
-			if err := remount(m, rootfs); err != nil {
+			if err := remount(m, rootfs, mountFd); err != nil {
 				return err
 			}
 		}
 
 		if m.Relabel != "" {
@@ -473,11 +495,11 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
 			return err
 		}
 		if err := os.MkdirAll(dest, 0o755); err != nil {
 			return err
 		}
-		return mountPropagate(m, rootfs, mountLabel)
+		return mountPropagate(m, rootfs, mountLabel, mountFd)
 	}
 	return nil
 }
 
 func getCgroupMounts(m *configs.Mount) ([]*configs.Mount, error) {
@@ -1035,19 +1057,24 @@ func maskPath(path string, mountLabel string) error {
 func writeSystemProperty(key, value string) error {
 	keyPath := strings.Replace(key, ".", "/", -1)
 	return ioutil.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0o644)
 }
 
-func remount(m *configs.Mount, rootfs string) error {
+func remount(m *configs.Mount, rootfs string, mountFd *int) error {
+	source := m.Source
+	if mountFd != nil {
+		source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
+	}
+
 	return utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
-		return mount(m.Source, m.Destination, procfd, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "")
+		return mount(source, m.Destination, procfd, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "")
 	})
 }
 
 // Do the mount operation followed by additional mounts required to take care
 // of propagation flags. This will always be scoped inside the container rootfs.
-func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error {
+func mountPropagate(m *configs.Mount, rootfs string, mountLabel string, mountFd *int) error {
 	var (
 		data  = label.FormatMountLabel(m.Data, mountLabel)
 		flags = m.Flags
 	)
 	// Delay mounting the filesystem read-only if we need to do further
@@ -1060,12 +1087,17 @@ func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error {
 
 	// Because the destination is inside a container path which might be
 	// mutating underneath us, we verify that we are actually going to mount
 	// inside the container with WithProcfd() -- mounting through a procfd
 	// mounts on the target.
+	source := m.Source
+	if mountFd != nil {
+		source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
+	}
+
 	if err := utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
-		return mount(m.Source, m.Destination, procfd, m.Device, uintptr(flags), data)
+		return mount(source, m.Destination, procfd, m.Device, uintptr(flags), data)
 	}); err != nil {
 		return err
 	}
 	// We have to apply mount propagation flags in a separate WithProcfd() call
 	// because the previous call invalidates the passed procfd -- the mount
diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go
index cd722c7e7..f59d8fa21 100644
--- a/libcontainer/standard_init_linux.go
+++ b/libcontainer/standard_init_linux.go
@@ -24,10 +24,11 @@ type linuxStandardInit struct {
 	pipe          *os.File
 	consoleSocket *os.File
 	parentPid     int
 	fifoFd        int
 	logFd         int
+	mountFds      []int
 	config        *initConfig
 }
 
 func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) {
 	var newperms uint32
@@ -85,13 +86,27 @@ func (l *linuxStandardInit) Init() error {
 		return err
 	}
 
 	// initialises the labeling system
 	selinux.GetEnabled()
-	if err := prepareRootfs(l.pipe, l.config); err != nil {
+
+	// We don't need the mountFds after prepareRootfs() nor if it fails.
+	err := prepareRootfs(l.pipe, l.config, l.mountFds)
+	for _, m := range l.mountFds {
+		if m == -1 {
+			continue
+		}
+
+		if err := unix.Close(m); err != nil {
+			return fmt.Errorf("Unable to close mountFds fds: %w", err)
+		}
+	}
+
+	if err != nil {
 		return err
 	}
+
 	// Set up the console. This has to be done *before* we finalize the rootfs,
 	// but *after* we've given the user the chance to set up all of the mounts
 	// they wanted.
 	if l.config.CreateConsole {
 		if err := setupConsole(l.consoleSocket, l.config, true); err != nil {

Vulnerable code: 	data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps)
	data, err := c.bootstrapData(0, state.NamespacePaths)
		if err := prepareBindMount(m, c.config.Rootfs); err != nil {
func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string) (io.Reader, error) {
	i, err := newContainerInit(it, pipe, consoleSocket, fifofd, logPipeFd)
func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd, logFd int) (initer, error) {
func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
	for _, m := range config.Mounts {
func prepareBindMount(m *configs.Mount, rootfs string) error {
	stat, err := os.Stat(m.Source)
	if err := checkProcMount(rootfs, dest, m.Source); err != nil {
	err = mountPropagate(m, "/", mountLabel)
		return mountPropagate(m, rootfs, "")
		if err := mountPropagate(m, rootfs, ""); err != nil {
			err = mountPropagate(m, rootfs, mountLabel)
			if err := remount(m, rootfs); err != nil {
		if err := prepareBindMount(m, rootfs); err != nil {
		if err := mountPropagate(m, rootfs, mountLabel); err != nil {
			if err := remount(m, rootfs); err != nil {
		return mountPropagate(m, rootfs, mountLabel)
func remount(m *configs.Mount, rootfs string) error {
		return mount(m.Source, m.Destination, procfd, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "")
func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error {
		return mount(m.Source, m.Destination, procfd, m.Device, uintptr(flags), data)
	if err := prepareRootfs(l.pipe, l.config); err != nil {
Fixed code: import "golang.org/x/sys/unix"


func (m *Mount) IsBind() bool {
	return m.Flags&unix.MS_BIND != 0
}
// shouldSendMountSources says whether the child process must setup bind mounts with
// the source pre-opened (O_PATH) in the host user namespace.
// See https://github.com/opencontainers/runc/issues/2484
func (c *linuxContainer) shouldSendMountSources() bool {
	// Passing the mount sources via SCM_RIGHTS is only necessary when
	// both userns and mntns are active.
	if !c.config.Namespaces.Contains(configs.NEWUSER) ||
		!c.config.Namespaces.Contains(configs.NEWNS) {
		return false
	}

	// nsexec.c send_mountsources() requires setns(mntns) capabilities
	// CAP_SYS_CHROOT and CAP_SYS_ADMIN.
	if c.config.RootlessEUID {
		return false
	}

	// We need to send sources if there are bind-mounts.
	for _, m := range c.config.Mounts {
		if m.IsBind() {
			return true
		}
	}

	return false
}

	data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps, initStandard)

	if c.shouldSendMountSources() {
		// Elements on this slice will be paired with mounts (see StartInitialization() and
		// prepareRootfs()). This slice MUST have the same size as c.config.Mounts.
		mountFds := make([]int, len(c.config.Mounts))
		for i, m := range c.config.Mounts {
			if !m.IsBind() {
				// Non bind-mounts do not use an fd.
				mountFds[i] = -1
				continue
			}

			// The fd passed here will not be used: nsexec.c will overwrite it with dup3(). We just need
			// to allocate a fd so that we know the number to pass in the environment variable. The fd
			// must not be closed before cmd.Start(), so we reuse messageSockPair.child because the
			// lifecycle of that fd is already taken care of.
			cmd.ExtraFiles = append(cmd.ExtraFiles, messageSockPair.child)
			mountFds[i] = stdioFdCount + len(cmd.ExtraFiles) - 1
		}

		mountFdsJson, err := json.Marshal(mountFds)
		if err != nil {
			return nil, fmt.Errorf("Error creating _LIBCONTAINER_MOUNT_FDS: %w", err)
		}

		cmd.Env = append(cmd.Env,
			"_LIBCONTAINER_MOUNT_FDS="+string(mountFdsJson),
		)
	}

	data, err := c.bootstrapData(0, state.NamespacePaths, initSetns)
		// TODO: pass something else than nil? Not sure if criu is
		// impacted by issue #2484
		if err := prepareBindMount(m, c.config.Rootfs, nil); err != nil {
func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string, it initType) (io.Reader, error) {
	// Bind mount source to open.
	if it == initStandard && c.shouldSendMountSources() {
		var mounts []byte
		for _, m := range c.config.Mounts {
			if m.IsBind() {
				mounts = append(mounts, []byte(m.Source)...)
			}
			mounts = append(mounts, byte(0))
		}

		r.AddData(&Bytemsg{
			Type:  MountSourcesAttr,
			Value: mounts,
		})
	}

	// Get mount files (O_PATH).
	mountFds, err := parseMountFds()
	if err != nil {
		return err
	}

	i, err := newContainerInit(it, pipe, consoleSocket, fifofd, logPipeFd, mountFds)

func parseMountFds() ([]int, error) {
	fdsJson := os.Getenv("_LIBCONTAINER_MOUNT_FDS")
	if fdsJson == "" {
		// Always return the nil slice if no fd is present.
		return nil, nil
	}

	var mountFds []int
	if err := json.Unmarshal([]byte(fdsJson), &mountFds); err != nil {
		return nil, fmt.Errorf("Error unmarshalling _LIBCONTAINER_MOUNT_FDS: %w", err)
	}

	return mountFds, nil
}
func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd, logFd int, mountFds []int) (initer, error) {
		// mountFds must be nil in this case. We don't mount while doing runc exec.
		if mountFds != nil {
			return nil, errors.New("mountFds must be nil. Can't mount while doing runc exec.")
		}

			mountFds:      mountFds,
	MountSourcesAttr uint16 = 27290
#include <limits.h>
	SYNC_MOUNTSOURCES_PLS = 0x46,	/* Tell parent to send mount sources by SCM_RIGHTS. */
	SYNC_MOUNTSOURCES_ACK = 0x47,	/* All mount sources have been sent. */

	/* Mount sources opened outside the container userns. */
	char *mountsources;
	size_t mountsources_len;
#define MOUNT_SOURCES_ATTR	27290
		case MOUNT_SOURCES_ATTR:
			config->mountsources = current;
			config->mountsources_len = payload_len;
			break;
void receive_fd(int sockfd, int new_fd)
{
	int bytes_read;
	struct msghdr msg = { };
	struct cmsghdr *cmsg;
	struct iovec iov = { };
	char null_byte = '\0';
	int ret;
	int fd_count;
	int *fd_payload;

	iov.iov_base = &null_byte;
	iov.iov_len = 1;

	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	msg.msg_controllen = CMSG_SPACE(sizeof(int));
	msg.msg_control = malloc(msg.msg_controllen);
	if (msg.msg_control == NULL) {
		bail("Can't allocate memory to receive fd.");
	}

	memset(msg.msg_control, 0, msg.msg_controllen);

	bytes_read = recvmsg(sockfd, &msg, 0);
	if (bytes_read != 1)
		bail("failed to receive fd from unix socket %d", sockfd);
	if (msg.msg_flags & MSG_CTRUNC)
		bail("received truncated control message from unix socket %d", sockfd);

	cmsg = CMSG_FIRSTHDR(&msg);
	if (!cmsg)
		bail("received message from unix socket %d without control message", sockfd);

	if (cmsg->cmsg_level != SOL_SOCKET)
		bail("received unknown control message from unix socket %d: cmsg_level=%d", sockfd, cmsg->cmsg_level);

	if (cmsg->cmsg_type != SCM_RIGHTS)
		bail("received unknown control message from unix socket %d: cmsg_type=%d", sockfd, cmsg->cmsg_type);

	fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
	if (fd_count != 1)
		bail("received control message from unix socket %d with too many fds: %d", sockfd, fd_count);

	fd_payload = (int *)CMSG_DATA(cmsg);
	ret = dup3(*fd_payload, new_fd, O_CLOEXEC);
	if (ret < 0)
		bail("cannot dup3 fd %d to %d", *fd_payload, new_fd);

	free(msg.msg_control);

	ret = close(*fd_payload);
	if (ret < 0)
		bail("cannot close fd %d", *fd_payload);
}

void send_fd(int sockfd, int fd)
{
	int bytes_written;
	struct msghdr msg = { };
	struct cmsghdr *cmsg;
	struct iovec iov[1] = { };
	char null_byte = '\0';

	iov[0].iov_base = &null_byte;
	iov[0].iov_len = 1;

	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	/* We send only one fd as specified by cmsg->cmsg_len below, even
	 * though msg.msg_controllen might have more space due to alignment. */
	msg.msg_controllen = CMSG_SPACE(sizeof(int));
	msg.msg_control = malloc(msg.msg_controllen);
	if (msg.msg_control == NULL) {
		bail("Can't allocate memory to send fd.");
	}

	memset(msg.msg_control, 0, msg.msg_controllen);

	cmsg = CMSG_FIRSTHDR(&msg);
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	cmsg->cmsg_len = CMSG_LEN(sizeof(int));
	memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));

	bytes_written = sendmsg(sockfd, &msg, 0);

	free(msg.msg_control);

	if (bytes_written != 1)
		bail("failed to send fd %d via unix socket %d", fd, sockfd);
}

void receive_mountsources(int sockfd)
{
	char *mount_fds, *endp;
	long new_fd;

	// This env var must be a json array of ints.
	mount_fds = getenv("_LIBCONTAINER_MOUNT_FDS");

	if (mount_fds[0] != '[') {
		bail("malformed _LIBCONTAINER_MOUNT_FDS env var: missing '['");
	}
	mount_fds++;

	for (endp = mount_fds; *endp != ']'; mount_fds = endp + 1) {
		new_fd = strtol(mount_fds, &endp, 10);
		if (endp == mount_fds) {
			bail("malformed _LIBCONTAINER_MOUNT_FDS env var: not a number");
		}
		if (*endp == '\0') {
			bail("malformed _LIBCONTAINER_MOUNT_FDS env var: missing ]");
		}
		// The list contains -1 when no fd is needed. Ignore them.
		if (new_fd == -1) {
			continue;
		}

		if (new_fd == LONG_MAX || new_fd < 0 || new_fd > INT_MAX) {
			bail("malformed _LIBCONTAINER_MOUNT_FDS env var: fds out of range");
		}

		receive_fd(sockfd, new_fd);
	}
}

void send_mountsources(int sockfd, pid_t child, char *mountsources, size_t mountsources_len)
{
	char proc_path[PATH_MAX];
	int host_mntns_fd;
	int container_mntns_fd;
	int fd;
	int ret;

	// container_linux.go shouldSendMountSources() decides if mount sources
	// should be pre-opened (O_PATH) and passed via SCM_RIGHTS
	if (mountsources == NULL)
		return;

	host_mntns_fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
	if (host_mntns_fd == -1)
		bail("failed to get current mount namespace");

	if (snprintf(proc_path, PATH_MAX, "/proc/%d/ns/mnt", child) < 0)
		bail("failed to get mount namespace path");

	container_mntns_fd = open(proc_path, O_RDONLY | O_CLOEXEC);
	if (container_mntns_fd == -1)
		bail("failed to get container mount namespace");

	if (setns(container_mntns_fd, CLONE_NEWNS) < 0)
		bail("failed to setns to container mntns");

	char *mountsources_end = mountsources + mountsources_len;
	while (mountsources < mountsources_end) {
		if (mountsources[0] == '\0') {
			mountsources++;
			continue;
		}

		fd = open(mountsources, O_PATH | O_CLOEXEC);
		if (fd < 0)
			bail("failed to open mount source %s", mountsources);

		send_fd(sockfd, fd);

		ret = close(fd);
		if (ret != 0)
			bail("failed to close mount source fd %d", fd);

		mountsources += strlen(mountsources) + 1;
	}

	if (setns(host_mntns_fd, CLONE_NEWNS) < 0)
		bail("failed to setns to host mntns");

	ret = close(host_mntns_fd);
	if (ret != 0)
		bail("failed to close host mount namespace fd %d", host_mntns_fd);
	ret = close(container_mntns_fd);
	if (ret != 0)
		bail("failed to close container mount namespace fd %d", container_mntns_fd);
}

				case SYNC_MOUNTSOURCES_PLS:
					send_mountsources(syncfd, stage1_pid, config.mountsources,
							  config.mountsources_len);

					s = SYNC_MOUNTSOURCES_ACK;
					if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {
						kill(stage1_pid, SIGKILL);
						bail("failed to sync with child: write(SYNC_MOUNTSOURCES_ACK)");
					}
					break;
			/* Ask our parent to send the mount sources fds. */
			if (config.mountsources) {
				s = SYNC_MOUNTSOURCES_PLS;
				if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {
					kill(stage2_pid, SIGKILL);
					bail("failed to sync with parent: write(SYNC_MOUNTSOURCES_PLS)");
				}

				/* Receive and install all mount sources fds. */
				receive_mountsources(syncfd);

				/* Parent finished to send the mount sources fds. */
				if (read(syncfd, &s, sizeof(s)) != sizeof(s)) {
					kill(stage2_pid, SIGKILL);
					bail("failed to sync with parent: read(SYNC_MOUNTSOURCES_ACK)");
				}
				if (s != SYNC_MOUNTSOURCES_ACK) {
					kill(stage2_pid, SIGKILL);
					bail("failed to sync with parent: SYNC_MOUNTSOURCES_ACK: got %u", s);
				}
			}

	fd              *int
func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig, mountFds []int) (err error) {
	if mountFds != nil && len(mountFds) != len(config.Mounts) {
		return fmt.Errorf("malformed mountFds slice. Expected size: %v, got: %v. Slice: %v", len(config.Mounts), len(mountFds), mountFds)
	}

	for i, m := range config.Mounts {

		// Just before the loop we checked that if not empty, len(mountFds) == len(config.Mounts).
		// Therefore, we can access mountFds[i] without any concerns.
		if mountFds != nil && mountFds[i] != -1 {
			mountConfig.fd = &mountFds[i]
		}

func prepareBindMount(m *configs.Mount, rootfs string, mountFd *int) error {
	source := m.Source
	if mountFd != nil {
		source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
	}

	stat, err := os.Stat(source)
	if err := checkProcMount(rootfs, dest, source); err != nil {


	err = mountPropagate(m, "/", mountLabel, nil)
	mountFd := c.fd
		return mountPropagate(m, rootfs, "", nil)
		if err := mountPropagate(m, rootfs, "", nil); err != nil {
			err = mountPropagate(m, rootfs, mountLabel, nil)


			if err := remount(m, rootfs, mountFd); err != nil {
		if err := prepareBindMount(m, rootfs, mountFd); err != nil {
		if err := mountPropagate(m, rootfs, mountLabel, mountFd); err != nil {
			if err := remount(m, rootfs, mountFd); err != nil {
		return mountPropagate(m, rootfs, mountLabel, mountFd)
func remount(m *configs.Mount, rootfs string, mountFd *int) error {
	source := m.Source
	if mountFd != nil {
		source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
	}

		return mount(source, m.Destination, procfd, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "")
func mountPropagate(m *configs.Mount, rootfs string, mountLabel string, mountFd *int) error {
	source := m.Source
	if mountFd != nil {
		source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
	}

		return mount(source, m.Destination, procfd, m.Device, uintptr(flags), data)
	mountFds      []int

	// We don't need the mountFds after prepareRootfs() nor if it fails.
	err := prepareRootfs(l.pipe, l.config, l.mountFds)
	for _, m := range l.mountFds {
		if m == -1 {
			continue
		}

		if err := unix.Close(m); err != nil {
			return fmt.Errorf("Unable to close mountFds fds: %w", err)
		}
	}

	if err != nil {