[lxc-devel] [lxd/master] seccomp: add support for SECCOMP_RET_NOTIF_USER

brauner on Github lxc-bot at linuxcontainers.org
Thu May 2 19:46:42 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 364 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190502/706aa10a/attachment.bin>
-------------- next part --------------
From f3d0ee28cd95c53e788e781b8a9fc4e1a601a4ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 29 Apr 2019 17:13:24 -0400
Subject: [PATCH 1/2] lxd/seccomp: Minimal seccomp server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go  | 11 +++++++
 lxd/seccomp.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 91 insertions(+)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index e7a1b4eba6..3f9d572a7d 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -68,6 +68,7 @@ type Daemon struct {
 	config    *DaemonConfig
 	endpoints *endpoints.Endpoints
 	gateway   *cluster.Gateway
+	seccomp   *SeccompServer
 
 	proxy func(req *http.Request) (*url.URL, error)
 
@@ -857,6 +858,16 @@ func (d *Daemon) init() error {
 		deviceInotifyDirRescan(d.State())
 		go deviceInotifyHandler(d.State())
 
+		// Setup seccomp handler
+		if d.os.SeccompListener {
+			seccompServer, err := NewSeccompServer(d, shared.VarPath("seccomp.socket"))
+			if err != nil {
+				return err
+			}
+			d.seccomp = seccompServer
+			logger.Info("Started seccomp handler", log.Ctx{"path": shared.VarPath("seccomp.socket")})
+		}
+
 		// Read the trusted certificates
 		readSavedClientCAList(d)
 
diff --git a/lxd/seccomp.go b/lxd/seccomp.go
index 391d8b1097..90e5ad4296 100644
--- a/lxd/seccomp.go
+++ b/lxd/seccomp.go
@@ -3,10 +3,12 @@ package main
 import (
 	"fmt"
 	"io/ioutil"
+	"net"
 	"os"
 	"path"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
@@ -166,3 +168,81 @@ func SeccompDeleteProfile(c container) {
 	 */
 	os.Remove(SeccompProfilePath(c))
 }
+
+type SeccompServer struct {
+	d    *Daemon
+	path string
+	l    net.Listener
+}
+
+func NewSeccompServer(d *Daemon, path string) (*SeccompServer, error) {
+	// Cleanup existing sockets
+	if shared.PathExists(path) {
+		err := os.Remove(path)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// Bind new socket
+	l, err := net.Listen("unix", path)
+	if err != nil {
+		return nil, err
+	}
+
+	// Restrict access
+	err = os.Chmod(path, 0700)
+	if err != nil {
+		return nil, err
+	}
+
+	// Start the server
+	s := SeccompServer{
+		d:    d,
+		path: path,
+		l:    l,
+	}
+
+	go func() {
+		for {
+			c, err := l.Accept()
+			if err != nil {
+				return
+			}
+
+			go func() {
+				ucred, err := getCred(c.(*net.UnixConn))
+				if err != nil {
+					logger.Errorf("Unable to get ucred from seccomp socket client: %v", err)
+					return
+				}
+				logger.Debugf("Connected to seccomp socket: pid=%v", ucred.pid)
+
+				for {
+					buf := make([]byte, 4096)
+					_, err := c.Read(buf)
+					if err != nil {
+						logger.Debugf("Disconnected from seccomp socket: pid=%v", ucred.pid)
+						c.Close()
+						return
+					}
+
+					// Unpack the struct here and pass unpacked struct to handler
+					go s.Handler(c, ucred, buf)
+				}
+			}()
+		}
+	}()
+
+	return &s, nil
+}
+
+func (s *SeccompServer) Handler(c net.Conn, ucred *ucred, buf []byte) error {
+	logger.Debugf("Handling seccomp notification from: %v", ucred.pid)
+	return nil
+}
+
+func (s *SeccompServer) Stop() error {
+	os.Remove(s.path)
+	return s.l.Close()
+}

From b5600e43fa1991087cb0b2b65c33d792de509b75 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 1 May 2019 18:26:41 +0200
Subject: [PATCH 2/2] seccomp: implement notifier structure unpacking and
 notifier responses

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go     |   7 ++
 lxd/main.go              |   4 +
 lxd/main_forkmknod.go    | 191 +++++++++++++++++++++++++++++++++++++++
 lxd/main_nsexec.go       |   3 +
 lxd/seccomp.go           | 132 +++++++++++++++++++++++++--
 shared/util_linux_cgo.go |  53 ++++++++---
 6 files changed, 371 insertions(+), 19 deletions(-)
 create mode 100644 lxd/main_forkmknod.go

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 68b238e9c6..11f4118015 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1811,6 +1811,13 @@ func (c *containerLXC) initLXC(config bool) error {
 		return err
 	}
 
+	if lxc.HasApiExtension("seccomp_notify") && c.DaemonState().OS.SeccompListener {
+		err = lxcSetConfigItem(cc, "lxc.seccomp.notify.proxy", fmt.Sprintf("unix:%s", shared.VarPath("seccomp.socket")))
+		if err != nil {
+			return err
+		}
+	}
+
 	// Apply raw.lxc
 	if lxcConfig, ok := c.expandedConfig["raw.lxc"]; ok {
 		f, err := ioutil.TempFile("", "lxd_config_")
diff --git a/lxd/main.go b/lxd/main.go
index 79d94fbe0e..66e25e7455 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -108,6 +108,10 @@ func main() {
 	forkmigrateCmd := cmdForkmigrate{global: &globalCmd}
 	app.AddCommand(forkmigrateCmd.Command())
 
+	// forkmknod sub-command
+	forkmknodCmd := cmdForkmknod{global: &globalCmd}
+	app.AddCommand(forkmknodCmd.Command())
+
 	// forkmount sub-command
 	forkmountCmd := cmdForkmount{global: &globalCmd}
 	app.AddCommand(forkmountCmd.Command())
diff --git a/lxd/main_forkmknod.go b/lxd/main_forkmknod.go
new file mode 100644
index 0000000000..22cd4ef5ad
--- /dev/null
+++ b/lxd/main_forkmknod.go
@@ -0,0 +1,191 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+)
+
+/*
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "include/memory_utils.h"
+
+extern char* advance_arg(bool required);
+extern int dosetns(int pid, char *nstype);
+
+static uid_t get_root_uid(pid_t pid)
+{
+	char *line = NULL;
+	size_t sz = 0;
+	uid_t nsid, hostid, range;
+	FILE *f;
+	char path[256];
+
+	snprintf(path, sizeof(path), "/proc/%d/uid_map", pid);
+	f = fopen(path, "re");
+	if (!f)
+		return -1;
+
+	while (getline(&line, &sz, f) != -1) {
+		if (sscanf(line, "%u %u %u", &nsid, &hostid, &range) != 3)
+			continue;
+
+		if (nsid == 0)
+			return hostid;
+	}
+
+	nsid = -1;
+
+found:
+	fclose(f);
+	free(line);
+	return nsid;
+}
+
+static gid_t get_root_gid(pid_t pid)
+{
+	char *line = NULL;
+	size_t sz = 0;
+	gid_t nsid, hostid, range;
+	FILE *f;
+	char path[256];
+
+	snprintf(path, sizeof(path), "/proc/%d/gid_map", pid);
+	f = fopen(path, "re");
+	if (!f)
+		return -1;
+
+	while (getline(&line, &sz, f) != -1) {
+		if (sscanf(line, "%u %u %u", &nsid, &hostid, &range) != 3)
+			continue;
+
+		if (nsid == 0)
+			return hostid;
+	}
+
+	nsid = -1;
+
+found:
+	fclose(f);
+	free(line);
+	return nsid;
+}
+
+// Expects command line to be in the form:
+// <PID> <root-uid> <root-gid> <path> <mode> <dev>
+void forkmknod()
+{
+	ssize_t bytes = 0;
+	char *cur = NULL;
+	char *path = NULL;
+	mode_t mode = 0;
+	dev_t dev = 0;
+	pid_t pid = 0;
+	uid_t uid = -1;
+	gid_t gid = -1;
+	char cwd[256], cwd_path[PATH_MAX];
+
+	// Get the subcommand
+	cur = advance_arg(false);
+	if (!cur ||
+	    (strcmp(cur, "--help") == 0 ||
+	     strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0))
+		return;
+
+	// Check that we're root
+	if (geteuid() != 0) {
+		fprintf(stderr, "Error: forkmknod requires root privileges\n");
+		_exit(EXIT_FAILURE);
+	}
+
+	// Get the container PID
+	pid = atoi(cur);
+
+	// path to create
+	path = advance_arg(true);
+	if (!path)
+		_exit(EXIT_FAILURE);
+
+	mode = atoi(advance_arg(true));
+	dev = atoi(advance_arg(true));
+
+	snprintf(cwd, sizeof(cwd), "/proc/%d/cwd", pid);
+	bytes = readlink(cwd, cwd_path, sizeof(cwd_path));
+	if (bytes < 0 || bytes >= sizeof(cwd_path)) {
+		fprintf(stderr, "Failed to retrieve cwd of target process: %s\n",
+			strerror(errno));
+		_exit(EXIT_FAILURE);
+	}
+	cwd_path[bytes] = '\0';
+
+	uid = get_root_uid(pid);
+	if (uid < 0)
+		fprintf(stderr, "No root uid found (%d)\n", uid);
+
+	gid = get_root_gid(pid);
+	if (gid < 0)
+		fprintf(stderr, "No root gid found (%d)\n", gid);
+
+	snprintf(cwd, sizeof(cwd), "/proc/%d/root", pid);
+	if (chroot(cwd)) {
+		fprintf(stderr, "Failed to chroot to container rootfs: %s\n",
+			strerror(errno));
+		_exit(EXIT_FAILURE);
+	}
+
+	if (chdir(cwd_path)) {
+		fprintf(stderr, "Failed to change to target process cwd: %s\n",
+			strerror(errno));
+		_exit(EXIT_FAILURE);
+	}
+
+	if (mknod(path, mode, dev)) {
+		fprintf(stderr, "Failed to create device %s\n", strerror(errno));
+		_exit(EXIT_FAILURE);
+	}
+
+	if (chown(path, uid, gid)) {
+		fprintf(stderr, "Failed to chown device to container root %s\n",
+			strerror(errno));
+		_exit(EXIT_FAILURE);
+	}
+
+	_exit(EXIT_SUCCESS);
+}
+*/
+import "C"
+
+type cmdForkmknod struct {
+	global *cmdGlobal
+}
+
+func (c *cmdForkmknod) Command() *cobra.Command {
+	// Main subcommand
+	cmd := &cobra.Command{}
+	cmd.Use = "forkmknod <PID> <path> <mode> <dev>"
+	cmd.Short = "Perform mknod operations"
+	cmd.Long = `Description:
+  Perform mknod operations
+
+  This set of internal commands are used for all seccom-based container mknod
+  operations.
+`
+	cmd.RunE = c.Run
+	cmd.Hidden = true
+
+	return cmd
+}
+
+func (c *cmdForkmknod) Run(cmd *cobra.Command, args []string) error {
+	return fmt.Errorf("This command should have been intercepted in cgo")
+}
diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
index 18e38d1f33..8fa6db708a 100644
--- a/lxd/main_nsexec.go
+++ b/lxd/main_nsexec.go
@@ -38,6 +38,7 @@ package main
 // External functions
 extern void checkfeature();
 extern void forkfile();
+extern void forkmknod();
 extern void forkmount();
 extern void forknet();
 extern void forkproxy();
@@ -265,6 +266,8 @@ __attribute__((constructor)) void init(void) {
 	// Intercepts some subcommands
 	if (strcmp(cmdline_cur, "forkfile") == 0)
 		forkfile();
+	else if (strcmp(cmdline_cur, "forkmknod") == 0)
+		forkmknod();
 	else if (strcmp(cmdline_cur, "forkmount") == 0)
 		forkmount();
 	else if (strcmp(cmdline_cur, "forknet") == 0)
diff --git a/lxd/seccomp.go b/lxd/seccomp.go
index 90e5ad4296..f37e701041 100644
--- a/lxd/seccomp.go
+++ b/lxd/seccomp.go
@@ -1,17 +1,88 @@
+// +build cgo
 package main
 
 import (
+	"bytes"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"net"
 	"os"
 	"path"
+	"unsafe"
 
+	"golang.org/x/sys/unix"
+
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
+/*
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/seccomp.h>
+#include <linux/types.h>
+#include <linux/kdev_t.h>
+#include <seccomp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+struct seccomp_notify_proxy_msg {
+	uint32_t version;
+	struct seccomp_notif req;
+	struct seccomp_notif_resp resp;
+	pid_t monitor_pid;
+	pid_t init_pid;
+};
+
+#define SECCOMP_PROXY_MSG_SIZE (sizeof(struct seccomp_notify_proxy_msg))
+
+static int device_allowed(dev_t dev, mode_t mode)
+{
+	if ((dev == makedev(5, 1)) && (mode & S_IFCHR)) // /dev/console
+		return 0;
+
+	return -EPERM;
+}
+
+static int seccomp_notify_mknod_set_response(struct seccomp_notify_proxy_msg *msg)
+{
+	struct seccomp_notif *req = &msg->req;
+	struct seccomp_notif_resp *resp = &msg->resp;
+	int ret;
+
+	resp->id = req->id;
+	resp->flags = req->flags;
+	resp->val = 0;
+
+	if (req->data.nr != __NR_mknod) {
+		resp->error = -ENOSYS;
+		return -1;
+	}
+
+	resp->error = device_allowed(req->data.args[2], req->data.args[1]);
+	if (resp->error)
+		return -1;
+
+	return 0;
+}
+*/
+// #cgo CFLAGS: -std=gnu11 -Wvla
+// #cgo LDFLAGS: -lseccomp
+import "C"
+
 const SECCOMP_HEADER = `2
 `
 
@@ -22,6 +93,7 @@ open_by_handle_at errno 38
 init_module errno 38
 finit_module errno 38
 delete_module errno 38
+mknod notify
 `
 const COMPAT_BLOCKING_POLICY = `[%s]
 compat_sys_rt_sigaction errno 38
@@ -216,19 +288,24 @@ func NewSeccompServer(d *Daemon, path string) (*SeccompServer, error) {
 					logger.Errorf("Unable to get ucred from seccomp socket client: %v", err)
 					return
 				}
+
 				logger.Debugf("Connected to seccomp socket: pid=%v", ucred.pid)
 
+				unixFile, err := c.(*net.UnixConn).File()
+				if err != nil {
+					return
+				}
+
 				for {
-					buf := make([]byte, 4096)
-					_, err := c.Read(buf)
-					if err != nil {
-						logger.Debugf("Disconnected from seccomp socket: pid=%v", ucred.pid)
+					buf := make([]byte, C.SECCOMP_PROXY_MSG_SIZE)
+					fdMem, err := shared.AbstractUnixReceiveFdData(int(unixFile.Fd()), buf)
+					if err != nil || err == io.EOF {
+						logger.Debugf("Disconnected from seccomp socket after receive: pid=%v", ucred.pid)
 						c.Close()
 						return
 					}
 
-					// Unpack the struct here and pass unpacked struct to handler
-					go s.Handler(c, ucred, buf)
+					go s.Handler(c, ucred, buf, fdMem)
 				}
 			}()
 		}
@@ -237,8 +314,49 @@ func NewSeccompServer(d *Daemon, path string) (*SeccompServer, error) {
 	return &s, nil
 }
 
-func (s *SeccompServer) Handler(c net.Conn, ucred *ucred, buf []byte) error {
+func (s *SeccompServer) Handler(c net.Conn, ucred *ucred, buf []byte, fdMem int) error {
 	logger.Debugf("Handling seccomp notification from: %v", ucred.pid)
+	pathBuf := make([]byte, unix.PathMax)
+
+	defer unix.Close(fdMem)
+	var msg C.struct_seccomp_notify_proxy_msg
+	C.memcpy(unsafe.Pointer(&msg), unsafe.Pointer(&buf[0]), C.SECCOMP_PROXY_MSG_SIZE)
+
+	// We're ignoring the return value for now but we'll need it later.
+	ret := C.seccomp_notify_mknod_set_response(&msg)
+	if ret == 0 {
+		_, err := unix.Pread(fdMem, pathBuf, int64(msg.req.data.args[0]))
+		if err != nil {
+			goto out
+		}
+
+		idx := bytes.IndexRune(pathBuf, 0)
+		path := string(pathBuf[:idx])
+		mode := int32(msg.req.data.args[1])
+		dev := uint32(msg.req.data.args[2])
+		// Expects command line to be in the form: <PID> <root-uid> <root-gid> <path> <mode> <dev>
+		_, err = shared.RunCommand(util.GetExecPath(),
+			"forkmknod",
+			fmt.Sprintf("%d", msg.req.pid),
+			path,
+			fmt.Sprintf("%d", mode),
+			fmt.Sprintf("%d", dev))
+		if err != nil {
+			logger.Errorf("Failed to create device node: %s", err)
+			msg.resp.error = -C.EPERM
+		}
+	}
+
+	C.memcpy(unsafe.Pointer(&buf[0]), unsafe.Pointer(&msg), C.SECCOMP_PROXY_MSG_SIZE)
+
+out:
+	_, err := c.Write(buf)
+	if err != nil {
+		logger.Debugf("Disconnected from seccomp socket after write: pid=%v", ucred.pid)
+		return err
+	}
+
+	logger.Debugf("Handled seccomp notification from: %v", ucred.pid)
 	return nil
 }
 
diff --git a/shared/util_linux_cgo.go b/shared/util_linux_cgo.go
index faf37d260e..acd8f9b218 100644
--- a/shared/util_linux_cgo.go
+++ b/shared/util_linux_cgo.go
@@ -191,14 +191,17 @@ int lxc_abstract_unix_recv_fds(int fd, int *recvfds, int num_recvfds,
 	struct iovec iov;
 	struct cmsghdr *cmsg = NULL;
 	char buf[1] = {0};
-	size_t cmsgbufsize = CMSG_SPACE(num_recvfds * sizeof(int));
+	size_t cmsgbufsize = CMSG_SPACE(sizeof(struct ucred)) +
+			     CMSG_SPACE(num_recvfds * sizeof(int));
 
 	memset(&msg, 0, sizeof(msg));
 	memset(&iov, 0, sizeof(iov));
 
 	cmsgbuf = malloc(cmsgbufsize);
-	if (!cmsgbuf)
+	if (!cmsgbuf) {
+		errno = ENOMEM;
 		return -1;
+	}
 
 	msg.msg_control = cmsgbuf;
 	msg.msg_controllen = cmsgbufsize;
@@ -208,20 +211,31 @@ int lxc_abstract_unix_recv_fds(int fd, int *recvfds, int num_recvfds,
 	msg.msg_iov = &iov;
 	msg.msg_iovlen = 1;
 
+again:
 	ret = recvmsg(fd, &msg, 0);
-	if (ret <= 0) {
-		fprintf(stderr, "%s - Failed to receive file descriptor\n", strerror(errno));
-		return ret;
-	}
-
-	cmsg = CMSG_FIRSTHDR(&msg);
+	if (ret < 0) {
+		if (errno == EINTR)
+			goto again;
 
-	memset(recvfds, -1, num_recvfds * sizeof(int));
-	if (cmsg && cmsg->cmsg_len == CMSG_LEN(num_recvfds * sizeof(int)) &&
-	    cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
-		memcpy(recvfds, CMSG_DATA(cmsg), num_recvfds * sizeof(int));
+		goto out;
+	}
+	if (ret == 0)
+		goto out;
+
+	// If SO_PASSCRED is set we will always get a ucred message.
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		if (cmsg->cmsg_type != SCM_RIGHTS)
+			continue;
+
+		memset(recvfds, -1, num_recvfds * sizeof(int));
+		if (cmsg &&
+		    cmsg->cmsg_len == CMSG_LEN(num_recvfds * sizeof(int)) &&
+		    cmsg->cmsg_level == SOL_SOCKET)
+			memcpy(recvfds, CMSG_DATA(cmsg), num_recvfds * sizeof(int));
+		break;
 	}
 
+out:
 	return ret;
 }
 */
@@ -273,6 +287,21 @@ func AbstractUnixReceiveFd(sockFD int) (*os.File, error) {
 	return file, nil
 }
 
+func AbstractUnixReceiveFdData(sockFD int, buf []byte) (int, error) {
+	fd := C.int(-1)
+	sk_fd := C.int(sockFD)
+	ret := C.lxc_abstract_unix_recv_fds(sk_fd, &fd, C.int(1), unsafe.Pointer(&buf[0]), C.size_t(len(buf)))
+	if ret < 0 {
+		return int(-C.EBADF), fmt.Errorf("Failed to receive file descriptor via abstract unix socket")
+	}
+
+	if ret == 0 {
+		return int(-C.EBADF), io.EOF
+	}
+
+	return int(fd), nil
+}
+
 func OpenPty(uid, gid int64) (master *os.File, slave *os.File, err error) {
 	fd_master := C.int(-1)
 	fd_slave := C.int(-1)


More information about the lxc-devel mailing list