From 9ccb531a5f99a7f399f352e79079188957f5a170 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 18 Aug 2021 16:08:14 +0100 Subject: [PATCH 1/4] dissect-image: add extension-specific validation flag Allows callers to specify which image type they are looking for --- src/portable/portable.c | 9 ++++++++- src/shared/dissect-image.c | 26 +++++++++++++++++++------- src/shared/dissect-image.h | 23 ++++++++++++----------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/portable/portable.c b/src/portable/portable.c index 4044c523e1..b3fcf2719a 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -423,9 +423,16 @@ static int portable_extract_by_path( if (r < 0) return r; if (r == 0) { + DissectImageFlags flags = DISSECT_IMAGE_READ_ONLY; + seq[0] = safe_close(seq[0]); - r = dissected_image_mount(m, tmpdir, UID_INVALID, UID_INVALID, DISSECT_IMAGE_READ_ONLY); + if (!extract_os_release) + flags |= DISSECT_IMAGE_VALIDATE_OS_EXT; + else + flags |= DISSECT_IMAGE_VALIDATE_OS; + + r = dissected_image_mount(m, tmpdir, UID_INVALID, UID_INVALID, flags); if (r < 0) { log_debug_errno(r, "Failed to mount dissected image: %m"); goto child_finish; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 99a2f62e4a..81ae0c3ffc 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1742,17 +1742,28 @@ int dissected_image_mount( if (r < 0) return r; - if (flags & DISSECT_IMAGE_VALIDATE_OS) { - r = path_is_os_tree(where); - if (r < 0) - return r; - if (r == 0) { + if ((flags & (DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_VALIDATE_OS_EXT)) != 0) { + /* If either one of the validation flags are set, ensure that the image qualifies + * as one or the other (or both). */ + bool ok = false; + + if (FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS)) { + r = path_is_os_tree(where); + if (r < 0) + return r; + if (r > 0) + ok = true; + } + if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT)) { r = path_is_extension_tree(where, m->image_name); if (r < 0) return r; - if (r == 0) - return -EMEDIUMTYPE; + if (r > 0) + ok = true; } + + if (!ok) + return -ENOMEDIUM; } } @@ -2623,6 +2634,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) { DISSECT_IMAGE_READ_ONLY| DISSECT_IMAGE_MOUNT_ROOT_ONLY| DISSECT_IMAGE_VALIDATE_OS| + DISSECT_IMAGE_VALIDATE_OS_EXT| DISSECT_IMAGE_USR_NO_ROOT); if (r < 0) { /* Let parent know the error */ diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index cad52f2184..4c4f5ff585 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -100,19 +100,20 @@ typedef enum DissectImageFlags { DISSECT_IMAGE_MOUNT_ROOT_ONLY = 1 << 6, /* Mount only the root and /usr partitions */ DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY = 1 << 7, /* Mount only the non-root and non-/usr partitions */ DISSECT_IMAGE_VALIDATE_OS = 1 << 8, /* Refuse mounting images that aren't identifiable as OS images */ - DISSECT_IMAGE_NO_UDEV = 1 << 9, /* Don't wait for udev initializing things */ - DISSECT_IMAGE_RELAX_VAR_CHECK = 1 << 10, /* Don't insist that the UUID of /var is hashed from /etc/machine-id */ - DISSECT_IMAGE_FSCK = 1 << 11, /* File system check the partition before mounting (no effect when combined with DISSECT_IMAGE_READ_ONLY) */ - DISSECT_IMAGE_NO_PARTITION_TABLE = 1 << 12, /* Only recognize single file system images */ - DISSECT_IMAGE_VERITY_SHARE = 1 << 13, /* When activating a verity device, reuse existing one if already open */ - DISSECT_IMAGE_MKDIR = 1 << 14, /* Make top-level directory to mount right before mounting, if missing */ - DISSECT_IMAGE_USR_NO_ROOT = 1 << 15, /* If no root fs is in the image, but /usr is, then allow this (so that we can mount the rootfs as tmpfs or so */ - DISSECT_IMAGE_REQUIRE_ROOT = 1 << 16, /* Don't accept disks without root partition (or at least /usr partition if DISSECT_IMAGE_USR_NO_ROOT is set) */ - DISSECT_IMAGE_MOUNT_READ_ONLY = 1 << 17, /* Make mounts read-only */ + DISSECT_IMAGE_VALIDATE_OS_EXT = 1 << 9, /* Refuse mounting images that aren't identifiable as OS extension images */ + DISSECT_IMAGE_NO_UDEV = 1 << 10, /* Don't wait for udev initializing things */ + DISSECT_IMAGE_RELAX_VAR_CHECK = 1 << 11, /* Don't insist that the UUID of /var is hashed from /etc/machine-id */ + DISSECT_IMAGE_FSCK = 1 << 12, /* File system check the partition before mounting (no effect when combined with DISSECT_IMAGE_READ_ONLY) */ + DISSECT_IMAGE_NO_PARTITION_TABLE = 1 << 13, /* Only recognize single file system images */ + DISSECT_IMAGE_VERITY_SHARE = 1 << 14, /* When activating a verity device, reuse existing one if already open */ + DISSECT_IMAGE_MKDIR = 1 << 15, /* Make top-level directory to mount right before mounting, if missing */ + DISSECT_IMAGE_USR_NO_ROOT = 1 << 16, /* If no root fs is in the image, but /usr is, then allow this (so that we can mount the rootfs as tmpfs or so */ + DISSECT_IMAGE_REQUIRE_ROOT = 1 << 17, /* Don't accept disks without root partition (or at least /usr partition if DISSECT_IMAGE_USR_NO_ROOT is set) */ + DISSECT_IMAGE_MOUNT_READ_ONLY = 1 << 18, /* Make mounts read-only */ DISSECT_IMAGE_READ_ONLY = DISSECT_IMAGE_DEVICE_READ_ONLY | DISSECT_IMAGE_MOUNT_READ_ONLY, - DISSECT_IMAGE_GROWFS = 1 << 18, /* Grow file systems in partitions marked for that to the size of the partitions after mount */ - DISSECT_IMAGE_MOUNT_IDMAPPED = 1 << 19, /* Mount mounts with kernel 5.12-style userns ID mapping, if file system type doesn't support uid=/gid= */ + DISSECT_IMAGE_GROWFS = 1 << 19, /* Grow file systems in partitions marked for that to the size of the partitions after mount */ + DISSECT_IMAGE_MOUNT_IDMAPPED = 1 << 20, /* Mount mounts with kernel 5.12-style userns ID mapping, if file system type doesn't support uid=/gid= */ } DissectImageFlags; struct DissectedImage { From 7bf5ec4538cd4c77979dd9d09d9e9429a0a3535c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 18 Aug 2021 16:37:13 +0100 Subject: [PATCH 2/4] portabled: error out if there are no units only after parsing all images It's ok if the OS image doesn't have matching units, if we find them in the extensions. Tidies up the parsing logic a bit. --- src/portable/portable.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/portable/portable.c b/src/portable/portable.c index b3fcf2719a..aa072f9489 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -503,9 +503,6 @@ static int portable_extract_by_path( if (extract_os_release && !os_release) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image '%s' lacks os-release data, refusing.", path); - if (!extract_os_release && hashmap_isempty(unit_files)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't find any matching unit files in image '%s', refusing.", path); - if (ret_unit_files) *ret_unit_files = TAKE_PTR(unit_files); @@ -572,6 +569,19 @@ int portable_extract( return r; } + if (hashmap_isempty(unit_files)) { + _cleanup_free_ char *extensions = strv_join(extension_image_paths, ", "); + if (!extensions) + 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); + } + *ret_os_release = TAKE_PTR(os_release); *ret_unit_files = TAKE_PTR(unit_files); @@ -1188,6 +1198,19 @@ int portable_attach( return r; } + if (hashmap_isempty(unit_files)) { + _cleanup_free_ char *extensions = strv_join(extension_image_paths, ", "); + if (!extensions) + 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); + } + r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL); if (r < 0) return r; From 239ac0c7f72c30cab2e84d395d064c3b7384ff84 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 4 Aug 2021 15:00:06 +0100 Subject: [PATCH 3/4] portabled: validate SYSEXT_LEVEL when attaching When attaching a portable service with extensions, immediately validate that the os-release and extension-release metadata values match, rather than letting it fail when the units are started --- src/portable/portable.c | 92 ++++++++++++++++++++++++++++++++--------- src/portable/portable.h | 1 + 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/src/portable/portable.c b/src/portable/portable.c index aa072f9489..11a112fe3a 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -11,8 +11,10 @@ #include "dirent-util.h" #include "discover-image.h" #include "dissect-image.h" +#include "env-file.h" #include "errno-list.h" #include "escape.h" +#include "extension-release.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -230,6 +232,8 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(portable_metadata_hash_ops, char, static int extract_now( const char *where, char **matches, + const char *image_name, + bool path_is_extension, int socket_fd, PortableMetadata **ret_os_release, Hashmap **ret_unit_files) { @@ -239,6 +243,7 @@ static int extract_now( _cleanup_(lookup_paths_free) LookupPaths paths = {}; _cleanup_close_ int os_release_fd = -1; _cleanup_free_ char *os_release_path = NULL; + const char *os_release_id; char **i; int r; @@ -253,19 +258,27 @@ static int extract_now( assert(where); - /* First, find /etc/os-release and send it upstream (or just save it). */ - r = open_os_release(where, &os_release_path, &os_release_fd); + /* First, find os-release/extension-release and send it upstream (or just save it). */ + if (path_is_extension) { + os_release_id = strjoina("/usr/lib/extension-release.d/extension-release.", image_name); + r = open_extension_release(where, image_name, &os_release_path, &os_release_fd); + } else { + os_release_id = "/etc/os-release"; + r = open_os_release(where, &os_release_path, &os_release_fd); + } if (r < 0) - log_debug_errno(r, "Couldn't acquire os-release file, ignoring: %m"); + log_debug_errno(r, + "Couldn't acquire %s file, ignoring: %m", + path_is_extension ? "extension-release " : "os-release"); else { if (socket_fd >= 0) { - r = send_item(socket_fd, "/etc/os-release", os_release_fd); + r = send_item(socket_fd, os_release_id, os_release_fd); if (r < 0) return log_debug_errno(r, "Failed to send os-release file: %m"); } if (ret_os_release) { - os_release = portable_metadata_new("/etc/os-release", NULL, os_release_fd); + os_release = portable_metadata_new(os_release_id, NULL, os_release_fd); if (!os_release) return -ENOMEM; @@ -351,7 +364,7 @@ static int extract_now( static int portable_extract_by_path( const char *path, - bool extract_os_release, + bool path_is_extension, char **matches, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, @@ -369,7 +382,7 @@ static int portable_extract_by_path( /* We can't turn this into a loop-back block device, and this returns EISDIR? Then this is a directory * tree and not a raw device. It's easy then. */ - r = extract_now(path, matches, -1, &os_release, &unit_files); + r = extract_now(path, matches, NULL, path_is_extension, -1, &os_release, &unit_files); if (r < 0) return r; @@ -427,7 +440,7 @@ static int portable_extract_by_path( seq[0] = safe_close(seq[0]); - if (!extract_os_release) + if (path_is_extension) flags |= DISSECT_IMAGE_VALIDATE_OS_EXT; else flags |= DISSECT_IMAGE_VALIDATE_OS; @@ -438,7 +451,7 @@ static int portable_extract_by_path( goto child_finish; } - r = extract_now(tmpdir, matches, seq[1], NULL, NULL); + r = extract_now(tmpdir, matches, m->image_name, path_is_extension, seq[1], NULL, NULL); child_finish: _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); @@ -484,7 +497,7 @@ static int portable_extract_by_path( add = NULL; - } else if (PORTABLE_METADATA_IS_OS_RELEASE(add)) { + } else if (PORTABLE_METADATA_IS_OS_RELEASE(add) || PORTABLE_METADATA_IS_EXTENSION_RELEASE(add)) { assert(!os_release); os_release = TAKE_PTR(add); @@ -498,10 +511,12 @@ static int portable_extract_by_path( child = 0; } - /* When the portable image is layered, the image with units will not - * have a full filesystem, so no os-release - it will be in the root layer */ - if (extract_os_release && !os_release) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image '%s' lacks os-release data, refusing.", path); + if (!os_release) + return sd_bus_error_setf(error, + SD_BUS_ERROR_INVALID_ARGS, + "Image '%s' lacks %s data, refusing.", + path, + path_is_extension ? "extension-release" : "os-release"); if (ret_unit_files) *ret_unit_files = TAKE_PTR(unit_files); @@ -554,14 +569,14 @@ int portable_extract( } } - r = portable_extract_by_path(image->path, true, matches, &os_release, &unit_files, error); + r = portable_extract_by_path(image->path, /* path_is_extension= */ false, matches, &os_release, &unit_files, error); if (r < 0) return r; ORDERED_HASHMAP_FOREACH(ext, extension_images) { _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; - r = portable_extract_by_path(ext->path, false, matches, NULL, &extra_unit_files, error); + r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, NULL, &extra_unit_files, error); if (r < 0) return r; r = hashmap_move(unit_files, extra_unit_files); @@ -1150,6 +1165,8 @@ int portable_attach( size_t *n_changes, 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_(lookup_paths_free) LookupPaths paths = {}; @@ -1183,16 +1200,51 @@ int portable_attach( } } - r = portable_extract_by_path(image->path, true, matches, NULL, &unit_files, error); + r = portable_extract_by_path(image->path, /* path_is_extension= */ false, matches, &os_release, &unit_files, error); if (r < 0) return r; - ORDERED_HASHMAP_FOREACH(ext, extension_images) { - _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; + /* 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 (os_release) { + _cleanup_fclose_ FILE *f = NULL; - r = portable_extract_by_path(ext->path, false, matches, NULL, &extra_unit_files, error); + r = take_fdopen_unlocked(&os_release->fd, "r", &f); if (r < 0) return r; + + r = parse_env_file(f, os_release->name, + "ID", &id, + "VERSION_ID", &version_id, + "SYSEXT_LEVEL", &sysext_level); + if (r < 0) + return r; + } + + ORDERED_HASHMAP_FOREACH(ext, extension_images) { + _cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL; + _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; + _cleanup_strv_free_ char **extension_release = NULL; + _cleanup_fclose_ FILE *f = NULL; + + r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, &extension_release_meta, &extra_unit_files, error); + if (r < 0) + return r; + + r = take_fdopen_unlocked(&extension_release_meta->fd, "r", &f); + if (r < 0) + return r; + + r = load_env_file_pairs(f, extension_release_meta->name, &extension_release); + if (r < 0) + return r; + + r = extension_release_validate(ext->path, id, version_id, sysext_level, 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); + r = hashmap_move(unit_files, extra_unit_files); if (r < 0) return r; diff --git a/src/portable/portable.h b/src/portable/portable.h index dd080edf4e..94144287ae 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -16,6 +16,7 @@ typedef struct PortableMetadata { } PortableMetadata; #define PORTABLE_METADATA_IS_OS_RELEASE(m) (streq((m)->name, "/etc/os-release")) +#define PORTABLE_METADATA_IS_EXTENSION_RELEASE(m) (startswith((m)->name, "/usr/lib/extension-release.d/extension-release.")) #define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/')) typedef enum PortableFlags { From 9ff61565be1efe5cc962964cde1af2278e554e9e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 6 Sep 2021 13:19:47 +0100 Subject: [PATCH 4/4] portabled: refactor extraction/validation into a common helper --- src/portable/portable.c | 175 ++++++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 80 deletions(-) diff --git a/src/portable/portable.c b/src/portable/portable.c index 11a112fe3a..3f841b9258 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -527,14 +527,18 @@ static int portable_extract_by_path( return 0; } -int portable_extract( +static int extract_image_and_extensions( const char *name_or_path, char **matches, char **extension_image_paths, + bool validate_sysext, + Image **ret_image, + OrderedHashmap **ret_extension_images, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, 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; @@ -543,6 +547,9 @@ int portable_extract( 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) @@ -573,17 +580,91 @@ int portable_extract( if (r < 0) return r; - ORDERED_HASHMAP_FOREACH(ext, extension_images) { - _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; + /* 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) { + _cleanup_fclose_ FILE *f = NULL; - r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, NULL, &extra_unit_files, error); + r = take_fdopen_unlocked(&os_release->fd, "r", &f); if (r < 0) return r; - r = hashmap_move(unit_files, extra_unit_files); + + r = parse_env_file(f, os_release->name, + "ID", &id, + "VERSION_ID", &version_id, + "SYSEXT_LEVEL", &sysext_level); if (r < 0) return r; } + ORDERED_HASHMAP_FOREACH(ext, extension_images) { + _cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL; + _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; + _cleanup_strv_free_ char **extension_release = NULL; + _cleanup_fclose_ FILE *f = NULL; + + r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, &extension_release_meta, &extra_unit_files, error); + if (r < 0) + return r; + + r = hashmap_move(unit_files, extra_unit_files); + if (r < 0) + return r; + + if (!validate_sysext) + continue; + + r = take_fdopen_unlocked(&extension_release_meta->fd, "r", &f); + if (r < 0) + return r; + + r = load_env_file_pairs(f, extension_release_meta->name, &extension_release); + if (r < 0) + return r; + + r = extension_release_validate(ext->path, id, version_id, sysext_level, 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); + } + + *ret_image = TAKE_PTR(image); + *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); + + return 0; +} + +int portable_extract( + const char *name_or_path, + char **matches, + char **extension_image_paths, + PortableMetadata **ret_os_release, + Hashmap **ret_unit_files, + 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_(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); + if (r < 0) + return r; + if (hashmap_isempty(unit_files)) { _cleanup_free_ char *extensions = strv_join(extension_image_paths, ", "); if (!extensions) @@ -1165,90 +1246,24 @@ int portable_attach( size_t *n_changes, 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_(lookup_paths_free) LookupPaths paths = {}; _cleanup_(image_unrefp) Image *image = NULL; PortableMetadata *item; - Image *ext; - char **p; int r; - assert(name_or_path); - - r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image); + r = extract_image_and_extensions(name_or_path, + matches, + extension_image_paths, + /* validate_sysext= */ true, + &image, + &extension_images, + /* os_release= */ NULL, + &unit_files, + error); if (r < 0) return r; - if (!strv_isempty(extension_image_paths)) { - extension_images = ordered_hashmap_new(&image_hash_ops); - if (!extension_images) - return -ENOMEM; - - STRV_FOREACH(p, extension_image_paths) { - _cleanup_(image_unrefp) Image *new = NULL; - - r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new); - if (r < 0) - return r; - - r = ordered_hashmap_put(extension_images, new->name, new); - if (r < 0) - return r; - TAKE_PTR(new); - } - } - - r = portable_extract_by_path(image->path, /* path_is_extension= */ false, matches, &os_release, &unit_files, error); - 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 (os_release) { - _cleanup_fclose_ FILE *f = NULL; - - r = take_fdopen_unlocked(&os_release->fd, "r", &f); - if (r < 0) - return r; - - r = parse_env_file(f, os_release->name, - "ID", &id, - "VERSION_ID", &version_id, - "SYSEXT_LEVEL", &sysext_level); - if (r < 0) - return r; - } - - ORDERED_HASHMAP_FOREACH(ext, extension_images) { - _cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL; - _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; - _cleanup_strv_free_ char **extension_release = NULL; - _cleanup_fclose_ FILE *f = NULL; - - r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, &extension_release_meta, &extra_unit_files, error); - if (r < 0) - return r; - - r = take_fdopen_unlocked(&extension_release_meta->fd, "r", &f); - if (r < 0) - return r; - - r = load_env_file_pairs(f, extension_release_meta->name, &extension_release); - if (r < 0) - return r; - - r = extension_release_validate(ext->path, id, version_id, sysext_level, 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); - - r = hashmap_move(unit_files, extra_unit_files); - if (r < 0) - return r; - } if (hashmap_isempty(unit_files)) { _cleanup_free_ char *extensions = strv_join(extension_image_paths, ", ");