]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add ExtensionDirectories= setting
authorLuca Boccassi <luca.boccassi@microsoft.com>
Mon, 17 Jan 2022 01:14:14 +0000 (01:14 +0000)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 21 Jan 2022 13:53:12 +0000 (22:53 +0900)
Add a new setting that follows the same principle and implementation
as ExtensionImages, but using directories as sources.
It will be used to implement support for extending portable images
with directories, since portable services can already use a directory
as root.

17 files changed:
man/org.freedesktop.systemd1.xml
man/systemd.exec.xml
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.in
src/core/namespace.c
src/core/namespace.h
src/shared/bus-unit-util.c
src/test/test-namespace.c
src/test/test-ns.c
test/fuzz/fuzz-unit-file/directives-all.service
test/fuzz/fuzz-unit-file/directives.mount
test/fuzz/fuzz-unit-file/directives.service
test/fuzz/fuzz-unit-file/directives.socket
test/fuzz/fuzz-unit-file/directives.swap
test/units/testsuite-50.sh

index bd69a00b57cabb7db30e01027c3f411a7b36fde5..97a8c98b39e0d6d6a8637f8a36106440e855e809 100644 (file)
@@ -2682,6 +2682,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s RootVerity = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as ExtensionDirectories = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(sba(ss)) ExtensionImages = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(ssba(ss)) MountImages = [...];
@@ -3827,6 +3829,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="RootVerity"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtensionDirectories"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImages"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="MountImages"/>
@@ -4185,6 +4189,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       <varname>RootHashSignature</varname>
       <varname>MountImages</varname>
       <varname>ExtensionImages</varname>
+      <varname>ExtensionDirectories</varname>
       see systemd.exec(5) for their meaning.</para>
 
       <para><varname>MemoryAvailable</varname> indicates how much unused memory is available to the unit before
@@ -4559,6 +4564,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s RootVerity = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as ExtensionDirectories = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(sba(ss)) ExtensionImages = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(ssba(ss)) MountImages = [...];
@@ -5722,6 +5729,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <variablelist class="dbus-property" generated="True" extra-ref="RootVerity"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtensionDirectories"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImages"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="MountImages"/>
@@ -6344,6 +6353,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s RootVerity = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as ExtensionDirectories = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(sba(ss)) ExtensionImages = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(ssba(ss)) MountImages = [...];
@@ -7353,6 +7364,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <variablelist class="dbus-property" generated="True" extra-ref="RootVerity"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtensionDirectories"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImages"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="MountImages"/>
@@ -8102,6 +8115,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s RootVerity = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as ExtensionDirectories = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(sba(ss)) ExtensionImages = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(ssba(ss)) MountImages = [...];
@@ -9083,6 +9098,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <variablelist class="dbus-property" generated="True" extra-ref="RootVerity"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtensionDirectories"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImages"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="MountImages"/>
index 079ff14aeaffc6357922b0e9130b828dce13fedf..36a884c9f443a6754386ae0f1a1f33e7905511d2 100644 (file)
 
         <xi:include href="system-only.xml" xpointer="singular"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>ExtensionDirectories=</varname></term>
