]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
os-release: add new PORTABLE_PREFIXES= field for declaring valid portable service...
authorLennart Poettering <lennart@poettering.net>
Fri, 19 Nov 2021 15:19:19 +0000 (16:19 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 23 Nov 2021 21:55:11 +0000 (22:55 +0100)
docs/PORTABLE_SERVICES.md
man/os-release.xml
src/portable/portable.c
src/portable/portable.h
src/portable/portabled-image-bus.c

index df6eb9958e00825693c4f004a788376df56b4fbd..dd9164126fd3eda68790b2f5e003fefcde24990b 100644 (file)
@@ -247,6 +247,20 @@ image. To facilitate 3 and 4 you also need to include a boot loader in the
 image. As mentioned, `mkosi -b` takes care of all of that for you, but any
 other image generator should work too.
 
+The
+[os-release(5)](https://www.freedesktop.org/software/systemd/man/os-release.html)
+file may optionally be extended with a `PORTABLE_PREFIXES=` field listing all
+supported portable service prefixes for the image (see above). This is useful
+for informational purposes (as it allows recognizing portable service images
+from their contents as such), but is also useful to protect the image from
+being used under a wrong name and prefix. This is particularly relevant if the
+images are cryptographically authenticated (via Verity or a similar mechanism)
+as this way the (not necessarily authenticated) image file name can be
+validated against the (authenticated) image contents. If the field is not
+specified the image will work fine, but is not necessarily recognizable as
+portable service image, and any set of units included in the image may be
+attached, there are no restrictions enforced.
+
 ## Extension Images
 
 Portable services can be delivered as one or multiple images that extend the base
index a985151b4d4658c734456cdba1021c6ec3a7ca64..1826a60d1afec8dd0bfa5b5267060375aae90a1d 100644 (file)
           regular systems and to portable service environments, but not to initrd
           environments.</para></listitem>
         </varlistentry>
+
+        <varlistentry>
+          <term><varname>PORTABLE_PREFIXES=</varname></term>
+          <listitem><para>Takes a space-separated list of one or more valid prefix match strings for the
+          <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> logic. This field
+          serves two purposes: it's informational, identifying portable service images as such (and thus
+          allowing them to be distinguished from other OS images, such as bootable system images); whenever a
+          portable service image is attached the specified or implied portable service prefix is checked
+          against this list, to enforce restrictions how images may be attached to a
+          system.</para></listitem>
+        </varlistentry>
       </variablelist>
     </refsect2>
 
index 612893b688b3db9af7607c74307f84797f7a6636..2d1006eabdbb77593bc6ebb63c2afeee4261bba2 100644 (file)
@@ -13,6 +13,7 @@
 #include "discover-image.h"
 #include "dissect-image.h"
 #include "env-file.h"
+#include "env-util.h"
 #include "errno-list.h"
 #include "escape.h"
 #include "extension-release.h"
@@ -509,20 +510,20 @@ static int extract_image_and_extensions(
                 OrderedHashmap **ret_extension_images,
                 PortableMetadata **ret_os_release,
                 Hashmap **ret_unit_files,
+                char ***ret_valid_prefixes,
                 sd_bus_error *error) {
 
         _cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL;
         _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
         _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
         _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
+        _cleanup_strv_free_ char **valid_prefixes = NULL;
         _cleanup_(image_unrefp) Image *image = NULL;
         Image *ext;
         int r;
 
         assert(name_or_path);
         assert(matches);
-        assert(ret_image);
-        assert(ret_extension_images);
 
         r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
         if (r < 0)
@@ -553,10 +554,12 @@ static int extract_image_and_extensions(
         if (r < 0)
                 return r;
 
-        /* If we are layering extension images on top of a runtime image, check that the os-release and extension-release metadata
-         * match, otherwise reject it immediately as invalid, or it will fail when the units are started. */
-        if (validate_sysext) {
+        /* If we are layering extension images on top of a runtime image, check that the os-release and
+         * extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
+         * the units are started. Also, collect valid portable prefixes if caller requested that. */
+        if (validate_sysext || ret_valid_prefixes) {
                 _cleanup_fclose_ FILE *f = NULL;
+                _cleanup_free_ char *prefixes = NULL;
 
                 r = take_fdopen_unlocked(&os_release->fd, "r", &f);
                 if (r < 0)
@@ -565,9 +568,16 @@ static int extract_image_and_extensions(
                 r = parse_env_file(f, os_release->name,
                                    "ID", &id,
                                    "VERSION_ID", &version_id,
-                                   "SYSEXT_LEVEL", &sysext_level);
+                                   "SYSEXT_LEVEL", &sysext_level,
+                                   "PORTABLE_PREFIXES", &prefixes);
                 if (r < 0)
                         return r;
+
+                if (prefixes) {
+                        valid_prefixes = strv_split(prefixes, WHITESPACE);
+                        if (!valid_prefixes)
+                                return -ENOMEM;
+                }
         }
 
         ORDERED_HASHMAP_FOREACH(ext, extension_images) {
@@ -575,6 +585,7 @@ static int extract_image_and_extensions(
                 _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
                 _cleanup_strv_free_ char **extension_release = NULL;
                 _cleanup_fclose_ FILE *f = NULL;
+                const char *e;
 
                 r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, &extension_release_meta, &extra_unit_files, error);
                 if (r < 0)
@@ -584,7 +595,7 @@ static int extract_image_and_extensions(
                 if (r < 0)
                         return r;
 
-                if (!validate_sysext)
+                if (!validate_sysext && !ret_valid_prefixes)
                         continue;
 
                 r = take_fdopen_unlocked(&extension_release_meta->fd, "r", &f);
@@ -595,19 +606,40 @@ static int extract_image_and_extensions(
                 if (r < 0)
                         return r;
 
-                r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
-                if (r == 0)
-                        return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
-                if (r < 0)
-                        return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path);
+                if (validate_sysext) {
+                        r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
+                        if (r == 0)
+                                return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
+                        if (r < 0)
+                                return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path);
+                }
+
+                e = strv_env_pairs_get(extension_release, "PORTABLE_PREFIXES");
+                if (e) {
+                        _cleanup_strv_free_ char **l = NULL;
+
+                        l = strv_split(e, WHITESPACE);
+                        if (!l)
+                                return -ENOMEM;
+
+                        r = strv_extend_strv(&valid_prefixes, l, true);
+                        if (r < 0)
+                                return r;
+                }
         }
 
-        *ret_image = TAKE_PTR(image);
-        *ret_extension_images = TAKE_PTR(extension_images);
+        strv_sort(valid_prefixes);
+
+        if (ret_image)
+                *ret_image = TAKE_PTR(image);
+        if (ret_extension_images)
+                *ret_extension_images = TAKE_PTR(extension_images);
         if (ret_os_release)
                 *ret_os_release = TAKE_PTR(os_release);
         if (ret_unit_files)
                 *ret_unit_files = TAKE_PTR(unit_files);
+        if (ret_valid_prefixes)
+                *ret_valid_prefixes = TAKE_PTR(valid_prefixes);
 
         return 0;
 }
@@ -618,23 +650,29 @@ int portable_extract(
                 char **extension_image_paths,
                 PortableMetadata **ret_os_release,
                 Hashmap **ret_unit_files,
+                char ***ret_valid_prefixes,
                 sd_bus_error *error) {
 
         _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
         _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
         _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
+        _cleanup_(strv_freep) char **valid_prefixes = NULL;
         _cleanup_(image_unrefp) Image *image = NULL;
         int r;
 
-        r = extract_image_and_extensions(name_or_path,
-                                         matches,
-                                         extension_image_paths,
-                                         /* validate_sysext= */ false,
-                                         &image,
-                                         &extension_images,
-                                         &os_release,
-                                         &unit_files,
-                                         error);
+        assert(name_or_path);
+
+        r = extract_image_and_extensions(
+                        name_or_path,
+                        matches,
+                        extension_image_paths,
+                        /* validate_sysext= */ false,
+                        &image,
+                        &extension_images,
+                        &os_release,
+                        &unit_files,
+                        ret_valid_prefixes ? &valid_prefixes : NULL,
+                        error);
         if (r < 0)
                 return r;
 
@@ -651,8 +689,12 @@ int portable_extract(
                                          isempty(extensions) ? "" : extensions);
         }
 
-        *ret_os_release = TAKE_PTR(os_release);
-        *ret_unit_files = TAKE_PTR(unit_files);
+        if (ret_os_release)
+                *ret_os_release = TAKE_PTR(os_release);
+        if (ret_unit_files)
+                *ret_unit_files = TAKE_PTR(unit_files);
+        if (ret_valid_prefixes)
+                *ret_valid_prefixes = TAKE_PTR(valid_prefixes);
 
         return 0;
 }
@@ -1211,6 +1253,18 @@ static int install_image_and_extensions_symlinks(
         return 0;
 }
 
+static bool prefix_matches_compatible(char **matches, char **valid_prefixes) {
+        char **m;
+
+        /* Checks if all 'matches' are included in the list of 'valid_prefixes' */
+
+        STRV_FOREACH(m, matches)
+                if (!strv_contains(valid_prefixes, *m))
+                        return false;
+
+        return true;
+}
+
 int portable_attach(
                 sd_bus *bus,
                 const char *name_or_path,
@@ -1225,33 +1279,63 @@ int portable_attach(
         _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
         _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
         _cleanup_(lookup_paths_free) LookupPaths paths = {};
+        _cleanup_strv_free_ char **valid_prefixes = NULL;
         _cleanup_(image_unrefp) Image *image = NULL;
         PortableMetadata *item;
         int r;
 
-        r = extract_image_and_extensions(name_or_path,
-                                         matches,
-                                         extension_image_paths,
-                                         /* validate_sysext= */ true,
-                                         &image,
-                                         &extension_images,
-                                         /* os_release= */ NULL,
-                                         &unit_files,
-                                         error);
+        r = extract_image_and_extensions(
+                        name_or_path,
+                        matches,
+                        extension_image_paths,
+                        /* validate_sysext= */ true,
+                        &image,
+                        &extension_images,
+                        /* os_release= */ NULL,
+                        &unit_files,
+                        &valid_prefixes,
+                        error);
         if (r < 0)
                 return r;
 
+        if (valid_prefixes && !prefix_matches_compatible(matches, valid_prefixes)) {
+                _cleanup_free_ char *matches_joined = NULL, *extensions_joined = NULL, *valid_prefixes_joined = NULL;
+
+                matches_joined = strv_join(matches, "', '");
+                if (!matches_joined)
+                        return -ENOMEM;
+
+                extensions_joined = strv_join(extension_image_paths, ", ");
+                if (!extensions_joined)
+                        return -ENOMEM;
+
+                valid_prefixes_joined = strv_join(valid_prefixes, ", ");
+                if (!valid_prefixes_joined)
+                        return -ENOMEM;
+
+                return sd_bus_error_setf(
+                                error,
+                                SD_BUS_ERROR_INVALID_ARGS,
+                                "Selected matches '%s' are not compatible with portable service image '%s%s%s', refusing. (Acceptable prefix matches are: %s)",
+                                matches_joined,
+                                image->path,
+                                isempty(extensions_joined) ? "" : "' or any of its extensions '",
+                                strempty(extensions_joined),
+                                valid_prefixes_joined);
+        }
+
         if (hashmap_isempty(unit_files)) {
-                _cleanup_free_ char *extensions = strv_join(extension_image_paths, ", ");
-                if (!extensions)
+                _cleanup_free_ char *extensions_joined = strv_join(extension_image_paths, ", ");
+                if (!extensions_joined)
                         return -ENOMEM;
 
-                return sd_bus_error_setf(error,
-                                         SD_BUS_ERROR_INVALID_ARGS,
-                                         "Couldn't find any matching unit files in image '%s%s%s', refusing.",
-                                         image->path,
-                                         isempty(extensions) ? "" : "' or any of its extensions '",
-                                         isempty(extensions) ? "" : extensions);
+                return sd_bus_error_setf(
+                                error,
+                                SD_BUS_ERROR_INVALID_ARGS,
+                                "Couldn't find any matching unit files in image '%s%s%s', refusing.",
+                                image->path,
+                                isempty(extensions_joined) ? "" : "' or any of its extensions '",
+                                strempty(extensions_joined));
         }
 
         r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL);
index 077ab3333f18f9fc0b8c6ef5eea1666eeeefe829..2837e8b2869ed1af1c8b9fa2a4f72301d63bffbc 100644 (file)
@@ -65,7 +65,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref);
 
 int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
 
-int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error);
+int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
 
 int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
 int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
index 23c6e2633a99496f8288e00855ae7adadfde19fd..ede062dbfb4e5de274cbaf0c1a92843dd367499c 100644 (file)
@@ -162,6 +162,7 @@ int bus_image_common_get_metadata(
                         extension_images,
                         &os_release,
                         &unit_files,
+                        NULL,
                         error);
         if (r < 0)
                 return r;