]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Add ExtensionImages directive to form overlays 18018/head
authorLuca Boccassi <luca.boccassi@microsoft.com>
Mon, 22 Feb 2021 12:20:33 +0000 (12:20 +0000)
committerLuca Boccassi <luca.boccassi@microsoft.com>
Tue, 23 Feb 2021 15:34:46 +0000 (15:34 +0000)
Add support for overlaying images for services on top of their
root fs, using a read-only overlay.

20 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.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/shared/dissect-image.c
src/shared/dissect-image.h
src/shared/mount-util.c
src/test/test-namespace.c
src/test/test-ns.c
test/TEST-50-DISSECT/test.sh
test/fuzz/fuzz-unit-file/directives.service
test/test-functions
test/units/testsuite-50.sh

index 8c370ba9a452aec4e8979a8891ad2923e37e2d99..21630478962c6ae35b1ebc9dd986a310d85210f8 100644 (file)
@@ -2565,6 +2565,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 a(sba(ss)) ExtensionImages = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(ssba(ss)) MountImages = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i OOMScoreAdjust = ...;
@@ -3070,24 +3072,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property WorkingDirectory is not documented!-->
 
-    <!--property RootDirectory is not documented!-->
-
-    <!--property RootImage is not documented!-->
-
-    <!--property RootImageOptions is not documented!-->
-
-    <!--property RootHash is not documented!-->
-
     <!--property RootHashPath is not documented!-->
 
-    <!--property RootHashSignature is not documented!-->
-
     <!--property RootHashSignaturePath is not documented!-->
 
-    <!--property RootVerity is not documented!-->
-
-    <!--property MountImages is not documented!-->
-
     <!--property OOMScoreAdjust is not documented!-->
 
     <!--property CoredumpFilter is not documented!-->
@@ -3656,6 +3644,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="ExtensionImages"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="MountImages"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="OOMScoreAdjust"/>
@@ -3978,6 +3968,17 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
       <para><varname>ControlGroup</varname> indicates the control group path the processes of this service
       unit are placed in.</para>
+
+      <para>The following properties map 1:1 to corresponding settings in the unit file:
+      <varname>RootDirectory</varname>
+      <varname>RootImage</varname>
+      <varname>RootImageOptions</varname>
+      <varname>RootVerity</varname>
+      <varname>RootHash</varname>
+      <varname>RootHashSignature</varname>
+      <varname>MountImages</varname>
+      <varname>ExtensionImages</varname>
+      see systemd.exec(5) for their meaning.</para>
     </refsect2>
   </refsect1>
 
@@ -4325,6 +4326,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 a(sba(ss)) ExtensionImages = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(ssba(ss)) MountImages = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i OOMScoreAdjust = ...;
@@ -4858,24 +4861,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <!--property WorkingDirectory is not documented!-->
 
-    <!--property RootDirectory is not documented!-->
-
-    <!--property RootImage is not documented!-->
-
-    <!--property RootImageOptions is not documented!-->
-
-    <!--property RootHash is not documented!-->
-
     <!--property RootHashPath is not documented!-->
 
-    <!--property RootHashSignature is not documented!-->
-
     <!--property RootHashSignaturePath is not documented!-->
 
-    <!--property RootVerity is not documented!-->
-
-    <!--property MountImages is not documented!-->
-
     <!--property OOMScoreAdjust is not documented!-->
 
     <!--property CoredumpFilter is not documented!-->
@@ -5442,6 +5431,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="ExtensionImages"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="MountImages"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="OOMScoreAdjust"/>
@@ -6024,6 +6015,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s RootVerity = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly a(sba(ss)) ExtensionImages = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(ssba(ss)) MountImages = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i OOMScoreAdjust = ...;
@@ -6485,24 +6478,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <!--property WorkingDirectory is not documented!-->
 
-    <!--property RootDirectory is not documented!-->
-
-    <!--property RootImage is not documented!-->
-
-    <!--property RootImageOptions is not documented!-->
-
-    <!--property RootHash is not documented!-->
-
     <!--property RootHashPath is not documented!-->
 
-    <!--property RootHashSignature is not documented!-->
-
     <!--property RootHashSignaturePath is not documented!-->
 
-    <!--property RootVerity is not documented!-->
-
-    <!--property MountImages is not documented!-->
-
     <!--property OOMScoreAdjust is not documented!-->
 
     <!--property CoredumpFilter is not documented!-->
@@ -6987,6 +6966,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="ExtensionImages"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="MountImages"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="OOMScoreAdjust"/>
@@ -7690,6 +7671,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 a(sba(ss)) ExtensionImages = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly a(ssba(ss)) MountImages = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i OOMScoreAdjust = ...;
@@ -8137,24 +8120,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <!--property WorkingDirectory is not documented!-->
 
-    <!--property RootDirectory is not documented!-->
-
-    <!--property RootImage is not documented!-->
-
-    <!--property RootImageOptions is not documented!-->
-
-    <!--property RootHash is not documented!-->
-
     <!--property RootHashPath is not documented!-->
 
-    <!--property RootHashSignature is not documented!-->
-
     <!--property RootHashSignaturePath is not documented!-->
 
-    <!--property RootVerity is not documented!-->
-
-    <!--property MountImages is not documented!-->
-
     <!--property OOMScoreAdjust is not documented!-->
 
     <!--property CoredumpFilter is not documented!-->
@@ -8625,6 +8594,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="ExtensionImages"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="MountImages"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="OOMScoreAdjust"/>
index 1ebce6188e46b9abb5fa06cf0844f74191af6bb8..bac8f9cdff942a7efe28c278cff6a141a1c8fa8b 100644 (file)
 
         <xi:include href="system-only.xml" xpointer="singular"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>ExtensionImages=</varname></term>
