]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add ability to define arbitrary bind mounts for services
authorLennart Poettering <lennart@poettering.net>
Wed, 23 Nov 2016 21:21:40 +0000 (22:21 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 13 Dec 2016 23:54:10 +0000 (00:54 +0100)
This adds two new settings BindPaths= and BindReadOnlyPaths=. They allow
defining arbitrary bind mounts specific to particular services. This is
particularly useful for services with RootDirectory= set as this permits making
specific bits of the host directory available to chrooted services.

The two new settings follow the concepts nspawn already possess in --bind= and
--bind-ro=, as well as the .nspawn settings Bind= and BindReadOnly= (and these
latter options should probably be renamed to BindPaths= and BindReadOnlyPaths=
too).

Fixes: #3439
man/systemd.exec.xml
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/core/namespace.c
src/core/namespace.h
src/shared/bus-unit-util.c
src/test/test-ns.c

index f27e4a5c04cc17a3f332e966f5783bd0ceea788c..812e61553068d4b8b060a54c380909792e89234e 100644 (file)
         <varname>SystemCallFilter=~@mount</varname>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>BindPaths=</varname></term>
+        <term><varname>BindReadOnlyPaths=</varname></term>
+
+        <listitem><para>Configures unit-specific bind mounts. A bind mount makes a particular file or directory
+        available at an additional place in the unit's view of the file system. Any bind mounts created with this
+        option are specific to the unit, and are not visible in the host's mount table. This option expects a
+        whitespace separated list of bind mount definitions. Each definition consists of a colon-separated triple of
+        source path, destination path and option string, where the latter two are optional. If only a source path is
+        specified the source and destination is taken to be the same. The option string may be either
+        <literal>rbind</literal> or <literal>norbind</literal> for configuring a recursive or non-recursive bind
+        mount. If the destination parth is omitted, the option string must be omitted too.</para>
+
+        <para><varname>BindPaths=</varname> creates regular writable bind mounts (unless the source file system mount
+        is already marked read-only), while <varname>BindReadOnlyPaths=</varname> creates read-only bind mounts. These
+        settings may be used more than once, each usage appends to the unit's list of bind mounts. If the empty string
+        is assigned to either of these two options the entire list of bind mounts defined prior to this is reset. Note
+        that in this case both read-only and regular bind mounts are reset, regardless which of the two settings is
+        used.</para>
+
+        <para>This option is particularly useful when <varname>RootDirectory=</varname> is used. In this case the
+        source path refers to a path on the host file system, while the destination path referes to a path below the
+        root directory of the unit.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>PrivateTmp=</varname></term>
 
index 78b177e1075bb8af9c35804cff75f7a9d08199c7..b3fc0ff5c339494f44552eed164956dcdd223613 100644 (file)
@@ -675,6 +675,49 @@ static int property_get_output_fdname(
         return sd_bus_message_append(reply, "s", name);
 }
 
+static int property_get_bind_paths(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        ExecContext *c = userdata;
+        unsigned i;
+        bool ro;
+        int r;
+
+        assert(bus);
+        assert(c);
+        assert(property);
+        assert(reply);
+
+        ro = !!strstr(property, "ReadOnly");
+
+        r = sd_bus_message_open_container(reply, 'a', "(ssbt)");
+        if (r < 0)
+                return r;
+
+        for (i = 0; i < c->n_bind_mounts; i++) {
+
+                if (ro != c->bind_mounts[i].read_only)
+                        continue;
+
+                r = sd_bus_message_append(
+                                reply, "(ssbt)",
+                                c->bind_mounts[i].source,
+                                c->bind_mounts[i].destination,
+                                c->bind_mounts[i].ignore_enoent,
+                                c->bind_mounts[i].recursive ? MS_REC : 0);
+                if (r < 0)
+                        return r;
+        }
+
+        return sd_bus_message_close_container(reply);
+}
+
 const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -783,6 +826,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RestrictNamespaces", "t", bus_property_get_ulong, offsetof(ExecContext, restrict_namespaces), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("BindPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("BindReadOnlyPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_VTABLE_END
 };
 
@@ -1364,8 +1409,8 @@ int bus_exec_context_set_transient_property(
                         if (r < 0)
                                 return r;
 
-                        if (!isempty(path) && !path_is_absolute(path))
-                                return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path);
+                        if (!path_is_absolute(path))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path);
 
                         if (mode != UNIT_CHECK) {
                                 char *buf = NULL;
@@ -1629,8 +1674,74 @@ int bus_exec_context_set_transient_property(
                         unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, strempty(mount_propagation_flags_to_string(flags)));
                 }
 
