mirror of
https://github.com/systemd/systemd.git
synced 2024-11-01 17:51:22 +03:00
Merge pull request #20465 from bluca/portable_validate_sysext
portabled: validate SYSEXT_LEVEL when attaching
This commit is contained in:
commit
7d50cd65bb
@ -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;
|
||||
|
||||
@ -423,15 +436,22 @@ 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 (path_is_extension)
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
@ -477,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);
|
||||
@ -491,13 +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 (!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 (!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);
|
||||
@ -508,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;
|
||||
@ -524,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)
|
||||
@ -550,19 +576,106 @@ 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;
|
||||
/* 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, 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 = 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)
|
||||
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);
|
||||
@ -1138,47 +1251,31 @@ int portable_attach(
|
||||
_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)
|
||||
|
||||
if (hashmap_isempty(unit_files)) {
|
||||
_cleanup_free_ char *extensions = strv_join(extension_image_paths, ", ");
|
||||
if (!extensions)
|
||||
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, true, matches, NULL, &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);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = hashmap_move(unit_files, extra_unit_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
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);
|
||||
|
@ -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 {
|
||||
|
@ -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 */
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user