+
+        <listitem><para>This setting is similar to <varname>MountImages=</varname> in that it mounts a file
+        system hierarchy from a block device node or loopback file, but instead of providing a destination path,
+        an overlay will be set up. This option expects a whitespace separated list of mount definitions. Each
+        definition consists of a source path, optionally followed by a colon and a list of mount options.</para>
+
+        <para>A read-only OverlayFS will be set up on top of <filename>/usr/</filename> and
+        <filename>/opt/</filename> hierarchies from the root. The order in which the images are listed
+        will determine the order in which the overlay is laid down: images specified first to last will result
+        in overlayfs layers bottom to top.</para>
+
+        <para>Mount options may be defined as a single comma-separated list of options, in which case they
+        will be implicitly applied to the root partition on the image, or a series of colon-separated tuples
+        of partition name and mount options. Valid partition names and mount options are the same as for
+        <varname>RootImageOptions=</varname> setting described above.</para>
+
+        <para>Each mount definition may be prefixed with <literal>-</literal>, in which case it will be
+        ignored when its source path does not exist. The source argument is a path to a block device node or
+        regular file. If the source path contains a <literal>:</literal>, it needs to be escaped as
+        <literal>\:</literal>. The device node or file system image file needs to follow the same rules as
+        specified for <varname>RootImage=</varname>. 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 image
+        paths. If the empty string is assigned, the entire list of mount paths defined prior to this is
+        reset.</para>
+
+        <para>When <varname>DevicePolicy=</varname> is set to <literal>closed</literal> or
+        <literal>strict</literal>, or set to <literal>auto</literal> and <varname>DeviceAllow=</varname> is
+        set, then this setting adds <filename>/dev/loop-control</filename> with <constant>rw</constant> mode,
+        <literal>block-loop</literal> and <literal>block-blkext</literal> with <constant>rwm</constant> mode
+        to <varname>DeviceAllow=</varname>. See
+        <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for the details about <varname>DevicePolicy=</varname> or <varname>DeviceAllow=</varname>. Also, see
+        <varname>PrivateDevices=</varname> below, as it may change the setting of
+        <varname>DevicePolicy=</varname>.</para>
+
+        <xi:include href="system-only.xml" xpointer="singular"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 1f0e27a14176fa5e0f7c0adb662638ae6c00c9d6..a4817ca6de7baa92dbda9782abe3a662d44f96b3 100644 (file)
@@ -996,6 +996,60 @@ static int property_get_mount_images(
         return sd_bus_message_close_container(reply);
 }
 
+static int property_get_extension_images(
+                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;
+        int r;
+
+        assert(bus);
+        assert(c);
+        assert(property);
+        assert(reply);
+
+        r = sd_bus_message_open_container(reply, 'a', "(sba(ss))");
+        if (r < 0)
+                return r;
+
+        for (size_t i = 0; i < c->n_extension_images; i++) {
+                MountOptions *m;
+
+                r = sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "sba(ss)");
+                if (r < 0)
+                        return r;
+                r = sd_bus_message_append(
+                                reply, "sb",
+                                c->extension_images[i].source,
+                                c->extension_images[i].ignore_enoent);
+                if (r < 0)
+                        return r;
+                r = sd_bus_message_open_container(reply, 'a', "(ss)");
+                if (r < 0)
+                        return r;
+                LIST_FOREACH(mount_options, m, c->extension_images[i].mount_options) {
+                        r = sd_bus_message_append(reply, "(ss)",
+                                                  partition_designator_to_string(m->partition_designator),
+                                                  m->options);
+                        if (r < 0)
+                                return r;
+                }
+                r = sd_bus_message_close_container(reply);
+                if (r < 0)
+                        return r;
+                r = sd_bus_message_close_container(reply);
+                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),
@@ -1044,6 +1098,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("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),
         SD_BUS_PROPERTY("CoredumpFilter", "t", property_get_coredump_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@@ -3356,6 +3411,7 @@ int bus_exec_context_set_transient_property(
                                                     .destination = destination,
                                                     .mount_options = options,
                                                     .ignore_enoent = permissive,
+                                                    .type = MOUNT_IMAGE_DISCRETE,
                                             });
                         if (r < 0)
                                 return r;
@@ -3389,6 +3445,95 @@ int bus_exec_context_set_transient_property(
 
                 mount_images = mount_image_free_many(mount_images, &n_mount_images);
 
+                return 1;
+        } else if (streq(name, "ExtensionImages")) {
+                _cleanup_free_ char *format_str = NULL;
+                MountImage *extension_images = NULL;
+                size_t n_extension_images = 0;
+
+                r = sd_bus_message_enter_container(message, 'a', "(sba(ss))");
+                if (r < 0)
+                        return r;
+
+                for (;;) {
+                        _cleanup_(mount_options_free_allp) MountOptions *options = NULL;
+                        _cleanup_free_ char *source_escaped = NULL;
+                        char *source, *tuple;
+                        int permissive;
+
+                        r = sd_bus_message_enter_container(message, 'r', "sba(ss)");
+                        if (r < 0)
+                                return r;
+
+                        r = sd_bus_message_read(message, "sb", &source, &permissive);
+                        if (r <= 0)
+                                break;
+
+                        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_normalized(source))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not normalized.", source);
+
+                        /* Need to store them in the unit with the escapes, so that they can be parsed again */
+                        source_escaped = shell_escape(source, ":");
+                        if (!source_escaped)
+                                return -ENOMEM;
+
+                        tuple = strjoin(format_str,
+                                        format_str ? " " : "",
+                                        permissive ? "-" : "",
+                                        source_escaped);
+                        if (!tuple)
+                                return -ENOMEM;
+                        free_and_replace(format_str, tuple);
+
+                        r = bus_read_mount_options(message, error, &options, &format_str, ":");
+                        if (r < 0)
+                                return r;
+
+                        r = sd_bus_message_exit_container(message);
+                        if (r < 0)
+                                return r;
+
+                        r = mount_image_add(&extension_images, &n_extension_images,
+                                            &(MountImage) {
+                                                    .source = source,
+                                                    .mount_options = options,
+                                                    .ignore_enoent = permissive,
+                                                    .type = MOUNT_IMAGE_EXTENSION,
+                                            });
+                        if (r < 0)
+                                return r;
+                }
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_exit_container(message);
+                if (r < 0)
+                        return r;
+
+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+                        if (n_extension_images == 0) {
+                                c->extension_images = mount_image_free_many(c->extension_images, &c->n_extension_images);
+
+                                unit_write_settingf(u, flags, name, "%s=", name);
+                        } else {
+                                for (size_t i = 0; i < n_extension_images; ++i) {
+                                        r = mount_image_add(&c->extension_images, &c->n_extension_images, &extension_images[i]);
+                                        if (r < 0)
+                                                return r;
+                                }
+
+                                unit_write_settingf(u, flags|UNIT_ESCAPE_C|UNIT_ESCAPE_SPECIFIERS,
+                                                    name,
+                                                    "%s=%s",
+                                                    name,
+                                                    format_str);
+                        }
+                }
+
+                extension_images = mount_image_free_many(extension_images, &n_extension_images);
+
                 return 1;
         }
 