+                return 1;
+        } else if (STR_IN_SET(name, "BindPaths", "BindReadOnlyPaths")) {
+                unsigned empty = true;
+
+                r = sd_bus_message_enter_container(message, 'a', "(ssbt)");
+                if (r < 0)
+                        return r;
+
+                while ((r = sd_bus_message_enter_container(message, 'r', "ssbt")) > 0) {
+                        const char *source, *destination;
+                        int ignore_enoent;
+                        uint64_t mount_flags;
+
+                        r = sd_bus_message_read(message, "ssbt", &source, &destination, &ignore_enoent, &mount_flags);
+                        if (r < 0)
+                                return r;
+
+                        r = sd_bus_message_exit_container(message);
+                        if (r < 0)
+                                return r;
+
+                        if (!path_is_absolute(source))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not absolute.", source);
+                        if (!path_is_absolute(destination))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not absolute.", source);
+                        if (!IN_SET(mount_flags, 0, MS_REC))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown mount flags.");
+
+                        if (mode != UNIT_CHECK) {
+                                r = bind_mount_add(&c->bind_mounts, &c->n_bind_mounts,
+                                                   &(BindMount) {
+                                                           .source = strdup(source),
+                                                           .destination = strdup(destination),
+                                                           .read_only = !!strstr(name, "ReadOnly"),
+                                                           .recursive = !!(mount_flags & MS_REC),
+                                                           .ignore_enoent = ignore_enoent,
+                                                   });
+                                if (r < 0)
+                                        return r;
+
+                                unit_write_drop_in_private_format(
+                                                u, mode, name,
+                                                "%s=%s%s:%s:%s",
+                                                name,
+                                                ignore_enoent ? "-" : "",
+                                                source,
+                                                destination,
+                                                (mount_flags & MS_REC) ? "rbind" : "norbind");
+                        }
+
+                        empty = false;
+                }
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_exit_container(message);
+                if (r < 0)
+                        return r;
+
+                if (empty) {
+                        bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
+                        c->bind_mounts = NULL;
+                        c->n_bind_mounts = 0;
+                }
+
                 return 1;
         }