+
+        <listitem><para>This setting is similar to <varname>BindReadOnlyPaths=</varname> in that it mounts a file
+        system hierarchy from a directory, but instead of providing a destination path, an overlay will be set
+        up. This option expects a whitespace separated list of source directories.</para>
+
+        <para>A read-only OverlayFS will be set up on top of <filename>/usr/</filename> and
+        <filename>/opt/</filename> hierarchies. The order in which the directories are listed will determine
+        the order in which the overlay is laid down: directories specified first to last will result in overlayfs
+        layers bottom to top.</para>
+
+        <para>Each directory listed in <varname>ExtensionDirectories=</varname> may be prefixed with <literal>-</literal>,
+        in which case it will be ignored when its source path does not exist. Any mounts created with this option are
+        specific to the unit, and are not visible in the host's mount table.</para>
+
+        <para>These settings may be used more than once, each usage appends to the unit's list of directories
+        paths. If the empty string is assigned, the entire list of mount paths defined prior to this is
+        reset.</para>
+
+        <para>Each directory must contain a <filename>/usr/lib/extension-release.d/extension-release.IMAGE</filename>
+        file, with the appropriate metadata which matches <varname>RootImage=</varname>/<varname>RootDirectory=</varname>
+        or the host. See:
+        <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+        <xi:include href="system-only.xml" xpointer="singular"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 5c499e5d060866bcf6f213713f638def7424e214..8a8d9c9b2e248ccd0c30e05f5b2017cbb5a50dea 100644 (file)
@@ -1204,6 +1204,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("RootHashSignature", "ay", property_get_root_hash_sig, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RootHashSignaturePath", "s", NULL, offsetof(ExecContext, root_hash_sig_path), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RootVerity", "s", NULL, offsetof(ExecContext, root_verity), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("ExtensionDirectories", "as", NULL, offsetof(ExecContext, extension_directories), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("ExtensionImages", "a(sba(ss))", property_get_extension_images, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("MountImages", "a(ssba(ss))", property_get_mount_images, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@@ -3261,7 +3262,8 @@ int bus_exec_context_set_transient_property(
                 return 1;
 
         } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
-                              "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths", "ExecPaths", "NoExecPaths")) {
+                              "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths", "ExecPaths", "NoExecPaths",
+                              "ExtensionDirectories")) {
                 _cleanup_strv_free_ char **l = NULL;
                 char ***dirs;
                 char **p;
@@ -3291,6 +3293,8 @@ int bus_exec_context_set_transient_property(
                                 dirs = &c->exec_paths;
                         else if (streq(name, "NoExecPaths"))
                                 dirs = &c->no_exec_paths;
+                        else if (streq(name, "ExtensionDirectories"))
+                                dirs = &c->extension_directories;
                         else /* "InaccessiblePaths" */
                                 dirs = &c->inaccessible_paths;
 
index eb25c98925685ae2ad301770500942a0cc395ad5..78c8d966df03987d0e9e13d41192110db34b3a95 100644 (file)
@@ -2065,6 +2065,9 @@ bool exec_needs_mount_namespace(
         if (context->n_extension_images > 0)
                 return true;
 
+        if (!strv_isempty(context->extension_directories))
+                return true;
+
         if (!IN_SET(context->mount_flags, 0, MS_SHARED))
                 return true;
 
@@ -3566,6 +3569,7 @@ static int apply_mount_namespace(
                             context->root_verity,
                             context->extension_images,
                             context->n_extension_images,
+                            context->extension_directories,
                             propagate_dir,
                             incoming_dir,
                             root_dir || root_image ? params->notify_socket : NULL,
@@ -5244,6 +5248,7 @@ void exec_context_done(ExecContext *c) {
         c->root_hash_sig_path = mfree(c->root_hash_sig_path);
         c->root_verity = mfree(c->root_verity);
         c->extension_images = mount_image_free_many(c->extension_images, &c->n_extension_images);
+        c->extension_directories = strv_free(c->extension_directories);
         c->tty_path = mfree(c->tty_path);
         c->syslog_identifier = mfree(c->syslog_identifier);
         c->user = mfree(c->user);
@@ -6120,6 +6125,8 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                                 strempty(o->options));
                 fprintf(f, "\n");
         }
+
+        strv_dump(f, prefix, "ExtensionDirectories", c->extension_directories);
 }
 
 bool exec_context_maintains_privileges(const ExecContext *c) {
index 805e9b476537a2c30a35b09155048098fe390379..4aff50b4424bf05de1fa13a3e1f49e50d2145818 100644 (file)
@@ -273,6 +273,7 @@ struct ExecContext {
         size_t n_mount_images;
         MountImage *extension_images;
         size_t n_extension_images;
+        char **extension_directories;
 
         uint64_t capability_bounding_set;
         uint64_t capability_ambient_set;
index deea540e1047382d0235ac94bb1b86eda3d8c92e..cc04393c1e8b4c20d26b17438d1b23cfef086e0e 100644 (file)
@@ -9,6 +9,7 @@
 {{type}}.RootHash,                         config_parse_exec_root_hash,                 0,                                  offsetof({{type}}, exec_context)
 {{type}}.RootHashSignature,                config_parse_exec_root_hash_sig,             0,                                  offsetof({{type}}, exec_context)
 {{type}}.RootVerity,                       config_parse_unit_path_printf,               true,                               offsetof({{type}}, exec_context.root_verity)
+{{type}}.ExtensionDirectories,             config_parse_namespace_path_strv,            0,                                  offsetof({{type}}, exec_context.extension_directories)
 {{type}}.ExtensionImages,                  config_parse_extension_images,               0,                                  offsetof({{type}}, exec_context)
 {{type}}.MountImages,                      config_parse_mount_images,                   0,                                  offsetof({{type}}, exec_context)
 {{type}}.User,                             config_parse_user_group_compat,              0,                                  offsetof({{type}}, exec_context.user)
index ecbd23833c6d9308ed697fe3a45fb4eecec490d6..088cb09ac9c0e74f4c8d843f26317b1afec0bf2e 100644 (file)
@@ -63,6 +63,7 @@ typedef enum MountMode {
         EXEC,
         TMPFS,
         RUN,
+        EXTENSION_DIRECTORIES, /* Bind-mounted outside the root directory, and used by subsequent mounts */
         EXTENSION_IMAGES, /* Mounted outside the root directory, and used by subsequent mounts */
         MQUEUEFS,
         READWRITE_IMPLICIT, /* Should have the lowest priority. */
@@ -408,22 +409,23 @@ static int append_mount_images(MountEntry **p, const MountImage *mount_images, s
         return 0;
 }
 
-static int append_extension_images(
+static int append_extensions(
                 MountEntry **p,
                 const char *root,
                 const char *extension_dir,
                 char **hierarchies,
                 const MountImage *mount_images,
-                size_t n) {
+                size_t n,
+                char **extension_directories) {
 
         _cleanup_strv_free_ char **overlays = NULL;
-        char **hierarchy;
+        char **hierarchy, **extension_directory;
         int r;
 
         assert(p);
         assert(extension_dir);
 
-        if (n == 0)
+        if (n == 0 && strv_isempty(extension_directories))
                 return 0;
 
         /* Prepare a list of overlays, that will have as each element a string suitable for being
@@ -482,6 +484,62 @@ static int append_extension_images(
                 };
         }
 
+        /* Secondly, extend the lowerdir= parameters with each ExtensionDirectory.
+         * Bind mount them in the same location as the ExtensionImages, so that we
+         * can check that they are valid trees (extension-release.d). */
+        STRV_FOREACH(extension_directory, extension_directories) {
+                _cleanup_free_ char *mount_point = NULL, *source = NULL;
+                const char *e = *extension_directory;
+                bool ignore_enoent = false;
+
+                /* Pick up the counter where the ExtensionImages left it. */
+                r = asprintf(&mount_point, "%s/%zu", extension_dir, n++);
+                if (r < 0)
+                        return -ENOMEM;
+
+                /* Look for any prefixes */
+                if (startswith(e, "-")) {
+                        e++;
+                        ignore_enoent = true;
+                }
+                /* Ignore this for now */
+                if (startswith(e, "+"))
+                        e++;
+
+                source = strdup(e);
+                if (!source)
+                        return -ENOMEM;
+
+                for (size_t j = 0; hierarchies && hierarchies[j]; ++j) {
+                        _cleanup_free_ char *prefixed_hierarchy = NULL, *escaped = NULL, *lowerdir = NULL;
+
+                        prefixed_hierarchy = path_join(mount_point, hierarchies[j]);
+                        if (!prefixed_hierarchy)
+                                return -ENOMEM;
+
+                        escaped = shell_escape(prefixed_hierarchy, ",:");
+                        if (!escaped)
+                                return -ENOMEM;
+
+                        /* Note that lowerdir= parameters are in 'reverse' order, so the
+                         * top-most directory in the overlay comes first in the list. */
+                        lowerdir = strjoin(escaped, ":", overlays[j]);
+                        if (!lowerdir)
+                                return -ENOMEM;
+
+                        free_and_replace(overlays[j], lowerdir);
+                }
+
+                *((*p)++) = (MountEntry) {
+                        .path_malloc = TAKE_PTR(mount_point),
+                        .source_const = TAKE_PTR(source),
+                        .mode = EXTENSION_DIRECTORIES,
+                        .ignore = ignore_enoent,
+                        .has_prefix = true,
+                        .read_only = true,
+                };
+        }
+
         /* Then, for each hierarchy, prepare an overlay with the list of lowerdir= strings
          * set up earlier. */
         for (size_t i = 0; hierarchies && hierarchies[i]; ++i) {
@@ -605,9 +663,12 @@ static int append_protect_system(MountEntry **p, ProtectSystem protect_system, b
 static int mount_path_compare(const MountEntry *a, const MountEntry *b) {
         int d;
 
-        /* EXTENSION_IMAGES will be used by other mounts as a base, so sort them first
+        /* ExtensionImages/Directories will be used by other mounts as a base, so sort them first
          * regardless of the prefix - they are set up in the propagate directory anyway */
         d = -CMP(a->mode == EXTENSION_IMAGES, b->mode == EXTENSION_IMAGES);
+        if (d != 0)
+                return d;
+        d = -CMP(a->mode == EXTENSION_DIRECTORIES, b->mode == EXTENSION_DIRECTORIES);
         if (d != 0)
                 return d;
 
@@ -757,8 +818,8 @@ static void drop_outside_root(const char *root_directory, MountEntry *m, size_t
 
         for (f = m, t = m; f < m + *n; f++) {
 
-                /* ExtensionImages bases are opened in /run/systemd/unit-extensions on the host */
-                if (f->mode != EXTENSION_IMAGES && !path_startswith(mount_entry_path(f), root_directory)) {
+                /* ExtensionImages/Directories bases are opened in /run/systemd/unit-extensions on the host */
+                if (!IN_SET(f->mode, EXTENSION_IMAGES, EXTENSION_DIRECTORIES) && !path_startswith(mount_entry_path(f), root_directory)) {
                         log_debug("%s is outside of root directory.", mount_entry_path(f));
                         mount_entry_done(f);
                         continue;
@@ -1296,6 +1357,47 @@ static int apply_one_mount(
                 what = mount_entry_path(m);
                 break;
 
+        case EXTENSION_DIRECTORIES: {
+                _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL,
+                                *host_os_release_sysext_level = NULL, *extension_name = NULL;
+                _cleanup_strv_free_ char **extension_release = NULL;
+
+                r = path_extract_filename(mount_entry_source(m), &extension_name);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to extract extension name from %s: %m", mount_entry_source(m));
+
+                r = parse_os_release(
+                                empty_to_root(root_directory),
+                                "ID", &host_os_release_id,
+                                "VERSION_ID", &host_os_release_version_id,
+                                "SYSEXT_LEVEL", &host_os_release_sysext_level,
+                                NULL);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory));
+                if (isempty(host_os_release_id))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory));
+
+                r = load_extension_release_pairs(mount_entry_source(m), extension_name, &extension_release);
+                if (r == -ENOENT && m->ignore)
+                        return 0;
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to parse directory %s extension-release metadata: %m", extension_name);
+
+                r = extension_release_validate(
+                                extension_name,
+                                host_os_release_id,
+                                host_os_release_version_id,
+                                host_os_release_sysext_level,
+                                /* host_sysext_scope */ NULL, /* Leave empty, we need to accept both system and portable */
+                                extension_release);
+                if (r == 0)
+                        return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Directory %s extension-release metadata does not match the root's", extension_name);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to compare directory %s extension-release metadata with the root's os-release: %m", extension_name);
+
+                _fallthrough_;
+        }
+
         case BIND_MOUNT:
                 rbind = false;
 
@@ -1525,6 +1627,7 @@ static size_t namespace_calculate_mounts(
                 size_t n_temporary_filesystems,
                 size_t n_mount_images,
                 size_t n_extension_images,
+                size_t n_extension_directories,
                 size_t n_hierarchies,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
@@ -1559,7 +1662,8 @@ static size_t namespace_calculate_mounts(
                 strv_length(empty_directories) +
                 n_bind_mounts +
                 n_mount_images +
-                (n_extension_images > 0 ? n_hierarchies + n_extension_images : 0) + /* Mount each image plus an overlay per hierarchy */
+                (n_extension_images > 0 || n_extension_directories > 0 ? /* Mount each image and directory plus an overlay per hierarchy */
+                        n_hierarchies + n_extension_images + n_extension_directories: 0) +
                 n_temporary_filesystems +
                 ns_info->private_dev +
                 (ns_info->protect_kernel_tunables ?
@@ -1655,8 +1759,8 @@ static int apply_mounts(
                         if (m->applied)
                                 continue;
 
-                        /* ExtensionImages are first opened in the propagate directory, not in the root_directory */
-                        r = follow_symlink(m->mode != EXTENSION_IMAGES ? root : NULL, m);
+                        /* ExtensionImages/Directories are first opened in the propagate directory, not in the root_directory */
+                        r = follow_symlink(!IN_SET(m->mode, EXTENSION_IMAGES, EXTENSION_DIRECTORIES) ? root : NULL, m);
                         if (r < 0) {
                                 if (error_path && mount_entry_path(m))
                                         *error_path = strdup(mount_entry_path(m));
@@ -1879,6 +1983,7 @@ int setup_namespace(
                 const char *verity_data_path,
                 const MountImage *extension_images,
                 size_t n_extension_images,
+                char **extension_directories,
                 const char *propagate_dir,
                 const char *incoming_dir,
                 const char *notify_socket,
@@ -1992,7 +2097,7 @@ int setup_namespace(
                 require_prefix = true;
         }
 
-        if (n_extension_images > 0) {
+        if (n_extension_images > 0 || !strv_isempty(extension_directories)) {
                 r = parse_env_extension_hierarchies(&hierarchies);
                 if (r < 0)
                         return r;
@@ -2010,6 +2115,7 @@ int setup_namespace(
                         n_temporary_filesystems,
                         n_mount_images,
                         n_extension_images,
+                        strv_length(extension_directories),
                         strv_length(hierarchies),
                         tmp_dir, var_tmp_dir,
                         creds_path,
@@ -2078,7 +2184,7 @@ int setup_namespace(
                 if (r < 0)
                         goto finish;
 
-                r = append_extension_images(&m, root, extension_dir, hierarchies, extension_images, n_extension_images);
+                r = append_extensions(&m, root, extension_dir, hierarchies, extension_images, n_extension_images, extension_directories);
                 if (r < 0)
                         goto finish;
 
index 62f05d7585fb9b508b16fc170a6467c73843a2d6..ae84d2b03b101b71e8012e66ba607bdf7d9650ee 100644 (file)
@@ -142,6 +142,7 @@ int setup_namespace(
                 const char *root_verity,
                 const MountImage *extension_images,
                 size_t n_extension_images,
+                char **extension_directories,
                 const char *propagate_dir,
                 const char *incoming_dir,
                 const char *notify_socket,
index dcce530c999a5a0d1bd4b2d19b5cfafdb0866e1c..c35dd286e60b6e9b589fe963c200c270f46c6ec9 100644 (file)
@@ -973,6 +973,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                               "ExecPaths",
                               "NoExecPaths",
                               "ExecSearchPath",
+                              "ExtensionDirectories",
                               "ConfigurationDirectory",
                               "SupplementaryGroups",
                               "SystemCallArchitectures"))
index 8df5533d6e3684e8a17b465c6f22723f8f7a74cb..09c3091641fe13a45c4d23b9a919ef2e3a920da8 100644 (file)
@@ -207,6 +207,7 @@ TEST(protect_kernel_logs) {
                                     NULL,
                                     NULL,
                                     NULL,
+                                    NULL,
                                     NULL);
                 assert_se(r == 0);
 
index b03eabb59bbc60adb9beafd26e39ca0c1ec8e485..cd455a3a5b79a3de0dd298029e97bdd5c239198b 100644 (file)
@@ -108,6 +108,7 @@ int main(int argc, char *argv[]) {
                             NULL,
                             NULL,
                             NULL,
+                            NULL,
                             NULL);
         if (r < 0) {
                 log_error_errno(r, "Failed to set up namespace: %m");
index 78ddaf5ec8a54b5e3dd4f015453162e1d26fd33e..186557f8a5431ddd747d662c8818cf362c958e8d 100644 (file)
@@ -217,6 +217,7 @@ RootImage=
 RootHash=
 RootHashSignature=
 RootVerity=
+ExtensionDirectories=
 ExtensionImages=
 RuntimeMaxSec=
 SELinuxContextFromNet=
index 67421444cca0b3e080369c074adfb80f264c4759..0a44328e5c687869a2caac7abe57eda9153384cd 100644 (file)
@@ -40,6 +40,7 @@ DynamicUser=
 Environment=
 EnvironmentFile=
 ExecPaths=
+ExtensionDirectories=
 ExtensionImages=
 FinalKillSignal=
 ForceUnmount=
index ca9959538b899336c8d6809a2d2f980d83dd3038..6be65062d3ec22a179f1a735b9d93b5b10b8d2d4 100644 (file)
@@ -168,6 +168,7 @@ ExecStartPre=
 ExecStop=
 ExecStopPost=
 ExitType=
+ExtensionDirectories=
 ExtensionImages=
 FailureAction=
 FileDescriptorStoreMax=
index 865fd83adc50c0da88aff8714c2b4dd7e2756edf..90358fc11aa84e4642a5afaf1c5f04974d6358dd 100644 (file)
@@ -50,6 +50,7 @@ ExecStartPost=
 ExecStartPre=
 ExecStopPost=
 ExecStopPre=
+ExtensionDirectories=
 ExtensionImages=
 FileDescriptorName=
 FinalKillSignal=
index f538ba8b6095b4281df2f62268512e16fa246422..5d057fa63060c2c791d25e6b10fa378d60b46b10 100644 (file)
@@ -39,6 +39,7 @@ DynamicUser=
 Environment=
 EnvironmentFile=
 ExecPaths=
+ExtensionDirectories=
 ExtensionImages=
 FinalKillSignal=
 Group=
index b35527f76147298528099183e0aceaae2fa1da78..ff4f77def2d59415e4363f99b0738b388c9d86d5 100755 (executable)
@@ -321,6 +321,36 @@ EOF
 systemctl start testservice-50e.service
 systemctl is-active testservice-50e.service
 
+# ExtensionDirectories will set up an overlay
+mkdir -p "${image_dir}/app0" "${image_dir}/app1"
+systemd-run -P --property ExtensionDirectories="${image_dir}/nonexistant" --property RootImage="${image}.raw" cat /opt/script0.sh && { echo 'unexpected success'; exit 1; }
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh && { echo 'unexpected success'; exit 1; }
+systemd-dissect --mount /usr/share/app0.raw "${image_dir}/app0"
+systemd-dissect --mount /usr/share/app1.raw "${image_dir}/app1"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /opt/script1.sh | grep -q -F "extension-release.app2"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/other_file | grep -q -F "MARKER=1"
+cat >/run/systemd/system/testservice-50f.service <<EOF
+[Service]
+MountAPIVFS=yes
+TemporaryFileSystem=/run
+RootImage=${image}.raw
+ExtensionDirectories=${image_dir}/app0 ${image_dir}/app1
+# Relevant only for sanitizer runs
+UnsetEnvironment=LD_PRELOAD
+ExecStart=/bin/bash -c '/opt/script0.sh | grep ID'
+ExecStart=/bin/bash -c '/opt/script1.sh | grep ID'
+Type=oneshot
+RemainAfterExit=yes
+EOF
+systemctl start testservice-50f.service
+systemctl is-active testservice-50f.service
+umount "${image_dir}/app0"
+umount "${image_dir}/app1"
+
 echo OK >/testok
 
 exit 0