[lxc-devel] [lxd/master] forkfile: port to using pidfds

brauner on Github lxc-bot at linuxcontainers.org
Thu May 14 12:49:49 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 773 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200514/544dc468/attachment.bin>
-------------- next part --------------
From 014db5f34e9135f108b4de9c2158d9080cc4ffab Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 14 May 2020 10:45:08 +0200
Subject: [PATCH] forkfile: port to using pidfds

This ports all of the forkfile() handlers to verify the validity of the target
process via pidfds. This hardens our codebase and specifically means that we
can't accidently operate on a wrong process when under heavy contention.

I'm going to port all subcommand but want to do so step by step to minimize the
risk of subtle breakages. Once they're all switched over I will do another
cleanup sweep.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/daemon.go                      |  7 +++
 lxd/include/macro.h                |  4 ++
 lxd/instance/drivers/driver_lxc.go | 67 +++++++++++++++++++++++--
 lxd/main_checkfeature.go           | 38 ++++++++++++++
 lxd/main_forkfile.go               | 58 +++++++++++++---------
 lxd/main_forksyscall.go            | 12 +----
 lxd/main_nsexec.go                 | 80 +++++++++++++++++++++++++++---
 lxd/seccomp/seccomp.go             | 34 ++++++++++---
 lxd/sys/os.go                      |  1 +
 shared/util.go                     | 10 ++--
 10 files changed, 253 insertions(+), 58 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 08d2996ac4..e30dccc677 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -605,6 +605,13 @@ func (d *Daemon) init() error {
 		logger.Infof(" - netnsid-based network retrieval: no")
 	}
 
+	d.os.PidFds = canUsePidFds()
+	if d.os.PidFds {
+		logger.Infof(" - pidfds: yes")
+	} else {
+		logger.Infof(" - pidfds: no")
+	}
+
 	d.os.UeventInjection = canUseUeventInjection()
 	if d.os.UeventInjection {
 		logger.Infof(" - uevent injection: yes")
diff --git a/lxd/include/macro.h b/lxd/include/macro.h
index ece46bc2c7..87d7d41fb2 100644
--- a/lxd/include/macro.h
+++ b/lxd/include/macro.h
@@ -268,4 +268,8 @@ enum {
 		__internal_ret__;                             \
 	})
 
+#ifndef P_PIDFD
+#define P_PIDFD 3
+#endif
+
 #endif /* __LXC_MACRO_H */
diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go
index 53346846ad..e910dbe3ab 100644
--- a/lxd/instance/drivers/driver_lxc.go
+++ b/lxd/instance/drivers/driver_lxc.go
@@ -5169,14 +5169,26 @@ func (c *lxc) FileExists(path string) error {
 		}
 	}
 
+	initPID := c.InitPID()
+	pidFd := -1
+	var inheritFd []*os.File = nil
+	if c.state.OS.PidFds {
+		pidFd = c.InitPidFd()
+		if pidFd >= 0 {
+			inheritFd = []*os.File{os.NewFile(uintptr(pidFd), fmt.Sprintf("%d", initPID))}
+		}
+	}
+
 	// Check if the file exists in the container
 	_, stderr, err := shared.RunCommandSplit(
 		nil,
+		inheritFd,
 		c.state.OS.ExecPath,
 		"forkfile",
 		"exists",
 		c.RootfsPath(),
-		fmt.Sprintf("%d", c.InitPID()),
+		fmt.Sprintf("%d", initPID),
+		fmt.Sprintf("%d", pidFd),
 		path,
 	)
 
@@ -5224,14 +5236,26 @@ func (c *lxc) FilePull(srcpath string, dstpath string) (int64, int64, os.FileMod
 		}
 	}
 