index d27adbbba54277282fd755609ae0c6e354baa023..60d107477b1f467612c161dbc6501f3500086194 100644 (file)
@@ -2018,6 +2018,9 @@ bool exec_needs_mount_namespace(
         if (context->n_mount_images > 0)
                 return true;
 
+        if (context->n_extension_images > 0)
+                return true;
+
         if (!IN_SET(context->mount_flags, 0, MS_SHARED))
                 return true;
 
@@ -3230,6 +3233,8 @@ static int apply_mount_namespace(
                             context->root_hash, context->root_hash_size, context->root_hash_path,
                             context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
                             context->root_verity,
+                            context->extension_images,
+                            context->n_extension_images,
                             propagate_dir,
                             incoming_dir,
                             root_dir || root_image ? params->notify_socket : NULL,
@@ -4816,6 +4821,7 @@ void exec_context_done(ExecContext *c) {
         c->root_hash_sig_size = 0;
         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->tty_path = mfree(c->tty_path);
         c->syslog_identifier = mfree(c->syslog_identifier);
         c->user = mfree(c->user);
@@ -5658,6 +5664,19 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                                 strempty(o->options));
                 fprintf(f, "\n");
         }
+
+        for (size_t i = 0; i < c->n_extension_images; i++) {
+                MountOptions *o;
+
+                fprintf(f, "%sExtensionImages: %s%s", prefix,
+                        c->extension_images[i].ignore_enoent ? "-": "",
+                        c->extension_images[i].source);
+                LIST_FOREACH(mount_options, o, c->extension_images[i].mount_options)
+                        fprintf(f, ":%s:%s",
+                                partition_designator_to_string(o->partition_designator),
+                                strempty(o->options));
+                fprintf(f, "\n");
+        }
 }
 
 bool exec_context_maintains_privileges(const ExecContext *c) {
index cf0c8b868b1dc57e14752028282d9307a583ea0b..20e1799b46a2f126a9feaac6dc34205ae78be20a 100644 (file)
@@ -251,6 +251,8 @@ struct ExecContext {
         size_t n_temporary_filesystems;
         MountImage *mount_images;
         size_t n_mount_images;
+        MountImage *extension_images;
+        size_t n_extension_images;
 
         uint64_t capability_bounding_set;
         uint64_t capability_ambient_set;
index 6ed6b07db215281abba93a9dc81b30d44b2c259d..6a11ef0d9d76923e8d0afcbe3d60e72e96e88ac3 100644 (file)
@@ -28,6 +28,7 @@ $1.RootImageOptions,                     config_parse_root_image_options,
 $1.RootHash,                             config_parse_exec_root_hash,                 0,                                  offsetof($1, exec_context)
 $1.RootHashSignature,                    config_parse_exec_root_hash_sig,             0,                                  offsetof($1, exec_context)
 $1.RootVerity,                           config_parse_unit_path_printf,               true,                               offsetof($1, exec_context.root_verity)
+$1.ExtensionImages,                      config_parse_extension_images,               0,                                  offsetof($1, exec_context)
 $1.MountImages,                          config_parse_mount_images,                   0,                                  offsetof($1, exec_context)
 $1.User,                                 config_parse_user_group_compat,              0,                                  offsetof($1, exec_context.user)
 $1.Group,                                config_parse_user_group_compat,              0,                                  offsetof($1, exec_context.group)
index 3e7081bf60941f2b7f7dc2db75e97c25678c5b03..c27814ad387f07e7797e575056efe9dac818eae3 100644 (file)
@@ -5117,6 +5117,148 @@ int config_parse_mount_images(
                                             .destination = dresolved,
                                             .mount_options = options,
                                             .ignore_enoent = permissive,
+                                            .type = MOUNT_IMAGE_DISCRETE,
+                                    });
+                if (r < 0)
+                        return log_oom();
+        }
+}
+
+int config_parse_extension_images(
+                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 Unit *u = userdata;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (isempty(rvalue)) {
+                /* Empty assignment resets the list */
+                c->extension_images = mount_image_free_many(c->extension_images, &c->n_extension_images);
+                return 0;
+        }
+
+        for (const char *p = rvalue;;) {
+                _cleanup_free_ char *source = NULL, *tuple = NULL, *sresolved = NULL;
+                _cleanup_(mount_options_free_allp) MountOptions *options = NULL;
+                bool permissive = false;
+                const char *q = NULL;
+                char *s = NULL;
+
+                r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                                   "Invalid syntax %s=%s, ignoring: %m", lvalue, rvalue);
+                        return 0;
+                }
+                if (r == 0)
+                        return 0;
+
+                q = tuple;
+                r = extract_first_word(&q, &source, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS);
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                                   "Invalid syntax in %s=, ignoring: %s", lvalue, tuple);
+                        return 0;
+                }
+                if (r == 0)
+                        continue;
+
+                s = source;
+                if (s[0] == '-') {
+                        permissive = true;
+                        s++;
+                }
+
+                r = unit_full_printf(u, s, &sresolved);
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                                   "Failed to resolve unit specifiers in \"%s\", ignoring: %m", s);
+                        continue;
+                }
+
+                r = path_simplify_and_warn(sresolved, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
+                if (r < 0)
+                        continue;
+
+                for (;;) {
+                        _cleanup_free_ char *partition = NULL, *mount_options = NULL, *mount_options_resolved = NULL;
+                        MountOptions *o = NULL;
+                        PartitionDesignator partition_designator;
+
+                        r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL);
+                        if (r == -ENOMEM)
+                                return log_oom();
+                        if (r < 0) {
+                                log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", q);
+                                return 0;
+                        }
+                        if (r == 0)
+                                break;
+                        /* Single set of options, applying to the root partition/single filesystem */
+                        if (r == 1) {
+                                r = unit_full_printf(u, partition, &mount_options_resolved);
+                                if (r < 0) {
+                                        log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", partition);
+                                        continue;
+                                }
+
+                                o = new(MountOptions, 1);
+                                if (!o)
+                                        return log_oom();
+                                *o = (MountOptions) {
+                                        .partition_designator = PARTITION_ROOT,
+                                        .options = TAKE_PTR(mount_options_resolved),
+                                };
+                                LIST_APPEND(mount_options, options, o);
+
+                                break;
+                        }
+
+                        partition_designator = partition_designator_from_string(partition);
+                        if (partition_designator < 0) {
+                                log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid partition name %s, ignoring", partition);
+                                continue;
+                        }
+                        r = unit_full_printf(u, mount_options, &mount_options_resolved);
+                        if (r < 0) {
+                                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", mount_options);
+                                continue;
+                        }
+
+                        o = new(MountOptions, 1);
+                        if (!o)
+                                return log_oom();
+                        *o = (MountOptions) {
+                                .partition_designator = partition_designator,
+                                .options = TAKE_PTR(mount_options_resolved),
+                        };
+                        LIST_APPEND(mount_options, options, o);
+                }
+
+                r = mount_image_add(&c->extension_images, &c->n_extension_images,
+                                    &(MountImage) {
+                                            .source = sresolved,
+                                            .mount_options = options,
+                                            .ignore_enoent = permissive,
+                                            .type = MOUNT_IMAGE_EXTENSION,
                                     });
                 if (r < 0)
                         return log_oom();