+
         ri = rlimit_from_string(name);
         if (ri < 0) {
                 soft = endswith(name, "Soft");
index 07ab067c05769bd1d1389037c15abd787296d52e..2ee8c9a416f1b1feddc932475093951b73fb4802 100644 (file)
@@ -1826,6 +1826,9 @@ static bool exec_needs_mount_namespace(
             !strv_isempty(context->inaccessible_paths))
                 return true;
 
+        if (context->n_bind_mounts > 0)
+                return true;
+
         if (context->mount_flags != 0)
                 return true;
 
@@ -2147,6 +2150,8 @@ static int apply_mount_namespace(Unit *u, const ExecContext *context,
         r = setup_namespace(root_dir, &ns_info, rw,
                             context->read_only_paths,
                             context->inaccessible_paths,
+                            context->bind_mounts,
+                            context->n_bind_mounts,
                             tmp,
                             var,
                             context->protect_home,
@@ -3086,6 +3091,8 @@ void exec_context_done(ExecContext *c) {
         c->read_write_paths = strv_free(c->read_write_paths);
         c->inaccessible_paths = strv_free(c->inaccessible_paths);
 
+        bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
+
         if (c->cpuset)
                 CPU_FREE(c->cpuset);
 
@@ -3569,6 +3576,15 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
                 fputs("\n", f);
         }
 
+        if (c->n_bind_mounts > 0)
+                for (i = 0; i < c->n_bind_mounts; i++) {
+                        fprintf(f, "%s%s: %s:%s:%s\n", prefix,
+                                c->bind_mounts[i].read_only ? "BindReadOnlyPaths" : "BindPaths",
+                                c->bind_mounts[i].source,
+                                c->bind_mounts[i].destination,
+                                c->bind_mounts[i].recursive ? "rbind" : "norbind");
+                }
+
         if (c->utmp_id)
                 fprintf(f,
                         "%sUtmpIdentifier: %s\n",
index 951c8f4da3881057fcb1a027372bc39d838f408a..84ab4339cf3561360266f4ad59413813941071cb 100644 (file)
@@ -161,6 +161,8 @@ struct ExecContext {
 
         char **read_write_paths, **read_only_paths, **inaccessible_paths;
         unsigned long mount_flags;
+        BindMount *bind_mounts;
+        unsigned n_bind_mounts;
 
         uint64_t capability_bounding_set;
         uint64_t capability_ambient_set;
index 2610442b919de5746b4dcd010e438e54182a85fb..15f22a2681adb4d007e5ba9c795f5ea3e377d32f 100644 (file)
@@ -89,6 +89,8 @@ $1.InaccessibleDirectories,      config_parse_namespace_path_strv,   0,
 $1.ReadWritePaths,               config_parse_namespace_path_strv,   0,                             offsetof($1, exec_context.read_write_paths)
 $1.ReadOnlyPaths,                config_parse_namespace_path_strv,   0,                             offsetof($1, exec_context.read_only_paths)
 $1.InaccessiblePaths,            config_parse_namespace_path_strv,   0,                             offsetof($1, exec_context.inaccessible_paths)
+$1.BindPaths,                    config_parse_bind_paths,            0,                             offsetof($1, exec_context)
+$1.BindReadOnlyPaths,            config_parse_bind_paths,            0,                             offsetof($1, exec_context)
 $1.PrivateTmp,                   config_parse_bool,                  0,                             offsetof($1, exec_context.private_tmp)
 $1.PrivateDevices,               config_parse_bool,                  0,                             offsetof($1, exec_context.private_devices)
 $1.ProtectKernelTunables,        config_parse_bool,                  0,                             offsetof($1, exec_context.protect_kernel_tunables)
index a2e7097de0b412acb8e233cc7fcd64ade58a418d..f325d853c65fb0301e4e3f0ca416050f6e3b470c 100644 (file)
@@ -3891,6 +3891,132 @@ int config_parse_namespace_path_strv(
         return 0;
 }
 
+int config_parse_bind_paths(
+                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;
+        const char *p;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (isempty(rvalue)) {
+                /* Empty assignment resets the list */
+                bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
+                c->bind_mounts = NULL;
+                c->n_bind_mounts = 0;
+                return 0;
+        }
+
+        p = rvalue;
+        for (;;) {
+                _cleanup_free_ char *source = NULL, *destination = NULL;
+                char *s = NULL, *d = NULL;
+                bool rbind = true, ignore_enoent = false;
+
+                r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r == 0)
+                        break;
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
+                        return 0;
+                }
+
+                s = source;
+                if (s[0] == '-') {
+                        ignore_enoent = true;
+                        s++;
+                }
+
+                if (!utf8_is_valid(s)) {
+                        log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, s);
+                        return 0;
+                }
+                if (!path_is_absolute(s)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute source path, ignoring: %s", s);
+                        return 0;
+                }
+
+                path_kill_slashes(s);
+
+                /* Optionally, the destination is specified. */
+                if (p && p[-1] == ':') {
+                        r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
+                        if (r == -ENOMEM)
+                                return log_oom();
+                        if (r < 0) {
+                                log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
+                                return 0;
+                        }
+                        if (r == 0) {
+                                log_syntax(unit, LOG_ERR, filename, line, 0, "Missing argument after ':': %s", rvalue);
+                                return 0;
+                        }
+
+                        if (!utf8_is_valid(destination)) {
+                                log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, destination);
+                                return 0;
+                        }
+                        if (!path_is_absolute(destination)) {
+                                log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute destination path, ignoring: %s", destination);
+                                return 0;
+                        }
+
+                        d = path_kill_slashes(destination);
+
+                        /* Optionally, there's also a short option string specified */
+                        if (p && p[-1] == ':') {
+                                _cleanup_free_ char *options = NULL;
+
+                                r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES);
+                                if (r == -ENOMEM)
+                                        return log_oom();
+                                if (r < 0) {
+                                        log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
+                                        return 0;
+                                }
+
+                                if (isempty(options) || streq(options, "rbind"))
+                                        rbind = true;
+                                else if (streq(options, "norbind"))
+                                        rbind = false;
+                                else {
+                                        log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid option string, ignoring setting: %s", options);
+                                        return 0;
+                                }
+                        }
+                } else
+                        d = s;
+
+                r = bind_mount_add(&c->bind_mounts, &c->n_bind_mounts,
+                                   &(BindMount) {
+                                           .source = s,
+                                           .destination = d,
+                                           .read_only = !!strstr(lvalue, "ReadOnly"),
+                                           .recursive = rbind,
+                                           .ignore_enoent = ignore_enoent,
+                                   });
+                if (r < 0)
+                        return log_oom();
+        }
+
+        return 0;
+}
+
 int config_parse_no_new_privileges(
                 const char* unit,
                 const char *filename,
@@ -4388,6 +4514,7 @@ void unit_dump_config_items(FILE *f) {
                 { config_parse_sec,                   "SECONDS" },
                 { config_parse_nsec,                  "NANOSECONDS" },
                 { config_parse_namespace_path_strv,   "PATH [...]" },
+                { config_parse_bind_paths,            "PATH[:PATH[:OPTIONS]] [...]" },
                 { config_parse_unit_requires_mounts_for, "PATH [...]" },
                 { config_parse_exec_mount_flags,      "MOUNTFLAG [...]" },
                 { config_parse_unit_string_printf,    "STRING" },
index 1cff815a50f367272a8fdaf4cad7df0805bc7450..bbac2d84b59ef1078a82dd674c826875aaca8f35 100644 (file)
@@ -117,6 +117,7 @@ int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line
 int config_parse_user_group(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_user_group_strv(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_restrict_namespaces(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_bind_paths(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);
index 746ebc45b0a6dc0daf87b367bfac4773532071b9..834883267c363971600258311d0697440fb037a1 100644 (file)
@@ -50,6 +50,8 @@
 typedef enum MountMode {
         /* This is ordered by priority! */
         INACCESSIBLE,
+        BIND_MOUNT,
+        BIND_MOUNT_RECURSIVE,
         READONLY,
         PRIVATE_TMP,
         PRIVATE_VAR_TMP,
@@ -64,6 +66,8 @@ typedef struct MountEntry {
         bool has_prefix:1;        /* Already is prefixed by the root dir? */
         bool read_only:1;         /* Shall this mount point be read-only? */
         char *path_malloc;        /* Use this instead of 'path' if we had to allocate memory */
+        const char *source_const; /* The source path, for bind mounts */
+        char *source_malloc;
 } MountEntry;
 
 /*
@@ -166,6 +170,12 @@ static bool mount_entry_read_only(const MountEntry *p) {
         return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE);
 }
 
+static const char *mount_entry_source(const MountEntry *p) {
+        assert(p);
+
+        return p->source_malloc ?: p->source_const;
+}
+
 static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) {
         char **i;
 
@@ -201,6 +211,25 @@ static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) {
         return 0;
 }
 
+static int append_bind_mounts(MountEntry **p, const BindMount *binds, unsigned n) {
+        unsigned i;
+
+        assert(p);
+
+        for (i = 0; i < n; i++) {
+                const BindMount *b = binds + i;
+
+                *((*p)++) = (MountEntry) {
+                        .path_const = b->destination,
+                        .mode = b->recursive ? BIND_MOUNT_RECURSIVE : BIND_MOUNT,
+                        .read_only = b->read_only,
+                        .source_const = b->source,
+                };
+        }
+
+        return 0;
+}
+
 static int append_static_mounts(MountEntry **p, const MountEntry *mounts, unsigned n, bool ignore_protect) {
         unsigned i;
 
@@ -568,27 +597,33 @@ fail:
         return r;
 }
 
-static int mount_entry_chase(MountEntry *m, const char *root_directory) {
+static int mount_entry_chase(
+                const char *root_directory,
+                MountEntry *m,
+                const char *path,
+                char **location) {
+
         char *chased;
         int r;
 
         assert(m);
 
         /* Since mount() will always follow symlinks and we need to take the different root directory into account we
-         * chase the symlinks on our own first. */
+         * chase the symlinks on our own first. This is called for the destination path, as well as the source path (if
+         * that applies). The result is stored in "location". */
 
-        r = chase_symlinks(mount_entry_path(m), root_directory, 0, &chased);
+        r = chase_symlinks(path, root_directory, 0, &chased);
         if (r == -ENOENT && m->ignore) {
-                log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_path(m));
+                log_debug_errno(r, "Path %s does not exist, ignoring.", path);
                 return 0;
         }
         if (r < 0)
-                return log_debug_errno(r, "Failed to follow symlinks on %s: %m", mount_entry_path(m));
+                return log_debug_errno(r, "Failed to follow symlinks on %s: %m", path);
 
-        log_debug("Followed symlinks %s → %s.", mount_entry_path(m), chased);
+        log_debug("Followed symlinks %s → %s.", path, chased);
 
-        free(m->path_malloc);
-        m->path_malloc = chased;
+        free(*location);
+        *location = chased;
 
         return 1;
 }
@@ -600,11 +635,12 @@ static int apply_mount(
                 const char *var_tmp_dir) {
 
         const char *what;
+        bool rbind = true;
         int r;
 
         assert(m);
 
-        r = mount_entry_chase(m, root_directory);
+        r = mount_entry_chase(root_directory, m, mount_entry_path(m), &m->path_malloc);
         if (r <= 0)
                 return r;
 
@@ -633,7 +669,6 @@ static int apply_mount(
 
         case READONLY:
         case READWRITE:
-
                 r = path_is_mount_point(mount_entry_path(m), root_directory, 0);
                 if (r < 0)
                         return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", mount_entry_path(m));
@@ -643,6 +678,19 @@ static int apply_mount(
                 what = mount_entry_path(m);
                 break;
 
+        case BIND_MOUNT:
+                rbind = false;
+                /* fallthrough */
+
+        case BIND_MOUNT_RECURSIVE:
+                /* Also chase the source mount */
+                r = mount_entry_chase(root_directory, m, mount_entry_source(m), &m->source_malloc);
+                if (r <= 0)
+                        return r;
+
+                what = mount_entry_source(m);
+                break;
+
         case PRIVATE_TMP:
                 what = tmp_dir;
                 break;
@@ -660,7 +708,7 @@ static int apply_mount(
 
         assert(what);
 
-        if (mount(what, mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL) < 0)
+        if (mount(what, mount_entry_path(m), NULL, MS_BIND|(rbind ? MS_REC : 0), NULL) < 0)
                 return log_debug_errno(errno, "Failed to mount %s to %s: %m", what, mount_entry_path(m));
 
         log_debug("Successfully mounted %s to %s", what, mount_entry_path(m));
@@ -695,6 +743,8 @@ static unsigned namespace_calculate_mounts(
                 char** read_write_paths,
                 char** read_only_paths,
                 char** inaccessible_paths,
+                const BindMount *bind_mounts,
+                unsigned n_bind_mounts,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
                 ProtectHome protect_home,
@@ -719,6 +769,7 @@ static unsigned namespace_calculate_mounts(
                 strv_length(read_write_paths) +
                 strv_length(read_only_paths) +
                 strv_length(inaccessible_paths) +
+                n_bind_mounts +
                 ns_info->private_dev +
                 (ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
                 (ns_info->protect_control_groups ? 1 : 0) +
@@ -732,6 +783,8 @@ int setup_namespace(
                 char** read_write_paths,
                 char** read_only_paths,
                 char** inaccessible_paths,
+                const BindMount *bind_mounts,
+                unsigned n_bind_mounts,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
                 ProtectHome protect_home,
@@ -751,6 +804,7 @@ int setup_namespace(
                         read_write_paths,
                         read_only_paths,
                         inaccessible_paths,
+                        bind_mounts, n_bind_mounts,
                         tmp_dir, var_tmp_dir,
                         protect_home, protect_system);
 
@@ -772,6 +826,10 @@ int setup_namespace(
                 if (r < 0)
                         goto finish;
 
+                r = append_bind_mounts(&m, bind_mounts, n_bind_mounts);
+                if (r < 0)
+                        goto finish;
+
                 if (tmp_dir) {
                         *(m++) = (MountEntry) {
                                 .path_const = "/tmp",
@@ -911,6 +969,53 @@ finish:
         return r;
 }
 
+void bind_mount_free_many(BindMount *b, unsigned n) {
+        unsigned i;
+
+        assert(b || n == 0);
+
+        for (i = 0; i < n; i++) {
+                free(b[i].source);
+                free(b[i].destination);
+        }
+
+        free(b);
+}
+
+int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item) {
+        _cleanup_free_ char *s = NULL, *d = NULL;
+        BindMount *c;
+
+        assert(b);
+        assert(n);
+        assert(item);
+
+        s = strdup(item->source);
+        if (!s)
+                return -ENOMEM;
+
+        d = strdup(item->destination);
+        if (!d)
+                return -ENOMEM;
+
+        c = realloc_multiply(*b, sizeof(BindMount), *n + 1);
+        if (!c)
+                return -ENOMEM;
+
+        *b = c;
+
+        c[(*n) ++] = (BindMount) {
+                .source = s,
+                .destination = d,
+                .read_only = item->read_only,
+                .recursive = item->recursive,
+                .ignore_enoent = item->ignore_enoent,
+        };
+
+        s = d = NULL;
+        return 0;
+}
+
 static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) {
         _cleanup_free_ char *x = NULL;
         char bid[SD_ID128_STRING_MAX];
index 2c278fd457cf82e98d8dc16f2d2f3ff3d1f9fc02..de3edc419cce3411e2955009d507835b4b79dd52 100644 (file)
@@ -21,6 +21,7 @@
 ***/
 
 typedef struct NameSpaceInfo NameSpaceInfo;
+typedef struct BindMount BindMount;
 
 #include <stdbool.h>
 
@@ -51,20 +52,32 @@ struct NameSpaceInfo {
         bool protect_kernel_modules:1;
 };
 
-int setup_namespace(const char *chroot,
-                    const NameSpaceInfo *ns_info,
-                    char **read_write_paths,
-                    char **read_only_paths,
-                    char **inaccessible_paths,
-                    const char *tmp_dir,
-                    const char *var_tmp_dir,
-                    ProtectHome protect_home,
-                    ProtectSystem protect_system,
-                    unsigned long mount_flags);
-
-int setup_tmp_dirs(const char *id,
-                  char **tmp_dir,
-                  char **var_tmp_dir);
+struct BindMount {
+        char *source;
+        char *destination;
+        bool read_only:1;
+        bool recursive:1;
+        bool ignore_enoent:1;
+};
+
+int setup_namespace(
+                const char *root_directory,
+                const NameSpaceInfo *ns_info,
+                char **read_write_paths,
+                char **read_only_paths,
+                char **inaccessible_paths,
+                const BindMount *bind_mounts,
+                unsigned n_bind_mounts,
+                const char *tmp_dir,
+                const char *var_tmp_dir,
+                ProtectHome protect_home,
+                ProtectSystem protect_system,
+                unsigned long mount_flags);
+
+int setup_tmp_dirs(
+                const char *id,
+                char **tmp_dir,
+                char **var_tmp_dir);
 
 int setup_netns(int netns_storage_socket[2]);
 
@@ -73,3 +86,6 @@ ProtectHome protect_home_from_string(const char *s) _pure_;
 
 const char* protect_system_to_string(ProtectSystem p) _const_;
 ProtectSystem protect_system_from_string(const char *s) _pure_;
+
+void bind_mount_free_many(BindMount *b, unsigned n);
+int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item);
index 6dd9f2ccd4580943538f7db015056e73103202c7..735b8effc968061c2a30df0fbfb0c60806512e4e 100644 (file)
@@ -590,6 +590,76 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
                 }
 
                 r = sd_bus_message_append(m, "v", "t", f);
+        } else if (STR_IN_SET(field, "BindPaths", "BindReadOnlyPaths")) {
+                const char *p = eq;
+
+                r = sd_bus_message_open_container(m, 'v', "a(ssbt)");
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_open_container(m, 'a', "(ssbt)");
+                if (r < 0)
+                        return r;
+
+                for (;;) {
+                        _cleanup_free_ char *source = NULL, *destination = NULL;
+                        char *s = NULL, *d = NULL;
+                        bool ignore_enoent = false;
+                        uint64_t flags = MS_REC;
+
+                        r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse argument: %m");
+                        if (r == 0)
+                                break;
+
+                        s = source;
+                        if (s[0] == '-') {
+                                ignore_enoent = true;
+                                s++;
+                        }
+
+                        if (p && p[-1] == ':') {
+                                r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse argument: %m");
+                                if (r == 0) {
+                                        log_error("Missing argument after ':': %s", eq);
+                                        return -EINVAL;
+                                }
+
+                                d = destination;
+
+                                if (p && p[-1] == ':') {
+                                        _cleanup_free_ char *options = NULL;
+
+                                        r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES);
+                                        if (r < 0)
+                                                return log_error_errno(r, "Failed to parse argument: %m");
+
+                                        if (isempty(options) || streq(options, "rbind"))
+                                                flags = MS_REC;
+                                        else if (streq(options, "norbind"))
+                                                flags = 0;
+                                        else {
+                                                log_error("Unknown options: %s", eq);
+                                                return -EINVAL;
+                                        }
+                                }
+                        } else
+                                d = s;
+
+
+                        r = sd_bus_message_append(m, "(ssbt)", s, d, ignore_enoent, flags);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_close_container(m);
         } else {
                 log_error("Unknown assignment %s.", assignment);
                 return -EINVAL;
index da7a8b0565a61fda36025bf660fccf1a1651f680..c99bcb371b99b545be5303ee1f65cb24bfe2efee 100644 (file)
@@ -81,6 +81,7 @@ int main(int argc, char *argv[]) {
                             (char **) writable,
                             (char **) readonly,
                             (char **) inaccessible,
+                            &(BindMount) { .source = (char*) "/usr/bin", .destination = (char*) "/etc/systemd", .read_only = true }, 1,
                             tmp_dir,
                             var_tmp_dir,
                             PROTECT_HOME_NO,