The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/5719

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
Signed-off-by: Christian Brauner <christian.brau...@ubuntu.com>
From f3d0ee28cd95c53e788e781b8a9fc4e1a601a4ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@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 <stgra...@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.brau...@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.brau...@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)
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to