index e4a5cb7986931f37689af612ea780d16e2474e0c..b8a6d5feadc57fed460cfea1bf516c8e6c1af75e 100644 (file)
@@ -138,6 +138,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_timeout_abort);
 CONFIG_PARSER_PROTOTYPE(config_parse_swap_priority);
 CONFIG_PARSER_PROTOTYPE(config_parse_mount_images);
 CONFIG_PARSER_PROTOTYPE(config_parse_socket_timestamping);
+CONFIG_PARSER_PROTOTYPE(config_parse_extension_images);
 
 /* gperf prototypes */
 const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
index 151fc9139763f7fc336ddc59485e94b987f69937..ed07db5c731d9ab3851007d00dfeeac41ec786ab 100644 (file)
@@ -11,6 +11,9 @@
 #include "alloc-util.h"
 #include "base-filesystem.h"
 #include "dev-setup.h"
+#include "env-util.h"
+#include "escape.h"
+#include "extension-release.h"
 #include "fd-util.h"
 #include "format-util.h"
 #include "fs-util.h"
@@ -24,6 +27,7 @@
 #include "namespace-util.h"
 #include "namespace.h"
 #include "nulstr-util.h"
+#include "os-util.h"
 #include "path-util.h"
 #include "selinux-util.h"
 #include "socket-util.h"
