jail: add support for maskedPaths and readonlyPaths
authorDaniel Golle <daniel@makrotopia.org>
Thu, 16 Jul 2020 22:00:35 +0000 (23:00 +0100)
committerDaniel Golle <daniel@makrotopia.org>
Fri, 17 Jul 2020 10:40:55 +0000 (11:40 +0100)
Parse maskedPaths and readonlyPaths string arrays if defined in OCI
container linux section. readonlyPaths are implemented by adding a
recursive read-only bind-mount on the path, maskedPaths are done by
mounting a zero-sized tmpfs with mode 000 for directories and mount-
binding an empty file having mode 000 for non-directories.
Mounts of both, maskedPaths and readonlyPaths, may fail silently if
the path doesn't exist.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
jail/fs.c
jail/jail.c

index 352fa0d087233b735db502062d1d5b189d7f3cf9..abe12d4e9810a0e8df7ab7de6645b5e90aeb9200 100644 (file)
--- a/jail/fs.c
+++ b/jail/fs.c
@@ -37,6 +37,8 @@
 #include "jail.h"
 #include "log.h"
 
+#define UJAIL_NOAFILE "/tmp/.ujailnoafile"
+
 struct mount {
        struct avl_node avl;
        const char *source;
@@ -81,18 +83,40 @@ static int do_mount(const char *root, const char *source, const char *target, co
        char new[PATH_MAX];
        int fd;
        bool is_bind = (orig_mountflags & MS_BIND);
+       bool is_mask = (source == (void *)(-1));
        unsigned long mountflags = orig_mountflags;
 
-       if (is_bind && stat(source, &s)) {
+       if (source && is_bind && stat(source, &s)) {
                ERROR("stat(%s) failed: %m\n", source);
                return error;
        }
 
        snprintf(new, sizeof(new), "%s%s", root, target?target:source);
 
-       if (!is_bind || S_ISDIR(s.st_mode)) {
+       if (is_mask) {
+               if (stat(new, &s))
+                       return 0; /* doesn't exists, nothing to mask */
+
+               if (S_ISDIR(s.st_mode)) {/* use empty 0-sized tmpfs for directories */
+                       if (mount(NULL, new, "tmpfs", MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_NOATIME, "size=0,mode=000"))
+                               return error;
+               } else {
+                       /* mount-bind 0-sized file having mode 000 */
+                       if (mount(UJAIL_NOAFILE, new, NULL, MS_BIND, NULL))
+                               return error;
+
+                       if (mount(UJAIL_NOAFILE, new, NULL, MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_NOATIME, NULL))
+                               return error;
+               }
+
+               DEBUG("masked path %s\n", new);
+               return 0;
+       }
+
+
+       if (!is_bind || (source && S_ISDIR(s.st_mode))) {
                mkdir_p(new, 0755);
-       } else {
+       } else if (is_bind && source) {
                mkdir_p(dirname(new), 0755);
                snprintf(new, sizeof(new), "%s%s", root, target?target:source);
                fd = creat(new, 0644);
@@ -103,16 +127,20 @@ static int do_mount(const char *root, const char *source, const char *target, co
                close(fd);
        }
 
-       if (mountflags & MS_BIND) {
-               if (mount(source, new, filesystemtype, MS_BIND | (mountflags & MS_REC), optstr)) {
-                       ERROR("failed to mount -B %s %s: %m\n", source, new);
+       if (is_bind) {
+               if (mount(source?:new, new, filesystemtype, MS_BIND | (mountflags & MS_REC), optstr)) {
+                       if (error)
+                               ERROR("failed to mount -B %s %s: %m\n", source, new);
+
                        return error;
                }
                mountflags |= MS_REMOUNT;
        }
 
-       if (mount(source, new, filesystemtype, mountflags, optstr)) {
-               ERROR("failed to mount %s %s: %m\n", source, new);
+       if (mount(source?:(is_bind?new:NULL), new, filesystemtype, mountflags, optstr)) {
+               if (error)
+                       ERROR("failed to mount %s %s: %m\n", source, new);
+
                return error;
        }
 
@@ -134,15 +162,19 @@ int add_mount(const char *source, const char *target, const char *filesystemtype
        m = calloc(1, sizeof(struct mount));
        assert(m != NULL);
        m->avl.key = m->target = strdup(target);
-       if (source)
-               m->source = strdup(source);
+       if (source) {
+               if (source != (void*)(-1))
+                       m->source = strdup(source);
+               else
+                       m->source = (void*)(-1);
+       }
        if (filesystemtype)
                m->filesystemtype = strdup(filesystemtype);
        m->mountflags = mountflags;
        m->error = error;
 
        avl_insert(&mounts, &m->avl);
-       DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", m->source, m->target,
+       DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m->source == (void*)(-1))?"mask":m->source, m->target,
                !!(m->mountflags & MS_BIND), !!(m->mountflags & MS_RDONLY), m->error != 0);
 
        return 0;
@@ -158,7 +190,6 @@ int add_mount_bind(const char *path, int readonly, int error)
        return add_mount(path, path, NULL, mountflags, NULL, error);
 }
 
-
 enum {
        OCI_MOUNT_SOURCE,
        OCI_MOUNT_DESTINATION,
@@ -299,7 +330,7 @@ static int parseOCImountopts(struct blob_attr *msg, unsigned long *mount_flags,
        DEBUG("mount flags(%08lx) fsopts(\"%s\")\n", mf, *mount_data?:"");
 
        return 0;
-};
+}
 
 int parseOCImount(struct blob_attr *msg)
 {
@@ -319,19 +350,29 @@ int parseOCImount(struct blob_attr *msg)
                        return ret;
        }
 
-       add_mount(tb[OCI_MOUNT_SOURCE] ? blobmsg_get_string(tb[OCI_MOUNT_SOURCE]) : NULL,
+       return add_mount(tb[OCI_MOUNT_SOURCE] ? blobmsg_get_string(tb[OCI_MOUNT_SOURCE]) : NULL,
                  blobmsg_get_string(tb[OCI_MOUNT_DESTINATION]),
                  tb[OCI_MOUNT_TYPE] ? blobmsg_get_string(tb[OCI_MOUNT_TYPE]) : NULL,
                  mount_flags, mount_data, err);
+}
 
-       return 0;
-};
+static void build_noafile(void) {
+       int fd;
 
+       fd = creat(UJAIL_NOAFILE, 0000);
+       if (fd == -1)
+               return;
+
+       close(fd);
+       return;
+}
 
 int mount_all(const char *jailroot) {
        struct library *l;
        struct mount *m;
 
+       build_noafile();
+
        avl_for_each_element(&libraries, l, avl)
                add_mount_bind(l->path, 1, -1);
 
index aa6288edaf02f5946d46fe31d7e783f2e8979c4f..fdf6285512c2e57fafc80c72b6c11d08b1bf5130 100644 (file)
@@ -1233,7 +1233,6 @@ static int parseOCIlinuxns(struct blob_attr *msg)
 {
        struct blob_attr *tb[__OCI_LINUX_NAMESPACE_MAX];
 
-
        blobmsg_parse(oci_linux_namespace_policy, __OCI_LINUX_NAMESPACE_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
 
        if (!tb[OCI_LINUX_NAMESPACE_TYPE])
@@ -1384,6 +1383,22 @@ static int parseOCIlinux(struct blob_attr *msg)
                        return res;
        }
 
+       if (tb[OCI_LINUX_READONLYPATHS]) {
+               blobmsg_for_each_attr(cur, tb[OCI_LINUX_READONLYPATHS], rem) {
+                       res = add_mount(NULL, blobmsg_get_string(cur), NULL, MS_BIND | MS_REC | MS_RDONLY, NULL, 0);
+                       if (res)
+                               return res;
+               }
+       }
+
+       if (tb[OCI_LINUX_MASKEDPATHS]) {
+               blobmsg_for_each_attr(cur, tb[OCI_LINUX_MASKEDPATHS], rem) {
+                       res = add_mount((void *)(-1), blobmsg_get_string(cur), NULL, 0, NULL, 1);
+                       if (res)
+                               return res;
+               }
+       }
+
        if (tb[OCI_LINUX_SECCOMP]) {
                opts.ociseccomp = parseOCIlinuxseccomp(tb[OCI_LINUX_SECCOMP]);
                if (!opts.ociseccomp)