+	initPID := c.InitPID()
+	pidFd := -1
+	var inheritFd []*os.File = nil
+	if c.state.OS.PidFds {
+		pidFd = c.InitPidFd()
+		if pidFd >= 0 {
+			inheritFd = []*os.File{os.NewFile(uintptr(pidFd), fmt.Sprintf("%d", initPID))}
+		}
+	}
+
 	// Get the file from the container
 	_, stderr, err := shared.RunCommandSplit(
 		nil,
+		inheritFd,
 		c.state.OS.ExecPath,
 		"forkfile",
 		"pull",
 		c.RootfsPath(),
-		fmt.Sprintf("%d", c.InitPID()),
+		fmt.Sprintf("%d", initPID),
+		fmt.Sprintf("%d", pidFd),
 		srcpath,
 		dstpath,
 	)
@@ -5376,14 +5400,26 @@ func (c *lxc) FilePush(fileType string, srcpath string, dstpath string, uid int6
 		defaultMode = 0750
 	}
 
+	initPID := c.InitPID()
+	pidFd := -1
+	var inheritFd []*os.File = nil
+	if c.state.OS.PidFds {
+		pidFd = c.InitPidFd()
+		if pidFd >= 0 {
+			inheritFd = []*os.File{os.NewFile(uintptr(pidFd), fmt.Sprintf("%d", initPID))}
+		}
+	}
+
 	// Push the file to the container
 	_, stderr, err := shared.RunCommandSplit(
 		nil,
+		inheritFd,
 		c.state.OS.ExecPath,
 		"forkfile",
 		"push",
 		c.RootfsPath(),
-		fmt.Sprintf("%d", c.InitPID()),
+		fmt.Sprintf("%d", initPID),
+		fmt.Sprintf("%d", pidFd),
 		srcpath,
 		dstpath,
 		fileType,
@@ -5447,14 +5483,26 @@ func (c *lxc) FileRemove(path string) error {
 		}
 	}
 
+	initPID := c.InitPID()
+	pidFd := -1
+	var inheritFd []*os.File = nil
+	if c.state.OS.PidFds {
+		pidFd = c.InitPidFd()
+		if pidFd >= 0 {
+			inheritFd = []*os.File{os.NewFile(uintptr(pidFd), fmt.Sprintf("%d", initPID))}
+		}
+	}
+
 	// Remove the file from the container
 	_, stderr, err := shared.RunCommandSplit(
 		nil,
+		inheritFd,
 		c.state.OS.ExecPath,
 		"forkfile",
 		"remove",
 		c.RootfsPath(),
-		fmt.Sprintf("%d", c.InitPID()),
+		fmt.Sprintf("%d", initPID),
+		fmt.Sprintf("%d", pidFd),
 		path,
 	)
 
@@ -6518,6 +6566,17 @@ func (c *lxc) InitPID() int {
 	return c.c.InitPid()
 }
 