@@ -41,6 +45,7 @@
 typedef enum MountMode {
         /* This is ordered by priority! */
         INACCESSIBLE,
+        OVERLAY_MOUNT,
         MOUNT_IMAGES,
         BIND_MOUNT,
         BIND_MOUNT_RECURSIVE,
@@ -57,6 +62,7 @@ typedef enum MountMode {
         NOEXEC,
         EXEC,
         TMPFS,
+        EXTENSION_IMAGES, /* Mounted outside the root directory, and used by subsequent mounts */
         READWRITE_IMPLICIT, /* Should have the lowest priority. */
         _MOUNT_MODE_MAX,
 } MountMode;
@@ -205,6 +211,7 @@ static const MountEntry protect_system_strict_table[] = {
 
 static const char * const mount_mode_table[_MOUNT_MODE_MAX] = {
         [INACCESSIBLE]         = "inaccessible",
+        [OVERLAY_MOUNT]        = "overlay",
         [BIND_MOUNT]           = "bind",
         [BIND_MOUNT_RECURSIVE] = "rbind",
         [PRIVATE_TMP]          = "private-tmp",
@@ -392,6 +399,101 @@ static int append_mount_images(MountEntry **p, const MountImage *mount_images, s
         return 0;
 }
 
+static int append_extension_images(
+                MountEntry **p,
+                const char *root,
+                const char *extension_dir,
+                char **hierarchies,
+                const MountImage *mount_images,
+                size_t n) {
+
+        _cleanup_strv_free_ char **overlays = NULL;
+        char **hierarchy;
+        int r;
+
+        assert(p);
+        assert(extension_dir);
+
+        if (n == 0)
+                return 0;
+
+        /* Prepare a list of overlays, that will have as each element a string suitable for being
+         * passed as a lowerdir= parameter, so start with the hierachy on the root.
+         * The overlays vector will have the same number of elements and will correspond to the
+         * hierarchies vector, so they can be iterated upon together. */
+        STRV_FOREACH(hierarchy, hierarchies) {
+                _cleanup_free_ char *prefixed_hierarchy = NULL;
+
+                prefixed_hierarchy = path_join(root, *hierarchy);
+                if (!prefixed_hierarchy)
+                        return -ENOMEM;
+
+                r = strv_consume(&overlays, TAKE_PTR(prefixed_hierarchy));
+                if (r < 0)
+                        return r;
+        }
+
+        /* First, prepare a mount for each image, but these won't be visible to the unit, instead
+         * they will be mounted in our propagate directory, and used as a source for the overlay. */
+        for (size_t i = 0; i < n; i++) {
+                _cleanup_free_ char *mount_point = NULL;
+                const MountImage *m = mount_images + i;
+
+                r = asprintf(&mount_point, "%s/%zu", extension_dir, i);
+                if (r < 0)
+                        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),
+                        .image_options = m->mount_options,
+                        .ignore = m->ignore_enoent,
+                        .source_const = m->source,
+                        .mode = EXTENSION_IMAGES,
+                        .has_prefix = 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) {
+                _cleanup_free_ char *prefixed_hierarchy = NULL;
+
+                prefixed_hierarchy = path_join(root, hierarchies[i]);
+                if (!prefixed_hierarchy)
+                        return -ENOMEM;
+
+                *((*p)++) = (MountEntry) {
+                        .path_malloc = TAKE_PTR(prefixed_hierarchy),
+                        .options_malloc = TAKE_PTR(overlays[i]),
+                        .mode = OVERLAY_MOUNT,
+                        .has_prefix = true,
+                        .ignore = true, /* If the source image doesn't set the ignore bit it will fail earlier. */
+                };
+        }
+
+        return 0;
+}
+
 static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs, size_t n) {
         assert(p);
 
@@ -494,6 +596,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
+         * 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;
+
         /* If the paths are not equal, then order prefixes first */
         d = path_compare(mount_entry_path(a), mount_entry_path(b));
         if (d != 0)
@@ -640,7 +748,8 @@ static void drop_outside_root(const char *root_directory, MountEntry *m, size_t
 
         for (f = m, t = m; f < m + *n; f++) {
 
-                if (!path_startswith(mount_entry_path(f), root_directory)) {
+                /* 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)) {
                         log_debug("%s is outside of root directory.", mount_entry_path(f));
                         mount_entry_done(f);
                         continue;
@@ -1003,12 +1112,28 @@ static int mount_run(const MountEntry *m) {
         return mount_tmpfs(m);
 }
 
-static int mount_image(const MountEntry *m) {
+static int mount_image(const MountEntry *m, const char *root_directory) {
+
+        _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL,
+                            *host_os_release_sysext_level = NULL;
         int r;
 
         assert(m);
 
-        r = verity_dissect_and_mount(mount_entry_source(m), mount_entry_path(m), m->image_options);
+        if (m->mode == EXTENSION_IMAGES) {
+                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));
+        }
+
+        r = verity_dissect_and_mount(
+                                mount_entry_source(m), mount_entry_path(m), m->image_options,
+                                host_os_release_id, host_os_release_version_id, host_os_release_sysext_level);
         if (r == -ENOENT && m->ignore)
                 return 0;
         if (r < 0)
@@ -1017,6 +1142,25 @@ static int mount_image(const MountEntry *m) {
         return 1;
 }
 
+static int mount_overlay(const MountEntry *m) {
+        const char *options;
+        int r;
+
+        assert(m);
+
+        options = strjoina("lowerdir=", mount_entry_options(m));
+
+        (void) mkdir_p_label(mount_entry_path(m), 0755);
+
+        r = mount_nofollow_verbose(LOG_DEBUG, "overlay", mount_entry_path(m), "overlay", MS_RDONLY, options);
+        if (r == -ENOENT && m->ignore)
+                return 0;
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
 static int follow_symlink(
                 const char *root_directory,
                 MountEntry *m) {
@@ -1173,7 +1317,13 @@ static int apply_one_mount(
                 return mount_run(m);
 
         case MOUNT_IMAGES:
-                return mount_image(m);
+                return mount_image(m, NULL);
+
+        case EXTENSION_IMAGES:
+                return mount_image(m, root_directory);
+
+        case OVERLAY_MOUNT:
+                return mount_overlay(m);
 
         default:
                 assert_not_reached("Unknown mode");
@@ -1317,6 +1467,8 @@ static size_t namespace_calculate_mounts(
                 size_t n_bind_mounts,
                 size_t n_temporary_filesystems,
                 size_t n_mount_images,
+                size_t n_extension_images,
+                size_t n_hierarchies,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
                 const char *creds_path,
@@ -1350,6 +1502,7 @@ 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_temporary_filesystems +
                 ns_info->private_dev +
                 (ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
@@ -1415,7 +1568,8 @@ static int apply_mounts(
                         if (m->applied)
                                 continue;
 
-                        r = follow_symlink(root, m);
+                        /* ExtensionImages are first opened in the propagate directory, not in the root_directory */
+                        r = follow_symlink(m->mode != EXTENSION_IMAGES ? root : NULL, m);
                         if (r < 0) {
                                 if (error_path && mount_entry_path(m))
                                         *error_path = strdup(mount_entry_path(m));
@@ -1618,6 +1772,8 @@ int setup_namespace(
                 size_t root_hash_sig_size,
                 const char *root_hash_sig_path,
                 const char *verity_data_path,
+                const MountImage *extension_images,
+                size_t n_extension_images,
                 const char *propagate_dir,
                 const char *incoming_dir,
                 const char *notify_socket,
@@ -1628,9 +1784,10 @@ int setup_namespace(
         _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
         _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
         _cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
+        _cleanup_strv_free_ char **hierarchies = NULL;
         MountEntry *m = NULL, *mounts = NULL;
         bool require_prefix = false, setup_propagate = false;
-        const char *root;
+        const char *root, *extension_dir = "/run/systemd/unit-extensions";
         size_t n_mounts;
         int r;
 
@@ -1711,6 +1868,12 @@ int setup_namespace(
                 require_prefix = true;
         }
 
+        if (n_extension_images > 0) {
+                r = parse_env_extension_hierarchies(&hierarchies);
+                if (r < 0)
+                        return r;
+        }
+
         n_mounts = namespace_calculate_mounts(
                         ns_info,
                         read_write_paths,
@@ -1722,6 +1885,8 @@ int setup_namespace(
                         n_bind_mounts,
                         n_temporary_filesystems,
                         n_mount_images,
+                        n_extension_images,
+                        strv_length(hierarchies),
                         tmp_dir, var_tmp_dir,
                         creds_path,
                         log_namespace,
@@ -1789,6 +1954,10 @@ int setup_namespace(
                 if (r < 0)
                         goto finish;
 
+                r = append_extension_images(&m, root, extension_dir, hierarchies, extension_images, n_extension_images);
+                if (r < 0)
+                        goto finish;
+
                 if (ns_info->private_dev)
                         *(m++) = (MountEntry) {
                                 .path_const = "/dev",
@@ -1948,6 +2117,12 @@ int setup_namespace(
         if (setup_propagate)
                 (void) mkdir_p(propagate_dir, 0600);
 
+        if (n_extension_images > 0) {
+                /* ExtensionImages mountpoint directories will be created
+                 * while parsing the mounts to create, so have the parent ready */
+                (void) mkdir_p(extension_dir, 0600);
+        }
+
         /* Remount / as SLAVE so that nothing now mounted in the namespace
          * shows up in the parent */
         if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
@@ -2114,9 +2289,11 @@ int mount_image_add(MountImage **m, size_t *n, const MountImage *item) {
         if (!s)
                 return -ENOMEM;
 
-        d = strdup(item->destination);
-        if (!d)
-                return -ENOMEM;
+        if (item->destination) {
+                d = strdup(item->destination);
+                if (!d)
+                        return -ENOMEM;
+        }
 
         LIST_FOREACH(mount_options, i, item->mount_options) {
                 _cleanup_(mount_options_free_allp) MountOptions *o;
@@ -2146,6 +2323,7 @@ int mount_image_add(MountImage **m, size_t *n, const MountImage *item) {
                 .destination = TAKE_PTR(d),
                 .mount_options = TAKE_PTR(options),
                 .ignore_enoent = item->ignore_enoent,
+                .type = item->type,
         };
 
         return 0;
index 54d4985f80f278a8d827ec8dc21d03247f1790d0..cb9d5a5d388a193b66547128c71800dbdc24f723 100644 (file)
@@ -93,11 +93,19 @@ struct TemporaryFileSystem {
         char *options;
 };
 
+typedef enum MountImageType {
+        MOUNT_IMAGE_DISCRETE,
+        MOUNT_IMAGE_EXTENSION,
+        _MOUNT_IMAGE_TYPE_MAX,
+        _MOUNT_IMAGE_TYPE_INVALID = -EINVAL,
+} MountImageType;
+
 struct MountImage {
         char *source;
-        char *destination;
+        char *destination; /* Unused if MountImageType == MOUNT_IMAGE_EXTENSION */
         LIST_HEAD(MountOptions, mount_options);
         bool ignore_enoent;
+        MountImageType type;
 };
 
 int setup_namespace(
@@ -129,6 +137,8 @@ int setup_namespace(
                 size_t root_hash_sig_size,
                 const char *root_hash_sig_path,
                 const char *root_verity,
+                const MountImage *extension_images,
+                size_t n_extension_images,
                 const char *propagate_dir,
                 const char *incoming_dir,
                 const char *notify_socket,
index 83130db2fa16141b3b8e08d70ffc0c3110386211..eaec48fd5e2ace6412af2391a15c97198c1a71fc 100644 (file)
@@ -1766,6 +1766,110 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                 return 1;
         }
 
+        if (streq(field, "ExtensionImages")) {
+                const char *p = eq;
+
+                r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'v', "a(sba(ss))");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'a', "(sba(ss))");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                for (;;) {
+                        _cleanup_free_ char *source = NULL, *tuple = NULL;
+                        const char *q = NULL, *s = NULL;
+                        bool permissive = false;
+
+                        r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                break;
+
+                        q = tuple;
+                        r = extract_first_word(&q, &source, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                continue;
+
+                        s = source;
+                        if (s[0] == '-') {
+                                permissive = true;
+                                s++;
+                        }
+
+                        r = sd_bus_message_open_container(m, 'r', "sba(ss)");
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_append(m, "sb", s, permissive);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_open_container(m, 'a', "(ss)");
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        for (;;) {
+                                _cleanup_free_ char *partition = NULL, *mount_options = NULL;
+
+                                r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL);
+                                if (r < 0)
+                                        return r;
+                                if (r == 0)
+                                        break;
+                                /* Single set of options, applying to the root partition/single filesystem */
+                                if (r == 1) {
+                                        r = sd_bus_message_append(m, "(ss)", "root", partition);
+                                        if (r < 0)
+                                                return bus_log_create_error(r);
+
+                                        break;
+                                }
+
+                                if (partition_designator_from_string(partition) < 0)
+                                        return bus_log_create_error(-EINVAL);
+
+                                r = sd_bus_message_append(m, "(ss)", partition, mount_options);
+                                if (r < 0)
+                                        return bus_log_create_error(r);
+                        }
+
+                        r = sd_bus_message_close_container(m);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_close_container(m);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+                }
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                return 1;
+        }
+
         return 0;
 }
 
index 791d747136e68e65c25023c3039e131c46497043..aa4c01bf6c7d2053bd6e37f7b985b9e037bc1156 100644 (file)
@@ -27,6 +27,7 @@
 #include "dissect-image.h"
 #include "dm-util.h"
 #include "env-file.h"
+#include "extension-release.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
@@ -2621,7 +2622,14 @@ static const char *const partition_designator_table[] = {
         [PARTITION_VAR] = "var",
 };
 
-int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options) {
+int verity_dissect_and_mount(
+                const char *src,
+                const char *dest,
+                const MountOptions *options,
+                const char *required_host_os_release_id,
+                const char *required_host_os_release_version_id,
+                const char *required_host_os_release_sysext_level) {
+
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
         _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
@@ -2683,6 +2691,30 @@ int verity_dissect_and_mount(const char *src, const char *dest, const MountOptio
         if (r < 0)
                 return log_debug_errno(r, "Failed to mount image: %m");
 
+        /* If we got os-release values from the caller, then we need to match them with the image's
+         * extension-release.d/ content. Return -EINVAL if there's any mismatch.
+         * First, check the distro ID. If that matches, then check the new SYSEXT_LEVEL value if
+         * available, or else fallback to VERSION_ID. */
+        if (required_host_os_release_id &&
+            (required_host_os_release_version_id || required_host_os_release_sysext_level)) {
+                _cleanup_strv_free_ char **extension_release = NULL;
+
+                r = load_extension_release_pairs(dest, dissected_image->image_name, &extension_release);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name);
+
+                r = extension_release_validate(
+                        dissected_image->image_name,
+                        required_host_os_release_id,
+                        required_host_os_release_version_id,
+                        required_host_os_release_sysext_level,
+                        extension_release);
+                if (r == 0)
+                        return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", dissected_image->image_name);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", dissected_image->image_name);
+        }
+
         if (decrypted_image) {
                 r = decrypted_image_relinquish(decrypted_image);
                 if (r < 0)
index 89078500007aaf38b73e7357ca61e7f3f14bfaff..77e7c80c20a5fbe2d0be07631d94da5243ac7bb4 100644 (file)
@@ -164,4 +164,4 @@ bool dissected_image_has_verity(const DissectedImage *image, PartitionDesignator
 
 int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, LoopDevice **ret_loop_device, DecryptedImage **ret_decrypted_image);
 
-int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options);
+int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level);
index 183a686706ef5ad030bbfc5740c9feb118931da3..576e4054c2944d2e1179761f19d78655e7b46a14 100644 (file)
@@ -855,7 +855,7 @@ static int mount_in_namespace(
         mount_tmp_created = true;
 
         if (is_image)
-                r = verity_dissect_and_mount(chased_src, mount_tmp, options);
+                r = verity_dissect_and_mount(chased_src, mount_tmp, options, NULL, NULL, NULL);
         else
                 r = mount_follow_verbose(LOG_DEBUG, chased_src, mount_tmp, NULL, MS_BIND, NULL);
         if (r < 0)
index b4db78492ea2f6859444b59746fcd1349af2a53e..b162928482cb7e17b553596b7e74740603f1e8f3 100644 (file)
@@ -175,6 +175,8 @@ static void test_protect_kernel_logs(void) {
                                     NULL,
                                     NULL,
                                     NULL,
+                                    0,
+                                    NULL,
                                     NULL,
                                     NULL,
                                     0,
index 71ccfb88f421fa5fa3dfb89b9aaa5eb7e86a4b1e..761ee5da866d5dc8c9eaa064bd89c5316b5282c0 100644 (file)
@@ -103,6 +103,8 @@ int main(int argc, char *argv[]) {
                             NULL,
                             NULL,
                             NULL,
+                            0,
+                            NULL,
                             NULL,
                             NULL,
                             0,
index 2ff81bf4fa9626532fda72e64033aa3be3d4ba61..9d42e4891c18ddc3429b69b0e4966e0b5375c9c4 100755 (executable)
@@ -9,6 +9,8 @@ TEST_INSTALL_VERITY_MINIMAL=1
 
 . $TEST_BASE_DIR/test-functions
 
+command -v mksquashfs >/dev/null 2>&1 || exit 0
+command -v veritysetup >/dev/null 2>&1 || exit 0
 command -v sfdisk >/dev/null 2>&1 || exit 0
 
 # Need loop devices for systemd-dissect
@@ -17,6 +19,7 @@ test_append_files() {
         instmods loop =block
         instmods squashfs =squashfs
         instmods dm_verity =md
+        instmods overlay =overlayfs
         install_dmevent
         generate_module_dependencies
         inst_binary losetup
index 0c7ded6786a2dc457e3d6c031242c5006bd44f9a..e2e8f61c67c559974433d8690803eafd4caeae86 100644 (file)
@@ -206,6 +206,7 @@ RootImage=
 RootHash=
 RootHashSignature=
 RootVerity=
+ExtensionImages=
 RuntimeMaxSec=
 SELinuxContextFromNet=
 SecureBits=
index 39df122ef23922645971a1983efc68af55fe7bbb..b2a875ffce4072f0b1c67636b7cf96080be2ccbe 100644 (file)
@@ -480,18 +480,20 @@ install_verity_minimal() {
         BASICTOOLS=(
             bash
             cat
+            grep
             mount
             sleep
         )
         oldinitdir=$initdir
         rm -rfv $TESTDIR/minimal
         export initdir=$TESTDIR/minimal
-        mkdir -p $initdir/usr/lib/systemd/system $initdir/etc
+        mkdir -p $initdir/usr/lib/systemd/system $initdir/usr/lib/extension-release.d $initdir/etc $initdir/var/tmp $initdir/opt
         setup_basic_dirs
         install_basic_tools
         cp $os_release $initdir/usr/lib/os-release
         ln -s ../usr/lib/os-release $initdir/etc/os-release
         touch $initdir/etc/machine-id $initdir/etc/resolv.conf
+        touch $initdir/opt/some_file
         echo MARKER=1 >> $initdir/usr/lib/os-release
         echo -e "[Service]\nExecStartPre=cat /usr/lib/os-release\nExecStart=sleep 120" > $initdir/usr/lib/systemd/system/app0.service
         cp $initdir/usr/lib/systemd/system/app0.service $initdir/usr/lib/systemd/system/app0-foo.service
@@ -507,6 +509,52 @@ install_verity_minimal() {
         mksquashfs $initdir $oldinitdir/usr/share/minimal_1.raw
         veritysetup format $oldinitdir/usr/share/minimal_1.raw $oldinitdir/usr/share/minimal_1.verity | \
             grep '^Root hash:' | cut -f2 | tr -d '\n' > $oldinitdir/usr/share/minimal_1.roothash
+
+        # Rolling distros like Arch do not set VERSION_ID
+        local version_id=""
+        if grep -q "^VERSION_ID=" $os_release; then
+            version_id="$(grep "^VERSION_ID=" $os_release)"
+        fi
+
+        export initdir=$TESTDIR/app0
+        mkdir -p $initdir/usr/lib/extension-release.d $initdir/usr/lib/systemd/system $initdir/opt
+        grep "^ID=" $os_release > $initdir/usr/lib/extension-release.d/extension-release.app0
+        echo "${version_id}" >> $initdir/usr/lib/extension-release.d/extension-release.app0
+        cat <<EOF > $initdir/usr/lib/systemd/system/app0.service
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/opt/script0.sh
+EOF
+        cat <<EOF > $initdir/opt/script0.sh
+#!/bin/bash
+set -e
+test -e /usr/lib/os-release
+cat /usr/lib/extension-release.d/extension-release.app0
+EOF
+        chmod +x $initdir/opt/script0.sh
+        echo MARKER=1 > $initdir/usr/lib/systemd/system/some_file
+        mksquashfs $initdir $oldinitdir/usr/share/app0.raw
+
+        export initdir=$TESTDIR/app1
+        mkdir -p $initdir/usr/lib/extension-release.d $initdir/usr/lib/systemd/system $initdir/opt
+        grep "^ID=" $os_release > $initdir/usr/lib/extension-release.d/extension-release.app1
+        echo "${version_id}" >> $initdir/usr/lib/extension-release.d/extension-release.app1
+        cat <<EOF > $initdir/usr/lib/systemd/system/app1.service
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/opt/script1.sh
+EOF
+        cat <<EOF > $initdir/opt/script1.sh
+#!/bin/bash
+set -e
+test -e /usr/lib/os-release
+cat /usr/lib/extension-release.d/extension-release.app1
+EOF
+        chmod +x $initdir/opt/script1.sh
+        echo MARKER=1 > $initdir/usr/lib/systemd/system/other_file
+        mksquashfs $initdir $oldinitdir/usr/share/app1.raw
     )
 }
 
index 1dd4b5dbd1cbfc447c69b55a3ea46dff76316e73..f3781e6d15de166bee7ac38d6f0cd079ef4ff22e 100755 (executable)
@@ -227,6 +227,27 @@ done
 
 systemctl is-active testservice-50d.service
 
+# ExtensionImages will set up an overlay
+systemd-run -t --property ExtensionImages=/usr/share/app0.raw --property RootImage=${image}.raw cat /opt/script0.sh | grep -q -F "extension-release.app0"
+systemd-run -t --property ExtensionImages=/usr/share/app0.raw --property RootImage=${image}.raw cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+systemd-run -t --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage=${image}.raw cat /opt/script0.sh | grep -q -F "extension-release.app0"
+systemd-run -t --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage=${image}.raw cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+systemd-run -t --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage=${image}.raw cat /opt/script1.sh | grep -q -F "extension-release.app1"
+systemd-run -t --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage=${image}.raw cat /usr/lib/systemd/system/other_file | grep -q -F "MARKER=1"
+cat >/run/systemd/system/testservice-50e.service <<EOF
+[Service]
+MountAPIVFS=yes
+TemporaryFileSystem=/run
+RootImage=${image}.raw
+ExtensionImages=/usr/share/app0.raw /usr/share/app1.raw:nosuid
+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-50e.service
+systemctl is-active testservice-50e.service
+
 echo OK >/testok
 
 exit 0