diff --git a/docs/PORTABLE_SERVICES.md b/docs/PORTABLE_SERVICES.md index df6eb9958e0..dd9164126fd 100644 --- a/docs/PORTABLE_SERVICES.md +++ b/docs/PORTABLE_SERVICES.md @@ -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 diff --git a/man/os-release.xml b/man/os-release.xml index a985151b4d4..1826a60d1af 100644 --- a/man/os-release.xml +++ b/man/os-release.xml @@ -419,6 +419,17 @@ regular systems and to portable service environments, but not to initrd environments. + + + PORTABLE_PREFIXES= + Takes a space-separated list of one or more valid prefix match strings for the + Portable Services 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. + diff --git a/src/portable/portable.c b/src/portable/portable.c index 612893b688b..2d1006eabdb 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -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 (hashmap_isempty(unit_files)) { - _cleanup_free_ char *extensions = strv_join(extension_image_paths, ", "); - if (!extensions) + 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; - 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); + 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_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_joined) ? "" : "' or any of its extensions '", + strempty(extensions_joined)); } r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL); diff --git a/src/portable/portable.h b/src/portable/portable.h index 077ab3333f1..2837e8b2869 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -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); diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c index 23c6e2633a9..ede062dbfb4 100644 --- a/src/portable/portabled-image-bus.c +++ b/src/portable/portabled-image-bus.c @@ -162,6 +162,7 @@ int bus_image_common_get_metadata( extension_images, &os_release, &unit_files, + NULL, error); if (r < 0) return r;