From 7cf660302fceba1928df78152fda967480aef19c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 9 Mar 2021 22:55:45 +0100 Subject: [PATCH] dissect-image: support images without rootfs but with /usr/ Let's add support for images that include an /usr/ file system but no root fs. Mount a tmpfs as root for images like this, all controlled by a new flag DISSECT_IMAGE_USR_NO_ROOT. This is useful for entirely stateless images, that come up pristine on every single boot. --- src/shared/dissect-image.c | 170 +++++++++++++++++++++++++------------ src/shared/dissect-image.h | 1 + 2 files changed, 115 insertions(+), 56 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 6f9a4576144..98f50c6826f 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1100,64 +1100,85 @@ int dissect_image( m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found = false; m->partitions[PARTITION_USR_SECONDARY].found = false; m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false; - } else { - /* No root partition found? Then let's see if ther's one for the secondary architecture. And if not - * either, then check if there's a single generic one, and use that. */ - if (m->partitions[PARTITION_ROOT_VERITY].found) - return -EADDRNOTAVAIL; + } else if (m->partitions[PARTITION_ROOT_VERITY].found) + return -EADDRNOTAVAIL; /* Verity found but no matching rootfs? Something is off, refuse. */ - /* We didn't find a primary architecture root, but we found a primary architecture /usr? Refuse that for now. */ - if (m->partitions[PARTITION_USR].found || m->partitions[PARTITION_USR_VERITY].found) - return -EADDRNOTAVAIL; + else if (m->partitions[PARTITION_ROOT_SECONDARY].found) { - if (m->partitions[PARTITION_ROOT_SECONDARY].found) { - /* Upgrade secondary arch to first */ - m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY]; - zero(m->partitions[PARTITION_ROOT_SECONDARY]); - m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY]; - zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]); - - m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY]; - zero(m->partitions[PARTITION_USR_SECONDARY]); - m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_SECONDARY_VERITY]; - zero(m->partitions[PARTITION_USR_SECONDARY_VERITY]); - - } else if (flags & DISSECT_IMAGE_REQUIRE_ROOT) { - _cleanup_free_ char *o = NULL; - const char *options = NULL; - - /* If the root hash was set, then we won't fall back to a generic node, because the - * root hash decides. */ - if (verity && verity->root_hash) - return -EADDRNOTAVAIL; + /* No root partition found but there's one for the secondary architecture? Then upgrade + * secondary arch to first */ - /* If we didn't find a generic node, then we can't fix this up either */ - if (!generic_node) - return -ENXIO; + m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY]; + zero(m->partitions[PARTITION_ROOT_SECONDARY]); + m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY]; + zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]); - /* If we didn't find a properly marked root partition, but we did find a single suitable - * generic Linux partition, then use this as root partition, if the caller asked for it. */ - if (multiple_generic) - return -ENOTUNIQ; + m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY]; + zero(m->partitions[PARTITION_USR_SECONDARY]); + m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_SECONDARY_VERITY]; + zero(m->partitions[PARTITION_USR_SECONDARY_VERITY]); - options = mount_options_from_designator(mount_options, PARTITION_ROOT); - if (options) { - o = strdup(options); - if (!o) - return -ENOMEM; - } + } else if (m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found) + return -EADDRNOTAVAIL; /* as above */ - m->partitions[PARTITION_ROOT] = (DissectedPartition) { - .found = true, - .rw = generic_rw, - .partno = generic_nr, - .architecture = _ARCHITECTURE_INVALID, - .node = TAKE_PTR(generic_node), - .uuid = generic_uuid, - .mount_options = TAKE_PTR(o), - }; + else if (m->partitions[PARTITION_USR].found) { + + /* Invalidate secondary arch /usr/ if we found the primary arch */ + m->partitions[PARTITION_USR_SECONDARY].found = false; + m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false; + + } else if (m->partitions[PARTITION_USR_VERITY].found) + return -EADDRNOTAVAIL; /* as above */ + + else if (m->partitions[PARTITION_USR_SECONDARY].found) { + + /* Upgrade secondary arch to primary */ + m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY]; + zero(m->partitions[PARTITION_USR_SECONDARY]); + m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_SECONDARY_VERITY]; + zero(m->partitions[PARTITION_USR_SECONDARY_VERITY]); + + } else if (m->partitions[PARTITION_USR_SECONDARY_VERITY].found) + return -EADDRNOTAVAIL; /* as above */ + + else if (flags & DISSECT_IMAGE_REQUIRE_ROOT) { + _cleanup_free_ char *o = NULL; + const char *options = NULL; + + /* OK, we found nothing usable, then check if there's a single generic one distro, and use + * that. */ + + /* If the root hash was set, then we won't fall back to a generic node, because the root hash + * decides. */ + if (verity && verity->root_hash) + return -EADDRNOTAVAIL; + + /* If we didn't find a generic node, then we can't fix this up either */ + if (!generic_node) + return -ENXIO; + + /* If we didn't find a properly marked root partition, but we did find a single suitable + * generic Linux partition, then use this as root partition, if the caller asked for it. */ + if (multiple_generic) + return -ENOTUNIQ; + + options = mount_options_from_designator(mount_options, PARTITION_ROOT); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; } + + m->partitions[PARTITION_ROOT] = (DissectedPartition) { + .found = true, + .rw = generic_rw, + .partno = generic_nr, + .architecture = _ARCHITECTURE_INVALID, + .node = TAKE_PTR(generic_node), + .uuid = generic_uuid, + .mount_options = TAKE_PTR(o), + }; } /* Refuse if we found a verity partition for /usr but no matching file system partition */ @@ -1397,6 +1418,32 @@ static int mount_partition( return 1; } +static int mount_root_tmpfs(const char *where, uid_t uid_shift, DissectImageFlags flags) { + _cleanup_free_ char *options = NULL; + int r; + + assert(where); + + /* For images that contain /usr/ but no rootfs, let's mount rootfs as tmpfs */ + + if (FLAGS_SET(flags, DISSECT_IMAGE_MKDIR)) { + r = mkdir_p(where, 0755); + if (r < 0) + return r; + } + + if (uid_is_valid(uid_shift)) { + if (asprintf(&options, "uid=" UID_FMT ",gid=" GID_FMT, uid_shift, (gid_t) uid_shift) < 0) + return -ENOMEM; + } + + r = mount_nofollow_verbose(LOG_DEBUG, "rootfs", where, "tmpfs", MS_NODEV, options); + if (r < 0) + return r; + + return 1; +} + int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags) { int r, xbootldr_mounted; @@ -1413,16 +1460,20 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, * -EAFNOSUPPORT → File system type not supported or not known */ - if (!m->partitions[PARTITION_ROOT].found) - return -ENXIO; + if (!(m->partitions[PARTITION_ROOT].found || + (m->partitions[PARTITION_USR].found && FLAGS_SET(flags, DISSECT_IMAGE_USR_NO_ROOT)))) + return -ENXIO; /* Require a root fs or at least a /usr/ fs (the latter is subject to a flag of its own) */ if ((flags & DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY) == 0) { - r = mount_partition(m->partitions + PARTITION_ROOT, where, NULL, uid_shift, flags); + + /* First mount the root fs. If there's none we use a tmpfs. */ + if (m->partitions[PARTITION_ROOT].found) + r = mount_partition(m->partitions + PARTITION_ROOT, where, NULL, uid_shift, flags); + else + r = mount_root_tmpfs(where, uid_shift, flags); if (r < 0) return r; - } - if ((flags & DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY) == 0) { /* For us mounting root always means mounting /usr as well */ r = mount_partition(m->partitions + PARTITION_USR, where, "/usr", uid_shift, flags); if (r < 0) @@ -2305,7 +2356,14 @@ int dissected_image_acquire_metadata(DissectedImage *m) { if (r == 0) { error_pipe[0] = safe_close(error_pipe[0]); - r = dissected_image_mount(m, t, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_VALIDATE_OS); + r = dissected_image_mount( + m, + t, + UID_INVALID, + DISSECT_IMAGE_READ_ONLY| + DISSECT_IMAGE_MOUNT_ROOT_ONLY| + DISSECT_IMAGE_VALIDATE_OS| + DISSECT_IMAGE_USR_NO_ROOT); if (r < 0) { /* Let parent know the error */ (void) write(error_pipe[1], &r, sizeof(r)); diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index ddadda1c0cd..a7c9af0a2bc 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -87,6 +87,7 @@ typedef enum DissectImageFlags { 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 */ } DissectImageFlags; struct DissectedImage { -- 2.47.3