[systemd-devel] [PATCH/RFC] FuseMAC: user space MAC in systemd

Topi Miettinen toiwoton at gmail.com
Mon Mar 2 12:49:52 PST 2015


Intercept and filter filesystem operations of processes launched
by systemd with FUSE.

Implement learning, enforcing and auto enforcing/learning modes,
enabled with new exec directive FuseMAC.

FS operations can be filtered by access type (e.g. getattr/read,
cf. AppArmor or TOMOYO Linux) or for more fine grained control,
which area of the file is being accessed.

Due to limitations of FUSE, API file systems can't be intercepted.

Also the patch seems to trigger bugs in kernel (hang CPU).
---
 Makefile.am                           |    9 +-
 configure.ac                          |   13 +
 src/core/dbus-execute.c               |    3 +
 src/core/execute.c                    |   22 +
 src/core/execute.h                    |   16 +
 src/core/fusemac.c                    | 1118 +++++++++++++++++++++++++++++++++
 src/core/load-fragment-gperf.gperf.m4 |    5 +-
 src/core/load-fragment.c              |   31 +
 src/core/load-fragment.h              |    1 +
 src/shared/build.h                    |    9 +-
 src/shared/exit-status.c              |    3 +
 src/shared/exit-status.h              |    1 +
 src/shared/util.c                     |    2 +-
 src/shared/util.h                     |    2 +
 14 files changed, 1231 insertions(+), 4 deletions(-)
 create mode 100644 src/core/fusemac.c

diff --git a/Makefile.am b/Makefile.am
index 9d41a2c..61598bf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1186,6 +1186,11 @@ libsystemd_core_la_SOURCES = \
 	src/core/failure-action.c \
 	src/core/failure-action.h
 
+if HAVE_FUSE
+libsystemd_core_la_SOURCES += \
+	src/core/fusemac.c
+endif
+
 nodist_libsystemd_core_la_SOURCES = \
 	src/core/load-fragment-gperf.c \
 	src/core/load-fragment-gperf-nulstr.c
@@ -1198,6 +1203,7 @@ libsystemd_core_la_CFLAGS = \
 	$(APPARMOR_CFLAGS) \
 	$(SECCOMP_CFLAGS) \
 	$(MOUNT_CFLAGS) \
+	$(FUSE_CFLAGS) \
 	-pthread
 
 libsystemd_core_la_LIBADD = \
@@ -1211,7 +1217,8 @@ libsystemd_core_la_LIBADD = \
 	$(KMOD_LIBS) \
 	$(APPARMOR_LIBS) \
 	$(SECCOMP_LIBS) \
-	$(MOUNT_LIBS)
+	$(MOUNT_LIBS) \
+	$(FUSE_LIBS)
 
 if HAVE_SECCOMP
 libsystemd_core_la_LIBADD += \
diff --git a/configure.ac b/configure.ac
index 419b5d4..12d7937 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1332,6 +1332,19 @@ AC_ARG_ENABLE(ldconfig,
 AM_CONDITIONAL(ENABLE_LDCONFIG, [test x$enable_ldconfig = xyes])
 
 # ------------------------------------------------------------------------------
+have_fuse=no
+AC_ARG_ENABLE(fuse, AS_HELP_STRING([--disable-fuse], [Disable optional FUSE support]))
+if test "x$enable_fuse" != "xno"; then
+        PKG_CHECK_MODULES(FUSE, [ fuse ],
+                [AC_DEFINE(HAVE_FUSE, 1, [Define if FUSE is available]) have_fuse=yes], have_fuse=no)
+        if test "x$have_fuse" = xno -a "x$enable_fuse" = xyes; then
+                AC_MSG_ERROR([*** FUSE support requested but libraries not found])
+        fi
+        M4_DEFINES="$M4_DEFINES -DHAVE_FUSE"
+fi
+AM_CONDITIONAL(HAVE_FUSE, [test "$have_fuse" = "yes"])
+
+# ------------------------------------------------------------------------------
 # Location of the init scripts as mandated by LSB
 SYSTEM_SYSVINIT_PATH=/etc/init.d
 SYSTEM_SYSVRCND_PATH=/etc/rc.d
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index a9f7971..9611f29 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -49,6 +49,8 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInp
 static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_home, protect_home, ProtectHome);
 static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_system, protect_system, ProtectSystem);
 
+static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_fusemac_mode, fusemac_mode, FuseMAC);
+
 static int property_get_environment_files(
                 sd_bus *bus,
                 const char *path,
@@ -665,6 +667,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, runtime_directory_mode), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RuntimeDirectory", "as", NULL, offsetof(ExecContext, runtime_directory), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("FuseMAC", "s", bus_property_get_fusemac_mode, offsetof(ExecContext, fusemac_mode), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_VTABLE_END
 };
 
diff --git a/src/core/execute.c b/src/core/execute.c
index 39ec5ad..08686bd 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -1600,9 +1600,19 @@ static int exec_child(
                         log_close();
                 } else if (r < 0) {
                         *exit_status = EXIT_NAMESPACE;
+
+                }
+        }
+
+#ifdef HAVE_FUSE
+        if (context->fusemac_mode != FUSEMAC_NO) {
+                r = setup_fusemac(context->fusemac_mode, params->unit_id);
+                if (r < 0) {
+                        *exit_status = EXIT_FUSE;
                         return r;
                 }
         }
