diff options
-rw-r--r-- | man/org.freedesktop.systemd1.xml | 17 | ||||
-rw-r--r-- | man/systemd.exec.xml | 28 | ||||
-rw-r--r-- | src/core/dbus-execute.c | 6 | ||||
-rw-r--r-- | src/core/execute.c | 7 | ||||
-rw-r--r-- | src/core/execute.h | 1 | ||||
-rw-r--r-- | src/core/load-fragment-gperf.gperf.in | 1 | ||||
-rw-r--r-- | src/core/namespace.c | 130 | ||||
-rw-r--r-- | src/core/namespace.h | 1 | ||||
-rw-r--r-- | src/shared/bus-unit-util.c | 1 | ||||
-rw-r--r-- | src/test/test-namespace.c | 1 | ||||
-rw-r--r-- | src/test/test-ns.c | 1 | ||||
-rw-r--r-- | test/fuzz/fuzz-unit-file/directives-all.service | 1 | ||||
-rw-r--r-- | test/fuzz/fuzz-unit-file/directives.mount | 1 | ||||
-rw-r--r-- | test/fuzz/fuzz-unit-file/directives.service | 1 | ||||
-rw-r--r-- | test/fuzz/fuzz-unit-file/directives.socket | 1 | ||||
-rw-r--r-- | test/fuzz/fuzz-unit-file/directives.swap | 1 | ||||
-rwxr-xr-x | test/units/testsuite-50.sh | 30 |
17 files changed, 216 insertions, 13 deletions
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index bd69a00b57..97a8c98b39 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -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"/> diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 079ff14aea..36a884c9f4 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -459,6 +459,34 @@ <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> diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 5c499e5d06..8a8d9c9b2e 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -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; diff --git a/src/core/execute.c b/src/core/execute.c index eb25c98925..78c8d966df 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -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) { diff --git a/src/core/execute.h b/src/core/execute.h index 805e9b4765..4aff50b442 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -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; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index deea540e10..cc04393c1e 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -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) diff --git a/src/core/namespace.c b/src/core/namespace.c index ecbd23833c..088cb09ac9 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -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,11 +663,14 @@ 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; /* If the paths are not equal, then order prefixes first */ d = path_compare(mount_entry_path(a), mount_entry_path(b)); @@ -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; diff --git a/src/core/namespace.h b/src/core/namespace.h index 62f05d7585..ae84d2b03b 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -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, diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index dcce530c99..c35dd286e6 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -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")) diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 8df5533d6e..09c3091641 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -207,6 +207,7 @@ TEST(protect_kernel_logs) { NULL, NULL, NULL, + NULL, NULL); assert_se(r == 0); diff --git a/src/test/test-ns.c b/src/test/test-ns.c index b03eabb59b..cd455a3a5b 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -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"); diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index 78ddaf5ec8..186557f8a5 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -217,6 +217,7 @@ RootImage= RootHash= RootHashSignature= RootVerity= +ExtensionDirectories= ExtensionImages= RuntimeMaxSec= SELinuxContextFromNet= diff --git a/test/fuzz/fuzz-unit-file/directives.mount b/test/fuzz/fuzz-unit-file/directives.mount index 67421444cc..0a44328e5c 100644 --- a/test/fuzz/fuzz-unit-file/directives.mount +++ b/test/fuzz/fuzz-unit-file/directives.mount @@ -40,6 +40,7 @@ DynamicUser= Environment= EnvironmentFile= ExecPaths= +ExtensionDirectories= ExtensionImages= FinalKillSignal= ForceUnmount= diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index ca9959538b..6be65062d3 100644 --- a/test/fuzz/fuzz-unit-file/directives.service +++ b/test/fuzz/fuzz-unit-file/directives.service @@ -168,6 +168,7 @@ ExecStartPre= ExecStop= ExecStopPost= ExitType= +ExtensionDirectories= ExtensionImages= FailureAction= FileDescriptorStoreMax= diff --git a/test/fuzz/fuzz-unit-file/directives.socket b/test/fuzz/fuzz-unit-file/directives.socket index 865fd83adc..90358fc11a 100644 --- a/test/fuzz/fuzz-unit-file/directives.socket +++ b/test/fuzz/fuzz-unit-file/directives.socket @@ -50,6 +50,7 @@ ExecStartPost= ExecStartPre= ExecStopPost= ExecStopPre= +ExtensionDirectories= ExtensionImages= FileDescriptorName= FinalKillSignal= diff --git a/test/fuzz/fuzz-unit-file/directives.swap b/test/fuzz/fuzz-unit-file/directives.swap index f538ba8b60..5d057fa630 100644 --- a/test/fuzz/fuzz-unit-file/directives.swap +++ b/test/fuzz/fuzz-unit-file/directives.swap @@ -39,6 +39,7 @@ DynamicUser= Environment= EnvironmentFile= ExecPaths= +ExtensionDirectories= ExtensionImages= FinalKillSignal= Group= diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index b35527f761..ff4f77def2 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -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 |