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