+#endif
 
         if (params->apply_chroot) {
                 if (context->root_directory)
@@ -2460,6 +2470,9 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
                 fprintf(f,
                         "%sAppArmorProfile: %s%s\n",
                         prefix, c->apparmor_profile_ignore ? "-" : "", c->apparmor_profile);
+        fprintf(f,
+                "%sFuseMAC: %s\n",
+                prefix, strna(fusemac_mode_to_string(c->fusemac_mode)));
 }
 
 bool exec_context_maintains_privileges(ExecContext *c) {
@@ -2911,3 +2924,12 @@ static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
 };
 
 DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
+
+static const char *const fusemac_mode_table[_FUSEMAC_MAX] = {
+        [FUSEMAC_NO] = "no",
+        [FUSEMAC_LEARN] = "learn",
+        [FUSEMAC_ENFORCE] = "enforce",
+        [FUSEMAC_AUTO] = "auto",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(fusemac_mode, FuseMAC);
diff --git a/src/core/execute.h b/src/core/execute.h
index 1a43ac7..68280fd 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -63,6 +63,15 @@ typedef enum ExecOutput {
         _EXEC_OUTPUT_INVALID = -1
 } ExecOutput;
 
+typedef enum FuseMAC {
+        FUSEMAC_NO,
+        FUSEMAC_LEARN,
+        FUSEMAC_ENFORCE,
+        FUSEMAC_AUTO,
+        _FUSEMAC_MAX,
+        _FUSEMAC_INVALID = -1
+} FuseMAC;
+
 struct ExecStatus {
         dual_timestamp start_timestamp;
         dual_timestamp exit_timestamp;
@@ -191,6 +200,8 @@ struct ExecContext {
 
         /* custom dbus enpoint */
         BusEndpoint *bus_endpoint;
+
+        FuseMAC fusemac_mode;
 };
 
 #include "cgroup.h"
@@ -265,3 +276,8 @@ ExecOutput exec_output_from_string(const char *s) _pure_;
 
 const char* exec_input_to_string(ExecInput i) _const_;
 ExecInput exec_input_from_string(const char *s) _pure_;
+
+const char* fusemac_mode_to_string(FuseMAC p) _const_;
+FuseMAC fusemac_mode_from_string(const char *s) _pure_;
+
+int setup_fusemac(FuseMAC fusemac_mode, const char *unit_id);
diff --git a/src/core/fusemac.c b/src/core/fusemac.c
new file mode 100644
index 0000000..e712cdb
--- /dev/null
+++ b/src/core/fusemac.c
@@ -0,0 +1,1118 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/*
+  Uses some code from fusexmp.c from FUSE:
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos at szeredi.hu>
+  Copyright (C) 2011       Sebastian Pipping <sebastian at pipping.org>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+
+  gcc -Wall fusemac.c `pkg-config fuse --cflags --libs` -o fusemac -DTEST
+*/
+
+/***
+  FuseMAC: FUSE-based Mandatory Access Control
+  Copyright (C) 2015       Topi Miettinen <toiwoton at gmail.com>
+  FuseMAC modes:
+  Learning: add any operations to permitted set
+  Enforcing: if the operation is not in permitted set, return -EPERM
+  Auto: like enforcing, but if the file has changed, update permitted set for that file
+***/
+
+#define FUSE_USE_VERSION 26
+#include <assert.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/xattr.h>
+#include <sys/mount.h>
+#include "list.h"
+#include "mkdir.h"
+#ifndef TEST
+#include "execute.h"
+#include "util.h"
+#include "log.h"
+#else
+typedef enum FuseMAC {
+        FUSEMAC_NO,
+        FUSEMAC_LEARN,
+        FUSEMAC_ENFORCE,
+        FUSEMAC_AUTO,
+        _FUSEMAC_MAX,
+        _FUSEMAC_INVALID = -1
+} FuseMAC;
+#define log_debug(...) fprintf(stderr, __VA_ARGS__)
+#define new0(t, n) ((t*) calloc((n), sizeof(t)))
+#endif
+
+enum {
+        p_getattr = 0,
+        p_access,
+        p_readlink,
+        p_readdir,
+        p_mknod,
+        p_mkdir,
+        p_symlink,
+        p_unlink,
+        p_rmdir,
+        p_rename,
+        p_link,
+        p_chmod,
+        p_chown,
+        p_truncate,
+        p_utimens,
+        p_open,
+        p_read,
+        p_write,
+        p_statfs,
+        p_release,
+        p_fsync,
+        p_fallocate,
+        p_setxattr,
+        p_getxattr,
+        p_listxattr,
+        p_removexattr,
+        p_lastop
+};
+
+static const char * const op_names[] = {
+        [p_getattr] = "getattr",
+        [p_access] = "access",
+        [p_readlink] = "readlink",
+        [p_readdir] = "readdir",
+        [p_mknod] = "mknod",
+        [p_mkdir] = "mkdir",
+        [p_symlink] = "symlink",
+        [p_unlink] = "unlink",
+        [p_rmdir] = "rmdir",
+        [p_rename] = "rename",
+        [p_link] = "link",
+        [p_chmod] = "chmod",
+        [p_chown] = "chown",
+        [p_truncate] = "truncate",
+        [p_utimens] = "utimens",
+        [p_open] = "open",
+        [p_read] = "read",
+        [p_write] = "write",
+        [p_statfs] = "statfs",
+        [p_release] = "release",
+        [p_fsync] = "fsync",
+        [p_fallocate] = "fallocate",
+        [p_setxattr] = "setxattr",
+        [p_getxattr] = "getxattr",
+        [p_listxattr] = "listxattr",
+        [p_removexattr] = "removexattr"
+};
+
+static FuseMAC global_fusemac_mode;
+
+struct range;
+
+struct range {
+        unsigned long start, end;
+        LIST_FIELDS(struct range, ranges);
+};
+
+struct path_ranges;
+
+struct path_ranges {
+        char *path;
+        off_t path_size;
+        time_t path_mtim;
+        unsigned long ops;
+        LIST_HEAD(struct range, ranges);
+        LIST_FIELDS(struct path_ranges, paths);
+};
+
+static struct path_ranges *db;
+
+static struct range *range_new(unsigned long start, unsigned long end) {
+        struct range *r;
+
+        log_debug("new range (%lx - %lx)\n", start, end);
+        r = new0(struct range, 1);
+        r->start = start;
+        r->end = end;
+
+        return r;
+}
+
+static struct path_ranges *path_ranges_new(const char *pathname, unsigned long op) {
+        struct path_ranges *p;
+        struct stat s;
+
+        p = new0(struct path_ranges, 1);
+        p->path = strdup(pathname);
+        memset(&s, 0, sizeof(s));
+        stat(pathname, &s);
+        p->path_size = s.st_size;
+        p->path_mtim = s.st_mtime;
+        p->ops = 1 << op;
+
+        return p;
+}
+
+static void merge_ranges(void) {
+        struct path_ranges *p;
+
+        LIST_FOREACH(paths, p, db) {
+                struct range *r1, *r1_safe;
+
+                LIST_FOREACH_SAFE(ranges, r1, r1_safe, p->ranges) {
+                        struct range *r2;
+
+                        LIST_FOREACH_OTHERS(ranges, r2, r1) {
+                                if ((r2->start >= r1->start && r2->start <= r1->end) || (r2->end >= r1->start && r2->end <= r1->end)) {
+                                        bool merged = false;
+
+                                        /* start in old range */
+                                        if (r1->end < r2->end) {
+                                                /* move end higher */
+                                                log_debug("merge path %s range (%lx - %lx) with (%lx - %lx) end higher\n", p->path, r1->start, r1->end, r2->start, r2->end);
+                                                r1->end = r2->end;
+                                                merged = true;
+                                        }
+                                        /* end in old range */
+                                        if (r1->start > r2->start) {
+                                                /* move start lower*/
+                                                log_debug("merge path %s range (%lx - %lx) with (%lx - %lx) start lower\n", p->path, r1->start, r1->end, r2->start, r2->end);
+                                                r1->start = r2->start;
+                                                merged = true;
+                                        }
+                                        if (merged)
+                                                LIST_REMOVE(ranges, p->ranges, r2);
+                                }
+                        }
+                }
+        }
+}
+
+static void add_range(const char *pathname, unsigned long start, unsigned long end, unsigned long op) {
+        struct path_ranges *p;
+
+        if (global_fusemac_mode == FUSEMAC_ENFORCE)
+                return;
+
+        LIST_FOREACH(paths, p, db) {
+                if (strcmp(p->path, pathname) == 0) {
+                        struct range *r;
+
+                        log_debug("found path %s\n", pathname);
+
+                        /* new ops are blocked in auto mode */
+                        if (global_fusemac_mode == FUSEMAC_AUTO && !(p->ops & (1 << op)))
+                                continue;
+
+                        p->ops |= 1 << op;
+                        LIST_FOREACH(ranges, r, p->ranges) {
+                                if (r->end == 0) {
+                                        /* not found */
+                                        log_debug("add range (%lx - %lx)\n", start, end);
+                                        r->end = end;
+                                        r->start = start;
+                                }
+                                if ((start >= r->start && start <= r->end) || (end >= r->start && end <= r->end)) {
+                                        /* start in old range */
+                                        if (r->end < end) {
+                                                /* move end higher */
+                                                log_debug("move range (%lx - %lx) end higher (%lx)\n", r->start, r->end, end);
+                                                r->end = end;
+                                        }
+                                        /* end in old range */
+                                        if (r->start > start) {
+                                                /* move start lower*/
+                                                log_debug("move range (%lx - %lx) start lower (%lx)\n", r->start, r->end, start);
+                                                r->start = start;
+                                        }
+                                        return;
+                                }
+                        }
+                        /* new ranges are blocked in auto mode */
+                        if (global_fusemac_mode != FUSEMAC_AUTO) {
+                                r = range_new(start, end);
+                                LIST_APPEND(ranges, p->ranges, r);
+                        }
+                        return;
+                }
+        }
+        /* new paths are blocked in auto mode */
+        if (global_fusemac_mode == FUSEMAC_AUTO)
+                return;
+        /* path not found */
+        p = path_ranges_new(pathname, op);
+        p->ranges = range_new(start, end);
+        LIST_APPEND(paths, db, p);
+        log_debug("add path %s (%lx - %lx)\n", pathname, start, end);
+}
+
+static void add_path(const char *pathname, unsigned long op) {
+        add_range(pathname, 0, 0, op);
+}
+
+static bool check_path(const char *pathname, unsigned long op) {
+        struct path_ranges *p;
+
+        if (global_fusemac_mode == FUSEMAC_LEARN)
+                return true;
+
+        LIST_FOREACH(paths, p, db) {
+                if (!p->path) {
+                        log_debug("check_path: did not find path %s\n", pathname);
+                        return false;
+                }
+                if (strcmp(p->path, pathname) == 0 && (p->ops & (1 << op))) {
+                        struct stat s;
+                        char buf[100];
+
+                        memset(&s, 0, sizeof(s));
+                        stat(pathname, &s);
+                        if (p->path_size == s.st_size && p->path_mtim == s.st_mtime)
+                                return true;
+
+                        if (global_fusemac_mode == FUSEMAC_AUTO) {
+                                /* file changed, switch to learning mode */
+                                log_debug("check_path: updated size/mtime %s, mode set to learning\n", pathname);
+                                global_fusemac_mode = FUSEMAC_LEARN;
+                                p->path_size = s.st_size;
+                                p->path_mtim = s.st_mtime;
+                                return true;
+                        }
+
+                        strftime(buf, sizeof(buf), "%s", localtime(&s.st_mtime));
+                        log_debug("bad %s: Size=%ld MTime=%s", pathname, s.st_size, buf);
+                        strftime(buf, sizeof(buf), "%s", localtime(&p->path_mtim));
+                        log_debug(" want: Size=%ld MTime=%s", p->path_size, buf);
+                        return false;
+                }
+        }
+        log_debug("check_path: did not find path %s\n", pathname);
+        return false;
+}
+
+static bool check_range(const char *pathname, unsigned long start, unsigned long end, unsigned long op) {
+        struct path_ranges *p;
+
+        if (global_fusemac_mode == FUSEMAC_LEARN)
+                return true;
+
+        LIST_FOREACH(paths, p, db) {
+                if (strcmp(p->path, pathname) == 0 && (p->ops & (1 << op))) {
+                        struct range *r;
+
+                        log_debug("check_range: found path %s\n", pathname);
+                        LIST_FOREACH(ranges, r, p->ranges) {
+                                if ((start >= r->start && start <= r->end) && (end >= r->start && end <= r->end)) {
+                                        log_debug("check_range: path %s: range %lx-%lx OK\n", pathname, start, end);
+                                        return true;
+                                }
+                        }
+                        log_debug("check_range: path %s: no ranges for %lx-%lx\n", pathname, start, end);
+                        return false;
+                }
+        }
+        log_debug("check_range: did not find path %s\n", pathname);
+        return false;
+}
+
+static void save_db(FILE *f) {
+        struct path_ranges *p;
+
+        merge_ranges();
+        LIST_FOREACH(paths, p, db) {
+                struct range *r;
+                char buf[100];
+                unsigned int j;
+
+                strftime(buf, sizeof(buf), "%s", localtime(&p->path_mtim));
+                fprintf(f, "[%s]\nSize=%ld\nMTime=%s", p->path, p->path_size, buf);
+
+                j = 0;
+                LIST_FOREACH(ranges, r, p->ranges) {
+                        if (r->end == 0)
+                                continue;
+
+                        if (j == 0)
+                                fprintf(f, "\nReadRanges=");
+
+                        if (j > 0)
+                                fprintf(f, " ");
+
+                        fprintf(f, "0x%lx-0x%lx", r->start, r->end);
+                        j++;
+                }
+
+                fprintf(f, "\nOps=");
+                for (j = 0; j < p_lastop; j++)
+                        if (p->ops & (1 << j))
+                                fprintf(f, "%s ", op_names[j]);
+
+                fprintf(f, "\n\n");
+        }
+}
+
+static void load_db(FILE *f) {
+        int n;
+        char line[8192], buf[8192], buf2[8192], *ptr;
+        struct path_ranges *p;
+
+        while (!feof(f)) {
+                do {
+                        ptr = fgets(line, sizeof(line), f);
+                        n = sscanf(line, "[%[^]]]\n", buf);
+                } while (ptr != NULL && n == 0);
+
+                if (ptr == NULL)
+                        break;
+
+                log_debug("path %s\n", buf);
+                p = path_ranges_new(buf, 0);
+
+                do {
+                        ptr = fgets(line, sizeof(line), f);
+                        if (ptr == NULL)
+                                break;
+
+                        n = sscanf(line, "%[^=]=%[^\n]\n", buf, buf2);
+                        if (n == 2) {
+                                log_debug("var %s = %s\n", buf, buf2);
+                                if (strcmp(buf, "Size") == 0)
+                                        p->path_size = strtoull(buf2, NULL, 0);
+                                else if (strcmp(buf, "MTime") == 0) {
+                                        struct tm t;
+                                        time_t mtim;
+
+                                        strptime(buf2, "%s", &t);
+                                        mtim = mktime(&t);
+                                        if (global_fusemac_mode == FUSEMAC_AUTO && mtim != p->path_mtim) {
+                                                /* file changed, switch to learning mode */
+                                                log_debug("check_path: updated size/mtime %s, mode set to learning\n", p->path);
+                                                global_fusemac_mode = FUSEMAC_LEARN;
+                                        }
+                                        p->path_mtim = mtim;
+                                } else if (strcmp(buf, "ReadRanges") == 0) {
+                                        int pos = 0, nread = 0;
+
+                                        do {
+                                                unsigned long start, end;
+
+                                                n = sscanf(&buf2[pos], "%lx-%lx%n", &start, &end, &nread);
+                                                pos += nread;
+                                                log_debug("range %lx-%lx nread %d n %d\n", start, end, nread, n);
+                                                if (n ==2) {
+                                                        struct range *r;
+
+                                                        r = range_new(start, end);
+                                                        LIST_APPEND(ranges, p->ranges, r);
+                                                }
+                                        } while (n == 2 && nread > 0 && buf2[pos] != 0 && buf2[pos] != '\n');
+                                } else if (strcmp(buf, "Ops") == 0) {
+                                        char *op, *cptr = buf2, *saveptr;
+
+                                        for (cptr = buf2; ; cptr = NULL) {
+                                                unsigned int j;
+
+                                                op = strtok_r(cptr, " ", &saveptr);
+                                                if (!op)
+                                                        break;
+
+                                                for (j = 0; j < p_lastop; j++)
+                                                        if (strcmp(op, op_names[j]) == 0) {
+                                                                log_debug("found op %s\n", op);
+                                                                p->ops |= 1 << j;
+                                                        }
+                                        }
+                                }
+                        }
+                } while (ptr != NULL && n == 2);
+
+                LIST_APPEND(paths, db, p);
+
+        }
+}
+
+static int fusemac_getattr(const char *pathname, struct stat *buf) {
+        int r;
+
+        if (!check_path(pathname, p_getattr))
+                return -EPERM;
+
+        r = lstat(pathname, buf);
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_getattr);
+
+        return r;
+}
+
+static int fusemac_access(const char *pathname, int mode) {
+        int r;
+
+        if (!check_path(pathname, p_access))
+                return -EPERM;
+
+        r = access(pathname, mode);
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_access);
+
+        return r;
+}
+
+static int fusemac_readlink(const char *pathname, char *buf, size_t bufsiz) {
+        int r;
+
+        if (!check_path(pathname, p_readlink))
+                return -EPERM;
+
+        r = readlink(pathname, buf, bufsiz - 1);
+        if (r == -1)
+                return -errno;
+
+        buf[r] = '\0';
+
+        add_path(pathname, p_readlink);
+
+        return r;
+}
+
+static int fusemac_readdir(const char *name, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
+        DIR *dirp;
+        struct dirent *dent;
+
+        if (!check_path(name, p_readdir))
+                return -EPERM;
+
+        dirp = opendir(name);
+        if (dirp == NULL)
+                return -errno;
+
+        while ((dent = readdir(dirp)) != NULL) {
+                struct stat st;
+
+                memset(&st, 0, sizeof(st));
+                st.st_ino = dent->d_ino;
+                st.st_mode = dent->d_type << 12;
+                if (filler(buf, dent->d_name, &st, 0))
+                        break;
+        }
+
+        closedir(dirp);
+
+        add_path(name, p_readdir);
+
+        return 0;
+}
+
+static int fusemac_mknod(const char *pathname, mode_t mode, dev_t dev) {
+        int r;
+
+        if (!check_path(pathname, p_mknod))
+                return -EPERM;
+
+        r = mknod(pathname, mode, dev);
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_mknod);
+
+        return r;
+}
+
+static int fusemac_mkdir(const char *pathname, mode_t mode) {
+        int r;
+
+        if (!check_path(pathname, p_mkdir))
+                return -EPERM;
+
+        r = mkdir(pathname, mode);
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_mkdir);
+
+        return r;
+}
+
+static int fusemac_unlink(const char *pathname) {
+        int r;
+
+        if (!check_path(pathname, p_unlink))
+                return -EPERM;
+
+        r = unlink(pathname);
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_unlink);
+        return r;
+}
+
+static int fusemac_rmdir(const char *pathname) {
+        int r;
+
+        if (!check_path(pathname, p_rmdir))
+                return -EPERM;
+
+        r = rmdir(pathname);
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_rmdir);
+        return r;
+}
+
+static int fusemac_symlink(const char *target, const char *linkpath) {
+        int r;
+
+        if (!check_path(linkpath, p_symlink))
+                return -EPERM;
+
+        r = symlink(target, linkpath);
+        if (r == -1)
+                return -errno;
+
+        add_path(linkpath, p_symlink);
+        return r;
+}
+
+static int fusemac_rename(const char *oldpath, const char *newpath) {
+        int r;
+
+        if (!check_path(oldpath, p_rename) || !check_path(newpath, p_rename))
+                return -EPERM;
+        r = rename(oldpath, newpath);
+        if (r == -1)
+                return -errno;
+
+        add_path(oldpath, p_rename);
+        add_path(newpath, p_rename);
+        return r;
+}
+
+static int fusemac_link(const char *oldpath, const char *newpath) {
+        int r;
+
+        if (!check_path(newpath, p_link))
+                return -EPERM;
+
+        r = link(oldpath, newpath);
+        if (r == -1)
+                return -errno;
+
+        add_path(newpath, p_link);
+        return r;
+}
+
+static int fusemac_chmod(const char *pathname, mode_t mode) {
+        int r;
+
+        if (!check_path(pathname, p_chmod))
+                return -EPERM;
+
+        r = chmod(pathname, mode);
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_chmod);
+        return r;
+}
+
+static int fusemac_chown(const char *pathname, uid_t owner, gid_t group) {
+        int r;
+
+        if (!check_path(pathname, p_chown))
+                return -EPERM;
+
+        r = lchown(pathname, owner, group);
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_chown);
+        return r;
+}
+
+static int fusemac_truncate(const char *path, off_t length) {
+        int r;
+
+        if (!check_path(path, p_truncate))
+                return -EPERM;
+
+        r = truncate(path, length);
+        if (r == -1)
+                return -errno;
+
+        add_path(path, p_truncate);
+        return r;
+}
+
+static int fusemac_utimens(const char *filename, const struct timespec times[2]) {
+        int r;
+
+        if (!check_path(filename, p_utimens))
+                return -EPERM;
+
+        r = utimensat(0, filename, times, AT_SYMLINK_NOFOLLOW);
+        if (r == -1)
+                return -errno;
+
+        add_path(filename, p_utimens);
+        return r;
+}
+
+static int fusemac_open(const char *pathname, struct fuse_file_info *fi) {
+        int r;
+
+        if (!check_path(pathname, p_open))
+                return -EPERM;
+
+        r = open(pathname, fi->flags);
+        if (r == -1)
+                return -errno;
+
+        close(r);
+
+        add_path(pathname, p_open);
+        return 0;
+}
+
+static int fusemac_read(const char *pathname, char *buf, size_t count, off_t offset, struct fuse_file_info *fi) {
+        int fd;
+        int r;
+
+        if (!check_range(pathname, offset, offset + count, p_read))
+                return -EPERM;
+
+        fd = open(pathname, O_RDONLY);
+        if (fd == -1)
+                return -errno;
+
+        r = pread(fd, buf, count, offset);
+        if (r == -1)
+                r = -errno;
+
+        close(fd);
+
+        add_range(pathname, offset, offset + count, p_read);
+        return r;
+}
+
+static int fusemac_write(const char *pathname, const char *buf, size_t count, off_t offset, struct fuse_file_info *fi) {
+        int fd;
+        int r;
+
+        if (!check_range(pathname, offset, offset + count, p_write))
+                return -EPERM;
+
+        fd = open(pathname, O_WRONLY);
+        if (fd == -1)
+                return -errno;
+
+        r = pwrite(fd, buf, count, offset);
+        if (r == -1)
+                r = -errno;
+
+        close(fd);
+
+        add_range(pathname, offset, offset + count, p_write);
+        return r;
+}
+
+static int fusemac_statfs(const char *path, struct statvfs *buf) {
+        int r;
+
+        if (!check_path(path, p_statfs))
+                return -EPERM;
+
+        r = statvfs(path, buf);
+        if (r == -1)
+                return -errno;
+
+        add_path(path, p_statfs);
+        return r;
+}
+
+static int fusemac_release(const char *pathname, struct fuse_file_info *fi) {
+        return 0;
+}
+
+static int fusemac_fsync(const char *pathname, int isdatasync, struct fuse_file_info *fi) {
+        return 0;
+}
+
+static int fusemac_fallocate(const char *pathname, int mode, off_t offset, off_t len, struct fuse_file_info *fi) {
+        int fd, r;
+
+        if (mode)
+                return -EOPNOTSUPP;
+
+        if (!check_path(pathname, p_fallocate))
+                return -EPERM;
+
+        fd = open(pathname, O_WRONLY);
+        if (fd == -1)
+                return -errno;
+
+        r = fallocate(fd, mode, offset, len);
+        close(fd);
+
+        if (r == -1)
+                return -errno;
+
+        add_path(pathname, p_fallocate);
+        return r;
+}
+
+static int fusemac_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) {
+        int r;
+
+        if (!check_path(path, p_setxattr))
+                return -EPERM;
+
+        r = lsetxattr(path, name, value, size, flags);
+        if (r == -1)
+                return -errno;
+
+        add_path(path, p_setxattr);
+        return r;
+}
+
+static int fusemac_getxattr(const char *path, const char *name, char *value, size_t size) {
+        int r;
+
+        if (!check_path(path, p_getxattr))
+                return -EPERM;
+
+        r = lgetxattr(path, name, value, size);
+        if (r == -1)
+                return -errno;
+
+        add_path(path, p_getxattr);
+        return r;
+}
+
+static int fusemac_listxattr(const char *path, char *list, size_t size) {
+        int r;
+
+        if (!check_path(path, p_listxattr))
+                return -EPERM;
+
+        r = llistxattr(path, list, size);
+        if (r == -1)
+                return -errno;
+
+        add_path(path, p_listxattr);
+        return r;
+}
+
+static int fusemac_removexattr(const char *path, const char *name) {
+        int r;
+
+        if (!check_path(path, p_removexattr))
+                return -EPERM;
+
+        r = lremovexattr(path, name);
+        if (r == -1)
+                return -errno;
+
+        add_path(path, p_removexattr);
+        return r;
+}
+
+static const struct fuse_operations fusemac_oper = {
+        .getattr        = fusemac_getattr,
+        .access         = fusemac_access,
+        .readlink       = fusemac_readlink,
+        .readdir        = fusemac_readdir,
+        .mknod          = fusemac_mknod,
+        .mkdir          = fusemac_mkdir,
+        .symlink        = fusemac_symlink,
+        .unlink         = fusemac_unlink,
+        .rmdir          = fusemac_rmdir,
+        .rename         = fusemac_rename,
+        .link           = fusemac_link,
+        .chmod          = fusemac_chmod,
+        .chown          = fusemac_chown,
+        .truncate       = fusemac_truncate,
+        .utimens        = fusemac_utimens,
+        .open           = fusemac_open,
+        .read           = fusemac_read,
+        .write          = fusemac_write,
+        .statfs         = fusemac_statfs,
+        .release        = fusemac_release,
+        .fsync          = fusemac_fsync,
+        .fallocate      = fusemac_fallocate,
+        .setxattr       = fusemac_setxattr,
+        .getxattr       = fusemac_getxattr,
+        .listxattr      = fusemac_listxattr,
+        .removexattr    = fusemac_removexattr,
+};
+
+static char *unit_fusemac_state;
+
+static void sighandler(int signum) {
+        FILE *f;
+
+        log_open();
+        log_warning("saving state %s", unit_fusemac_state);
+        log_close();
+        f = fopen(unit_fusemac_state, "w");
+        if (f) {
+                save_db(f);
+                fclose(f);
+        }
+}
+
+#ifndef TEST
+static int bind_apifs_mounts_to(const char *newroot) {
+        FILE *proc_self_mountinfo = NULL;
+        char *path = NULL, *p = NULL, *type = NULL;
+
+        proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
+        if (!proc_self_mountinfo)
+                return -errno;
+
+        for (;;) {
+                static const char apifs[] =
+                        "cgroup\0"
+                        "debugfs\0"
+                        "devtmpfs\0"
+                        "devpts\0"
+                        "hugetlbfs\0"
+                        "mqueue\0"
+                        "proc\0"
+                        "sysfs\0";
+                int k;
+
+                k = fscanf(proc_self_mountinfo,
+                           "%*s "       /* (1) mount id */
+                           "%*s "       /* (2) parent id */
+                           "%*s "       /* (3) major:minor */
+                           "%*s "       /* (4) root */
+                           "%ms "       /* (5) mount point */
+                           "%*s"        /* (6) mount options (superblock) */
+                           "%*[^-]"     /* (7) optional fields */
+                           "- "         /* (8) separator */
+                           "%ms "       /* (9) file system type */
+                           "%*s"        /* (10) mount source */
+                           "%*s"        /* (11) mount options (bind mount) */
+                           "%*[^\n]",   /* some rubbish at the end */
+                           &path,
+                           &type);
+                if (k != 2) {
+                        if (k == EOF)
+                                break;
+
+                        continue;
+                }
+
+                p = cunescape(path);
+                if (!p)
+                        return -ENOMEM;
+
+                /* Bind mount API fs */
+                if (nulstr_contains(apifs, type)) {
+                        char *dest;
+                        unsigned long orig_flags;
+
+                        dest = strjoina(newroot, path);
+                        mkdir_p(dest, 0755);
+
+                        if (mount(path, dest, NULL, MS_BIND|MS_REC, NULL) < 0)
+                                return -errno;
+
+                        orig_flags = 0;
+                        (void) get_mount_flags(path, &orig_flags);
+
+                        if (mount(NULL, dest, NULL, orig_flags|MS_BIND|MS_REMOUNT, NULL) < 0)
+                                return -errno;
+
+                        //free(dest);
+                }
+                //free(path);
+                //free(type);
+        }
+        fclose(proc_self_mountinfo);
+
+        return 0;
+}
+
+int setup_fusemac(FuseMAC fusemac_mode, const char *unit_id) {
+        char temporary_mount[] = "/tmp/fusemac-dev-XXXXXX";
+        FILE *f;
+        int r;
+        pid_t child;
+        const char *argv[] = {
+                unit_id,
+                "-o", "max_readahead=0",
+                "-o", "allow_other",
+                "-o", "default_permissions",
+                "-f",
+                "-d",
+                temporary_mount
+        };
+
+        unit_fusemac_state = strjoina("/var/lib/systemd/fusemac/", unit_id);
+        mkdir_parents(unit_fusemac_state, 0755);
+
+        log_open();
+        log_warning("loading state %s", unit_fusemac_state);
+        log_close();
+        f = fopen(unit_fusemac_state, "r");
+        if (f) {
+                load_db(f);
+                fclose(f);
+        }
+
+        if (!mkdtemp(temporary_mount)) {
+                r = errno;
+                log_open();
+                log_error_errno(errno, "Failed to create temp dir: %m");
+                log_close();
+                return -r;
+        }
+
+        log_open();
+        log_warning("created temporary mount %s", temporary_mount);
+        log_close();
+
+        r = bind_apifs_mounts_to(temporary_mount);
+        if (r < 0) {
+                log_open();
+                log_error_errno(r, "Failed to bind mounts: %m");
+                log_close();
+                return -r;
+        }
+
+        global_fusemac_mode = fusemac_mode;
+
+        child = fork();
+        if (child < 0) {
+                r = errno;
+                log_open();
+                log_error_errno(r, "Failed to fork(): %m");
+                log_close();
+                return -r;
+        }
+
+        if (child == 0) {
+                signal(SIGHUP, sighandler);
+
+                umask(0);
+                r = fuse_main(ELEMENTSOF(argv), (char **)argv, &fusemac_oper, NULL);
+                if (r != EXIT_SUCCESS) {
+                        log_open();
+                        log_error_errno(errno, "fuse_main failed: %m");
+                        log_close();
+                        _exit(r);
+                }
+
+                switch (fusemac_mode) {
+                case FUSEMAC_AUTO:
+                case FUSEMAC_LEARN:
+                        log_open();
+                        log_warning("saving state %s", unit_fusemac_state);
+                        log_close();
+                        f = fopen(unit_fusemac_state, "w");
+                        if (f) {
+                                save_db(f);
+                                fclose(f);
+                        }
+                        break;
+                default:
+                        break;
+                }
+                _exit(EXIT_SUCCESS);
+        }
+        log_open();
+        log_warning("forked pid %d, chroot", child);
+        log_close();
+
+        /* temporary kludge to wait for mount to appear */
+        sleep(1);
+
+        r = chdir(temporary_mount);
+        if (r < 0) {
+                r = errno;
+                log_open();
+                log_error_errno(r, "Failed to chdir(): %m");
+                log_close();
+                return -r;
+        }
+        r = chroot(temporary_mount);
+        if (r < 0) {
+                r = errno;
+                log_open();
+                log_error_errno(r, "Failed to chdir(): %m");
+                log_close();
+                return -r;
+        }
+        return 0;
+}
+
+#else
+
+/* to run: fusemac [--learn|--auto|--enforce] statefile -o max_readahead=0 -o allow_other -o default_permissions [-f] [-d] mountpoint */
+int main(int argc, char *argv[])
+{
+        int r;
+        const char *save_file = NULL;
+        bool dump = false;
+
+        umask(0);
+        if (argc >= 2 && argv[1]) {
+                if (strcmp(argv[1], "--learn") == 0 || strcmp(argv[1], "--auto") == 0) {
+                        FILE *f;
+
+                        if (strcmp(argv[1], "--auto") == 0)
+                                global_fusemac_mode = FUSEMAC_AUTO;
+                        else
+                                global_fusemac_mode = FUSEMAC_LEARN;
+
+                        f = fopen(argv[2], "r");
+                        if (f) {
+                                load_db(f);
+                                fclose(f);
+                        }
+                        save_file = argv[2];
+                        argc -=2;
+                        argv = &argv[2];
+                        dump = true;
+                } else if (strcmp(argv[1], "--enforce") == 0) {
+                        FILE *f;
+
+                        f = fopen(argv[2], "r");
+                        if (f) {
+                                load_db(f);
+                                fclose(f);
+                        }
+                        argc -=2;
+                        argv = &argv[2];
+                        global_fusemac_mode = FUSEMAC_ENFORCE;
+                }
+        }
+        r = fuse_main(argc, argv, &fusemac_oper, NULL);
+        //save_db(stderr);
+        if (dump) {
+                FILE *f;
+
+                f = fopen(save_file, "w");
+                save_db(f);
+                fclose(f);
+        }
+
+        return r;
+}
+#endif
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 5305984..38fc836 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -99,7 +99,10 @@ m4_ifdef(`HAVE_APPARMOR',
 `$1.AppArmorProfile,             config_parse_warn_compat,           DISABLED_CONFIGURATION,        0')
 m4_ifdef(`HAVE_SMACK',
 `$1.SmackProcessLabel,           config_parse_exec_smack_process_label, 0,                          offsetof($1, exec_context)',
-`$1.SmackProcessLabel,           config_parse_warn_compat,           DISABLED_CONFIGURATION,        0')'
+`$1.SmackProcessLabel,           config_parse_warn_compat,           DISABLED_CONFIGURATION,        0')
+m4_ifdef(`HAVE_FUSE',
+`$1.FuseMAC,                     config_parse_fusemac_mode,          0,                             offsetof($1, exec_context)',
+`$1.FuseMAC,                     config_parse_warn_compat,           DISABLED_CONFIGURATION,        0')'
 )m4_dnl
 m4_define(`KILL_CONTEXT_CONFIG_ITEMS',
 `$1.SendSIGKILL,                 config_parse_bool,                  0,                             offsetof($1, kill_context.send_sigkill)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 6d0192f..9f4046e 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -3370,6 +3370,37 @@ int config_parse_protect_system(
         return 0;
 }
 
+int config_parse_fusemac_mode(
+                const char* unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        ExecContext *c = data;
+        FuseMAC h;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        h = fusemac_mode_from_string(rvalue);
+        if (h < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, -h,
+                           "Failed to parse FuseMAC value, ignoring: %s", rvalue);
+                return 0;
+        }
+        c->fusemac_mode = h;
+
+        return 0;
+}
+
 #define FOLLOW_MAX 8
 
 static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index ce10d03..9b049b6 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -104,6 +104,7 @@ int config_parse_cpu_quota(const char *unit, const char *filename, unsigned line
 int config_parse_protect_home(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_protect_system(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_bus_name(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_fusemac_mode(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 
 /* gperf prototypes */
 const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);
diff --git a/src/shared/build.h b/src/shared/build.h
index 24873ab..32a6280 100644
--- a/src/shared/build.h
+++ b/src/shared/build.h
@@ -135,6 +135,12 @@
 #define _IDN_FEATURE_ "-IDN"
 #endif
 
+#ifdef HAVE_FUSE
+#define _FUSE_FEATURE_ "+FUSE"
+#else
+#define _FUSE_FEATURE_ "-FUSE"
+#endif
+
 #define SYSTEMD_FEATURES                                                \
         _PAM_FEATURE_ " "                                               \
         _AUDIT_FEATURE_ " "                                             \
@@ -154,4 +160,5 @@
         _BLKID_FEATURE_ " "                                             \
         _ELFUTILS_FEATURE_ " "                                          \
         _KMOD_FEATURE_ " "                                              \
-        _IDN_FEATURE_
+        _IDN_FEATURE_ " "                                               \
+        _FUSE_FEATURE_
diff --git a/src/shared/exit-status.c b/src/shared/exit-status.c
index 57022c5..59bd389 100644
--- a/src/shared/exit-status.c
+++ b/src/shared/exit-status.c
@@ -150,6 +150,9 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) {
 
                 case EXIT_BUS_ENDPOINT:
                         return "BUS_ENDPOINT";
+
+                case EXIT_FUSE:
+                        return "FUSE";
                 }
         }
 
diff --git a/src/shared/exit-status.h b/src/shared/exit-status.h
index 1d774f2..96341e8 100644
--- a/src/shared/exit-status.h
+++ b/src/shared/exit-status.h
@@ -79,6 +79,7 @@ typedef enum ExitStatus {
         EXIT_CHOWN,
         EXIT_BUS_ENDPOINT,
         EXIT_SMACK_PROCESS_LABEL,
+        EXIT_FUSE,
 } ExitStatus;
 
 typedef enum ExitStatusLevel {
diff --git a/src/shared/util.c b/src/shared/util.c
index 4a970af..369bfc0 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -6913,7 +6913,7 @@ int umount_recursive(const char *prefix, int flags) {
         return r ? r : n;
 }
 
-static int get_mount_flags(const char *path, unsigned long *flags) {
+int get_mount_flags(const char *path, unsigned long *flags) {
         struct statvfs buf;
 
         if (statvfs(path, &buf) < 0)
diff --git a/src/shared/util.h b/src/shared/util.h
index 2de654f..8bb96a4 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -1001,6 +1001,8 @@ int update_reboot_param_file(const char *param);
 
 int umount_recursive(const char *target, int flags);
 
+int get_mount_flags(const char *path, unsigned long *flags);
+
 int bind_remount_recursive(const char *prefix, bool ro);
 
 int fflush_and_check(FILE *f);
-- 
2.1.4



More information about the systemd-devel mailing list