[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