+// InitPidFd returns pidfd of init process.
+func (c *lxc) InitPidFd() int {
+	// Load the go-lxc struct
+	err := c.initLXC(false)
+	if err != nil {
+		return -1
+	}
+
+	return c.c.InitPidFd()
+}
+
 // LocalConfig returns local config.
 func (c *lxc) LocalConfig() map[string]string {
 	return c.localConfig
diff --git a/lxd/main_checkfeature.go b/lxd/main_checkfeature.go
index 375b632250..6213675581 100644
--- a/lxd/main_checkfeature.go
+++ b/lxd/main_checkfeature.go
@@ -26,19 +26,24 @@ import (
 #include <linux/filter.h>
 #include <linux/audit.h>
 #include <sys/ptrace.h>
+#include <sys/wait.h>
 
 #include "../shared/netutils/netns_getifaddrs.c"
 #include "include/compiler.h"
 #include "include/lxd_seccomp.h"
 #include "include/memory_utils.h"
+#include "include/syscall_numbers.h"
 
 __ro_after_init bool netnsid_aware = false;
+__ro_after_init bool pidfd_aware = false;
 __ro_after_init bool uevent_aware = false;
 __ro_after_init int seccomp_notify_aware = 0;
 __ro_after_init char errbuf[4096];
 
 extern int can_inject_uevent(const char *uevent, size_t len);
 extern int wait_for_pid(pid_t pid);
+extern int pidfd_open(pid_t pid, unsigned int flags);
+extern int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags);
 
 static int netns_set_nsid(int fd)
 {
@@ -291,11 +296,40 @@ static void is_seccomp_notify_aware(void)
 
 }
 
+static void is_pidfd_aware(void)
+{
+	__do_close int pidfd = -EBADF;
+	int ret;
+
+	pidfd = pidfd_open(getpid(), 0);
+	if (pidfd < 0)
+		return;
+
+	// We don't care whether or not children were in a waitable state. We
+	// just care whether waitid() recognizes P_PIDFD.
+	ret = waitid(P_PIDFD, pidfd, NULL,
+		    // Type of children to wait for.
+		    __WALL |
+		    // How to wait for them.
+		    WNOHANG | WNOWAIT |
+		    // What state to wait for.
+		    WEXITED | WSTOPPED | WCONTINUED);
+	if (ret < 0 && errno != ECHILD)
+		return;
+
+	ret = pidfd_send_signal(pidfd, 0, NULL, 0);
+	if (ret)
+		return;
+
+	pidfd_aware = true;
+}
+
 void checkfeature(void)
 {
 	__do_close int hostnetns_fd = -EBADF, newnetns_fd = -EBADF;
 
 	is_netnsid_aware(&hostnetns_fd, &newnetns_fd);
+	is_pidfd_aware();
 	is_uevent_aware();
 	is_seccomp_notify_aware();
 
@@ -330,3 +364,7 @@ func canUseSeccompListener() bool {
 func canUseSeccompListenerContinue() bool {
 	return bool(C.seccomp_notify_aware == 2)
 }
+
+func canUsePidFds() bool {
+	return bool(C.pidfd_aware)
+}
diff --git a/lxd/main_forkfile.go b/lxd/main_forkfile.go
index 1b27e0ca34..1c6ca3a13e 100644
--- a/lxd/main_forkfile.go
+++ b/lxd/main_forkfile.go
@@ -25,8 +25,9 @@ import (
 
 extern char* advance_arg(bool required);
 extern void error(char *msg);
-extern void attach_userns(int pid);
-extern int dosetns(int pid, char *nstype);
+extern void attach_userns_fd(int ns_fd);
+extern bool setnsat(int ns_fd, const char *ns);
+extern int pidfd_nsfd(int pidfd, pid_t pid);
 
 int copy(int target, int source, bool append)
 {
@@ -58,7 +59,7 @@ int copy(int target, int source, bool append)
 	return 0;
 }
 
-int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, char *type, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, mode_t defaultMode, bool append) {
+int manip_file_in_ns(char *rootfs, int ns_fd, char *host, char *container, bool is_put, char *type, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, mode_t defaultMode, bool append) {
 	__do_close int host_fd = -EBADF, container_fd = -EBADF;
 	int ret = -1;
 	int container_open_flags;
@@ -80,10 +81,10 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 		}
 	}
 
-	if (pid > 0) {
-		attach_userns(pid);
+	if (ns_fd >= 0) {
+		attach_userns_fd(ns_fd);
 
-		if (dosetns(pid, "mnt") < 0) {
+		if (!setnsat(ns_fd, "mnt")) {
 			error("error: setns");
 			return -1;
 		}
@@ -272,7 +273,7 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 	return ret;
 }
 
-void forkdofile(bool is_put, char *rootfs, pid_t pid) {
+void forkdofile(bool is_put, char *rootfs, int ns_fd) {
 	char *cur = NULL;
 
 	uid_t uid = 0;
@@ -321,18 +322,18 @@ void forkdofile(bool is_put, char *rootfs, pid_t pid) {
 
 	printf("%d: %s to %s\n", is_put, source, target);
 
-	_exit(manip_file_in_ns(rootfs, pid, source, target, is_put, type, uid, gid, mode, defaultUid, defaultGid, defaultMode, append));
+	_exit(manip_file_in_ns(rootfs, ns_fd, source, target, is_put, type, uid, gid, mode, defaultUid, defaultGid, defaultMode, append));
 }
 
-void forkcheckfile(char *rootfs, pid_t pid) {
+void forkcheckfile(char *rootfs, int ns_fd) {
 	char *path = NULL;
 
 	path = advance_arg(true);
 
-	if (pid > 0) {
-		attach_userns(pid);
+	if (ns_fd >= 0) {
+		attach_userns_fd(ns_fd);
 
-		if (dosetns(pid, "mnt") < 0) {
+		if (!setnsat(ns_fd, "mnt")) {
 			error("error: setns");
 			_exit(1);
 		}
@@ -356,16 +357,16 @@ void forkcheckfile(char *rootfs, pid_t pid) {
 	_exit(0);
 }
 
-void forkremovefile(char *rootfs, pid_t pid) {
+void forkremovefile(char *rootfs, int ns_fd) {
 	char *path = NULL;
 	struct stat sb;
 
 	path = advance_arg(true);
 
-	if (pid > 0) {
-		attach_userns(pid);
+	if (ns_fd >= 0) {
+		attach_userns_fd(ns_fd);
 
-		if (dosetns(pid, "mnt") < 0) {
+		if (!setnsat(ns_fd, "mnt")) {
 			error("error: setns");
 			_exit(1);
 		}
@@ -401,8 +402,11 @@ void forkremovefile(char *rootfs, pid_t pid) {
 	_exit(0);
 }
 
+#define PIDFD_NR 3
+
 void forkfile(void)
 {
+	int ns_fd = -EBADF;
 	char *command = NULL;
 	char *rootfs = NULL;
 	pid_t pid = 0;
@@ -428,15 +432,21 @@ void forkfile(void)
 		_exit(1);
 	}
 
+	if (pid > 0) {
+		ns_fd = pidfd_nsfd(atoi(advance_arg(true), pid);
+		if (ns_fd < 0)
+			_exit(1);
+	}
+
 	// Call the subcommands
 	if (strcmp(command, "push") == 0) {
-		forkdofile(true, rootfs, pid);
+		forkdofile(true, rootfs, ns_fd);
 	} else if (strcmp(command, "pull") == 0) {
-		forkdofile(false, rootfs, pid);
+		forkdofile(false, rootfs, ns_fd);
 	} else if (strcmp(command, "exists") == 0) {
-		forkcheckfile(rootfs, pid);
+		forkcheckfile(rootfs, ns_fd);
 	} else if (strcmp(command, "remove") == 0) {
-		forkremovefile(rootfs, pid);
+		forkremovefile(rootfs, ns_fd);
 	}
 }
 */
@@ -462,28 +472,28 @@ func (c *cmdForkfile) Command() *cobra.Command {
 
 	// pull
 	cmdPull := &cobra.Command{}
-	cmdPull.Use = "pull <rootfs> <PID> <source> <destination>"
+	cmdPull.Use = "pull <rootfs> <PID> <PidFd> <source> <destination>"
 	cmdPull.Args = cobra.ExactArgs(4)
 	cmdPull.RunE = c.Run
 	cmd.AddCommand(cmdPull)
 
 	// push
 	cmdPush := &cobra.Command{}
-	cmdPush.Use = "push <rootfs> <PID> <source> <destination> <type> <uid> <gid> <mode> <root uid> <root gid> <default mode> <write type>"
+	cmdPush.Use = "push <rootfs> <PID> <PidFd> <source> <destination> <type> <uid> <gid> <mode> <root uid> <root gid> <default mode> <write type>"
 	cmdPush.Args = cobra.ExactArgs(12)
 	cmdPush.RunE = c.Run
 	cmd.AddCommand(cmdPush)
 
 	// exists
 	cmdExists := &cobra.Command{}
-	cmdExists.Use = "exists <rootfs> <PID> <path>"
+	cmdExists.Use = "exists <rootfs> <PID> <PidFd> <path>"
 	cmdExists.Args = cobra.ExactArgs(3)
 	cmdExists.RunE = c.Run
 	cmd.AddCommand(cmdExists)
 
 	// remove
 	cmdRemove := &cobra.Command{}
-	cmdRemove.Use = "remove <rootfs> <PID> <path>"
+	cmdRemove.Use = "remove <rootfs> <PID> <PidFd> <path>"
 	cmdRemove.Args = cobra.ExactArgs(3)
 	cmdRemove.RunE = c.Run
 	cmd.AddCommand(cmdRemove)
diff --git a/lxd/main_forksyscall.go b/lxd/main_forksyscall.go
index feb68a39e9..b5f1475c4f 100644
--- a/lxd/main_forksyscall.go
+++ b/lxd/main_forksyscall.go
@@ -32,6 +32,7 @@ import (
 extern void attach_userns(int pid);
 extern char* advance_arg(bool required);
 extern int dosetns(int pid, char *nstype);
+extern bool setnsat(int ns_fd, const char *ns);
 
 static inline bool same_fsinfo(struct stat *s1, struct stat *s2,
 			       struct statfs *sfs1, struct statfs *sfs2)
@@ -207,17 +208,6 @@ static void mknod_emulate(void)
 
 const char *ns_names[] = { "user", "pid", "uts", "ipc", "net", "cgroup", NULL };
 
-static bool setnsat(int ns_fd, const char *ns)
-{
-	__do_close int fd = -EBADF;
-
-	fd = openat(ns_fd, ns, O_RDONLY | O_CLOEXEC);
-	if (fd < 0)
-		return false;
-
-	return setns(fd, 0) == 0;
-}
-
 static bool change_creds(int ns_fd, cap_t caps, uid_t nsuid, gid_t nsgid, uid_t nsfsuid, gid_t nsfsgid)
 {
 	__do_close int fd = -EBADF;
diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
index 279f7f8411..a4062d1589 100644
--- a/lxd/main_nsexec.go
+++ b/lxd/main_nsexec.go
@@ -37,6 +37,7 @@ package main
 #include <unistd.h>
 
 #include "include/memory_utils.h"
+#include "include/syscall_numbers.h"
 
 // External functions
 extern void checkfeature();
@@ -137,9 +138,12 @@ int dosetns_file(char *file, char *nstype) {
 	return 0;
 }
 
-static int preserve_ns(const int pid, const char *ns)
+static int preserve_ns(pid_t pid, int ns_fd, const char *ns)
 {
 	int ret;
+	if (ns_fd >= 0)
+		return openat(ns_fd, ns, O_RDONLY | O_CLOEXEC);
+
 // 5 /proc + 21 /int_as_str + 3 /ns + 20 /NS_NAME + 1 \0
 #define __NS_PATH_LEN 50
 	char path[__NS_PATH_LEN];
@@ -158,21 +162,22 @@ static int preserve_ns(const int pid, const char *ns)
 }
 
 // in_same_namespace - Check whether two processes are in the same namespace.
-// @pid1 - PID of the first process.
-// @pid2 - PID of the second process.
+// @pid1	- PID of the first process.
+// @pid2	- PID of the second process.
+// @ns_fd2	- ns_fd @pid2.
 // @ns   - Name of the namespace to check. Must correspond to one of the names
 //         for the namespaces as shown in /proc/<pid/ns/
 //
 // If the two processes are not in the same namespace returns an fd to the
 // namespace of the second process identified by @pid2. If the two processes are
 // in the same namespace returns -EINVAL, -1 if an error occurred.
-static int in_same_namespace(pid_t pid1, pid_t pid2, const char *ns)
+static int in_same_namespace(pid_t pid1, pid_t pid2, int ns_fd_pid2, const char *ns)
 {
 	__do_close int ns_fd1 = -EBADF, ns_fd2 = -EBADF;
 	int ret = -1;
 	struct stat ns_st1, ns_st2;
 
-	ns_fd1 = preserve_ns(pid1, ns);
+	ns_fd1 = preserve_ns(pid1, -EBADF, ns);
 	if (ns_fd1 < 0) {
 		// The kernel does not support this namespace. This is not an
 		// error.
@@ -182,7 +187,7 @@ static int in_same_namespace(pid_t pid1, pid_t pid2, const char *ns)
 		return -1;
 	}
 
-	ns_fd2 = preserve_ns(pid2, ns);
+	ns_fd2 = preserve_ns(pid2, ns_fd_pid2, ns);
 	if (ns_fd2 < 0)
 		return -1;
 
@@ -202,11 +207,12 @@ static int in_same_namespace(pid_t pid1, pid_t pid2, const char *ns)
 	return move_fd(ns_fd2);
 }
 
-void attach_userns(int pid) {
+static void __attach_userns(int pid, int ns_fd)
+{
 	__do_close int userns_fd = -EBADF;
 	int ret;
 
-	userns_fd = in_same_namespace(getpid(), pid, "user");
+	userns_fd = in_same_namespace(getpid(), pid, ns_fd, "user");
 	if (userns_fd < 0) {
 		if (userns_fd == -EINVAL)
 			return;
@@ -239,6 +245,64 @@ void attach_userns(int pid) {
 	}
 }
 
+void attach_userns(int pid)
+{
+	return __attach_userns(pid, -EBADF);
+}
+
+void attach_userns_fd(int ns_fd)
+{
+	return __attach_userns(-1, ns_fd);
+}
+
+int pidfd_open(pid_t pid, unsigned int flags)
+{
+	return syscall(__NR_pidfd_open, pid, flags);
+}
+
+int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags)
+{
+	return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
+}
+
+int pidfd_nsfd(int pidfd, pid_t pid)
+{
+	__do_close int ns_fd = -EBADF;
+	int ret;
+	char path[100];
+
+	ret = snprintf(path, sizeof(path), "/proc/%d/ns", pid);
+	if (ret < 0 || (size_t)ret >= sizeof(path))
+		return -E2BIG;
+
+	ns_fd = open(path, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+	if (ns_fd < 0)
+		return -errno;
+
+	if (pidfd >= 0) {
+		// Verify that the pid has not been recycled and our /proc/<pid> handle
+		// is still valid.
+		ret = pidfd_send_signal(pidfd, 0, NULL, 0);
+		if (ret < 0) {
+			if (errno != EPERM)
+				return -errno;
+		}
+	}
+
+	return move_fd(ns_fd);
+}
+
+bool setnsat(int ns_fd, const char *ns)
+{
+	__do_close int fd = -EBADF;
+
+	fd = openat(ns_fd, ns, O_RDONLY | O_CLOEXEC);
+	if (fd < 0)
+		return false;
+
+	return setns(fd, 0) == 0;
+}
+
 static ssize_t lxc_read_nointr(int fd, void *buf, size_t count)
 {
 	ssize_t ret;
diff --git a/lxd/seccomp/seccomp.go b/lxd/seccomp/seccomp.go
index 0c78860b1a..c4fb9c3f77 100644
--- a/lxd/seccomp/seccomp.go
+++ b/lxd/seccomp/seccomp.go
@@ -877,11 +877,20 @@ func CallForkmknod(c Instance, dev deviceConfig.Device, requestPID int) int {
 		return int(-C.EPERM)
 	}
 
-	_, stderr, err := shared.RunCommandSplit(nil, util.GetExecPath(),
-		"forksyscall", "mknod", dev["pid"], dev["path"],
-		dev["mode_t"], dev["dev_t"],
-		fmt.Sprintf("%d", uid), fmt.Sprintf("%d", gid),
-		fmt.Sprintf("%d", fsuid), fmt.Sprintf("%d", fsgid))
+	_, stderr, err := shared.RunCommandSplit(
+		nil,
+		nil,
+		util.GetExecPath(),
+		"forksyscall",
+		"mknod",
+		dev["pid"],
+		dev["path"],
+		dev["mode_t"],
+		dev["dev_t"],
+		fmt.Sprintf("%d", uid),
+		fmt.Sprintf("%d", gid),
+		fmt.Sprintf("%d", fsuid),
+		fmt.Sprintf("%d", fsgid))
 	if err != nil {
 		errno, err := strconv.Atoi(stderr)
 		if err != nil || errno == C.ENOANO {
@@ -1155,7 +1164,10 @@ func (s *Server) HandleSetxattrSyscall(c Instance, siov *Iovec) int {
 		return 0
 	}
 
-	_, stderr, err := shared.RunCommandSplit(nil, util.GetExecPath(),
+	_, stderr, err := shared.RunCommandSplit(
+		nil,
+		nil,
+		util.GetExecPath(),
 		"forksyscall",
 		"setxattr",
 		fmt.Sprintf("%d", args.pid),
@@ -1435,7 +1447,10 @@ func (s *Server) HandleMountSyscall(c Instance, siov *Iovec) int {
 		ctx["fuse_source"] = fuseSource
 		ctx["fuse_target"] = args.target
 		ctx["fuse_opts"] = fuseOpts
-		_, _, err = shared.RunCommandSplit(nil, util.GetExecPath(),
+		_, _, err = shared.RunCommandSplit(
+			nil,
+			nil,
+			util.GetExecPath(),
 			"forksyscall",
 			"mount",
 			fmt.Sprintf("%d", args.pid),
@@ -1448,7 +1463,10 @@ func (s *Server) HandleMountSyscall(c Instance, siov *Iovec) int {
 			fmt.Sprintf("%s", args.target),
 			fmt.Sprintf("%s", fuseOpts))
 	} else {
-		_, _, err = shared.RunCommandSplit(nil, util.GetExecPath(),
+		_, _, err = shared.RunCommandSplit(
+			nil,
+			nil,
+			util.GetExecPath(),
 			"forksyscall",
 			"mount",
 			fmt.Sprintf("%d", args.pid),
diff --git a/lxd/sys/os.go b/lxd/sys/os.go
index 8214a3b9ca..143a71b632 100644
--- a/lxd/sys/os.go
+++ b/lxd/sys/os.go
@@ -64,6 +64,7 @@ type OS struct {
 
 	// Kernel features
 	NetnsGetifaddrs         bool
+	PidFds                  bool
 	SeccompListener         bool
 	SeccompListenerContinue bool
 	Shiftfs                 bool
diff --git a/shared/util.go b/shared/util.go
index 3a7c55cfd5..69ee1707bc 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -855,13 +855,17 @@ func (e RunError) Error() string {
 // resulting stdout and stderr output as separate variables. If the supplied environment is nil then
 // the default environment is used. If the command fails to start or returns a non-zero exit code
 // then an error is returned containing the output of stderr too.
-func RunCommandSplit(env []string, name string, arg ...string) (string, string, error) {
+func RunCommandSplit(env []string, filesInherit []*os.File, name string, arg ...string) (string, string, error) {
 	cmd := exec.Command(name, arg...)
 
 	if env != nil {
 		cmd.Env = env
 	}
 
+	if filesInherit != nil {
+		cmd.ExtraFiles = filesInherit
+	}
+
 	var stdout bytes.Buffer
 	var stderr bytes.Buffer
 	cmd.Stdout = &stdout
@@ -884,7 +888,7 @@ func RunCommandSplit(env []string, name string, arg ...string) (string, string,
 // RunCommand runs a command with optional arguments and returns stdout. If the command fails to
 // start or returns a non-zero exit code then an error is returned containing the output of stderr.
 func RunCommand(name string, arg ...string) (string, error) {
-	stdout, _, err := RunCommandSplit(nil, name, arg...)
+	stdout, _, err := RunCommandSplit(nil, nil, name, arg...)
 	return stdout, err
 }
 
@@ -892,7 +896,7 @@ func RunCommand(name string, arg ...string) (string, error) {
 // returns stdout. If the command fails to start or returns a non-zero exit code then an error is
 // returned containing the output of stderr.
 func RunCommandCLocale(name string, arg ...string) (string, error) {
-	stdout, _, err := RunCommandSplit(append(os.Environ(), "LANG=C.UTF-8"), name, arg...)
+	stdout, _, err := RunCommandSplit(append(os.Environ(), "LANG=C.UTF-8"), nil, name, arg...)
 	return stdout, err
 }
 


More information about the lxc-devel mailing list