]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/dissect-image.c
strv: make iterator in STRV_FOREACH() declaread in the loop
[thirdparty/systemd.git] / src / shared / dissect-image.c
index 99a2f62e4a7eb5ac61f67e4da4de608215167abd..8a3da1f650810c4e0a60c0ea736b79c689510c1c 100644 (file)
 #include <sys/wait.h>
 #include <sysexits.h>
 
+#if HAVE_OPENSSL
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#endif
+
 #include "sd-device.h"
 #include "sd-id128.h"
 
@@ -18,6 +24,8 @@
 #include "ask-password-api.h"
 #include "blkid-util.h"
 #include "blockdev-util.h"
+#include "chase-symlinks.h"
+#include "conf-files.h"
 #include "copy.h"
 #include "cryptsetup-util.h"
 #include "def.h"
@@ -27,6 +35,7 @@
 #include "dissect-image.h"
 #include "dm-util.h"
 #include "env-file.h"
+#include "env-util.h"
 #include "extension-release.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "hostname-setup.h"
 #include "id128-util.h"
 #include "import-util.h"
-#include "mkdir.h"
+#include "io-util.h"
+#include "mkdir-label.h"
 #include "mount-util.h"
 #include "mountpoint-util.h"
 #include "namespace-util.h"
 #include "nulstr-util.h"
+#include "openssl-util.h"
 #include "os-util.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -536,7 +547,7 @@ static int device_wait_for_initialization_harder(
 
                 (void) sd_device_get_sysname(device, &sn);
                 log_device_debug(device,
-                                 "Waiting for device '%s' to initialize for %s.", strna(sn), FORMAT_TIMESPAN(left, 0));
+                                 "Will wait up to %s for '%s' to initialize…", FORMAT_TIMESPAN(left, 0), strna(sn));
         }
 
         if (left != USEC_INFINITY)
@@ -616,12 +627,8 @@ int dissect_image(
                 DissectedImage **ret) {
 
 #if HAVE_BLKID
-#ifdef GPT_ROOT_NATIVE
         sd_id128_t root_uuid = SD_ID128_NULL, root_verity_uuid = SD_ID128_NULL;
-#endif
-#ifdef GPT_USR_NATIVE
         sd_id128_t usr_uuid = SD_ID128_NULL, usr_verity_uuid = SD_ID128_NULL;
-#endif
         bool is_gpt, is_mbr, multiple_generic = false,
                 generic_rw = false,  /* initialize to appease gcc */
                 generic_growfs = false;
@@ -638,7 +645,10 @@ int dissect_image(
 
         assert(fd >= 0);
         assert(ret);
+        assert(!verity || verity->designator < 0 || IN_SET(verity->designator, PARTITION_ROOT, PARTITION_USR));
         assert(!verity || verity->root_hash || verity->root_hash_size == 0);
+        assert(!verity || verity->root_hash_sig || verity->root_hash_sig_size == 0);
+        assert(!verity || (verity->root_hash || !verity->root_hash_sig));
         assert(!((flags & DISSECT_IMAGE_GPT_ONLY) && (flags & DISSECT_IMAGE_NO_PARTITION_TABLE)));
 
         /* Probes a disk image, and returns information about what it found in *ret.
@@ -668,19 +678,13 @@ int dissect_image(
 
                 /* If the verity data declares it's for the /usr partition, then search for that, in all
                  * other cases assume it's for the root partition. */
-#ifdef GPT_USR_NATIVE
                 if (verity->designator == PARTITION_USR) {
                         usr_uuid = fsuuid;
                         usr_verity_uuid = vuuid;
                 } else {
-#endif
-#ifdef GPT_ROOT_NATIVE
                         root_uuid = fsuuid;
                         root_verity_uuid = vuuid;
-#endif
-#ifdef GPT_USR_NATIVE
                 }
-#endif
         }
 
         if (fstat(fd, &st) < 0)
@@ -735,10 +739,14 @@ int dissect_image(
         if (r != 0)
                 return errno_or_else(EIO);
 
-        m = new0(DissectedImage, 1);
+        m = new(DissectedImage, 1);
         if (!m)
                 return -ENOMEM;
 
+        *m = (DissectedImage) {
+                .has_init_system = -1,
+        };
+
         r = sd_device_get_sysname(d, &sysname);
         if (r < 0)
                 return log_debug_errno(r, "Failed to get device sysname: %m");
@@ -797,8 +805,16 @@ int dissect_image(
                                 return -ENOMEM;
 
                         m->single_file_system = true;
-                        m->verity = verity && verity->root_hash && verity->data_path && (verity->designator < 0 || verity->designator == PARTITION_ROOT);
-                        m->can_verity = verity && verity->data_path;
+                        m->encrypted = streq_ptr(fstype, "crypto_LUKS");
+
+                        m->has_verity = verity && verity->data_path;
+                        m->verity_ready = m->has_verity &&
+                                verity->root_hash &&
+                                (verity->designator < 0 || verity->designator == PARTITION_ROOT);
+
+                        m->has_verity_sig = false; /* signature not embedded, must be specified */
+                        m->verity_sig_ready = m->verity_ready &&
+                                verity->root_hash_sig;
 
                         options = mount_options_from_designator(mount_options, PARTITION_ROOT);
                         if (options) {
@@ -809,16 +825,16 @@ int dissect_image(
 
                         m->partitions[PARTITION_ROOT] = (DissectedPartition) {
                                 .found = true,
-                                .rw = !m->verity,
+                                .rw = !m->verity_ready && !fstype_is_ro(fstype),
                                 .partno = -1,
                                 .architecture = _ARCHITECTURE_INVALID,
                                 .fstype = TAKE_PTR(t),
                                 .node = TAKE_PTR(n),
                                 .mount_options = TAKE_PTR(o),
+                                .offset = 0,
+                                .size = UINT64_MAX,
                         };
 
-                        m->encrypted = streq_ptr(fstype, "crypto_LUKS");
-
                         *ret = TAKE_PTR(m);
                         return 0;
                 }
@@ -834,6 +850,10 @@ int dissect_image(
         if (!is_gpt && ((flags & DISSECT_IMAGE_GPT_ONLY) || !is_mbr))
                 return -ENOPKG;
 
+        /* We support external verity data partitions only if the image has no partition table */
+        if (verity && verity->data_path)
+                return -EBADR;
+
         /* Safety check: refuse block devices that carry a partition table but for which the kernel doesn't
          * do partition scanning. */
         r = blockdev_partscan_enabled(fd);
@@ -856,6 +876,7 @@ int dissect_image(
         for (int i = 0; i < n_partitions; i++) {
                 _cleanup_(sd_device_unrefp) sd_device *q = NULL;
                 unsigned long long pflags;
+                blkid_loff_t start, size;
                 blkid_partition pp;
                 const char *node;
                 int nr;
@@ -880,6 +901,20 @@ int dissect_image(
                 if (nr < 0)
                         return errno_or_else(EIO);
 
+                errno = 0;
+                start = blkid_partition_get_start(pp);
+                if (start < 0)
+                        return errno_or_else(EIO);
+
+                assert((uint64_t) start < UINT64_MAX/512);
+
+                errno = 0;
+                size = blkid_partition_get_size(pp);
+                if (size < 0)
+                        return errno_or_else(EIO);
+
+                assert((uint64_t) size < UINT64_MAX/512);
+
                 if (is_gpt) {
                         PartitionDesignator designator = _PARTITION_DESIGNATOR_INVALID;
                         int architecture = _ARCHITECTURE_INVALID;
@@ -946,9 +981,8 @@ int dissect_image(
                                 designator = PARTITION_XBOOTLDR;
                                 rw = !(pflags & GPT_FLAG_READ_ONLY);
                                 growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
-                        }
-#ifdef GPT_ROOT_NATIVE
-                        else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
+
+                        } else if (gpt_partition_type_is_root(type_id)) {
 
                                 check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY|GPT_FLAG_GROWFS);
 
@@ -959,68 +993,58 @@ int dissect_image(
                                 if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id))
                                         continue;
 
-                                designator = PARTITION_ROOT;
-                                architecture = native_architecture();
+                                assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+                                designator = PARTITION_ROOT_OF_ARCH(architecture);
                                 rw = !(pflags & GPT_FLAG_READ_ONLY);
                                 growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
 
-                        } else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE_VERITY)) {
+                        } else if (gpt_partition_type_is_root_verity(type_id)) {
 
                                 check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
 
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
-                                m->can_verity = true;
+                                m->has_verity = true;
 
-                                /* Ignore verity unless a root hash is specified */
-                                if (sd_id128_is_null(root_verity_uuid) || !sd_id128_equal(root_verity_uuid, id))
+                                /* If no verity configuration is specified, then don't do verity */
+                                if (!verity)
                                         continue;
-
-                                designator = PARTITION_ROOT_VERITY;
-                                fstype = "DM_verity_hash";
-                                architecture = native_architecture();
-                                rw = false;
-                        }
-#endif
-#ifdef GPT_ROOT_SECONDARY
-                        else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) {
-
-                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY|GPT_FLAG_GROWFS);
-
-                                if (pflags & GPT_FLAG_NO_AUTO)
+                                if (verity->designator >= 0 && verity->designator != PARTITION_ROOT)
                                         continue;
 
-                                /* If a root ID is specified, ignore everything but the root id */
-                                if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id))
+                                /* If root hash is specified, then ignore everything but the root id */
+                                if (!sd_id128_is_null(root_verity_uuid) && !sd_id128_equal(root_verity_uuid, id))
                                         continue;
 
-                                designator = PARTITION_ROOT_SECONDARY;
-                                architecture = SECONDARY_ARCHITECTURE;
-                                rw = !(pflags & GPT_FLAG_READ_ONLY);
-                                growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
+                                assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+                                designator = PARTITION_VERITY_OF(PARTITION_ROOT_OF_ARCH(architecture));
+                                fstype = "DM_verity_hash";
+                                rw = false;
 
-                        } else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY_VERITY)) {
+                        } else if (gpt_partition_type_is_root_verity_sig(type_id)) {
 
                                 check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
 
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
-                                m->can_verity = true;
+                                m->has_verity_sig = true;
 
-                                /* Ignore verity unless root has is specified */
-                                if (sd_id128_is_null(root_verity_uuid) || !sd_id128_equal(root_verity_uuid, id))
+                                /* If root hash is specified explicitly, then ignore any embedded signature */
+                                if (!verity)
+                                        continue;
+                                if (verity->designator >= 0 && verity->designator != PARTITION_ROOT)
+                                        continue;
+                                if (verity->root_hash)
                                         continue;
 
-                                designator = PARTITION_ROOT_SECONDARY_VERITY;
-                                fstype = "DM_verity_hash";
-                                architecture = SECONDARY_ARCHITECTURE;
+                                assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+                                designator = PARTITION_VERITY_SIG_OF(PARTITION_ROOT_OF_ARCH(architecture));
+                                fstype = "verity_hash_signature";
                                 rw = false;
-                        }
-#endif
-#ifdef GPT_USR_NATIVE
-                        else if (sd_id128_equal(type_id, GPT_USR_NATIVE)) {
+
+                        } else if (gpt_partition_type_is_usr(type_id)) {
 
                                 check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY|GPT_FLAG_GROWFS);
 
@@ -1031,67 +1055,57 @@ int dissect_image(
                                 if (!sd_id128_is_null(usr_uuid) && !sd_id128_equal(usr_uuid, id))
                                         continue;
 
-                                designator = PARTITION_USR;
-                                architecture = native_architecture();
+                                assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+                                designator = PARTITION_USR_OF_ARCH(architecture);
                                 rw = !(pflags & GPT_FLAG_READ_ONLY);
                                 growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
 
-                        } else if (sd_id128_equal(type_id, GPT_USR_NATIVE_VERITY)) {
+                        } else if (gpt_partition_type_is_usr_verity(type_id)) {
 
                                 check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
 
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
-                                m->can_verity = true;
+                                m->has_verity = true;
 
-                                /* Ignore verity unless a usr hash is specified */
-                                if (sd_id128_is_null(usr_verity_uuid) || !sd_id128_equal(usr_verity_uuid, id))
+                                if (!verity)
                                         continue;
-
-                                designator = PARTITION_USR_VERITY;
-                                fstype = "DM_verity_hash";
-                                architecture = native_architecture();
-                                rw = false;
-                        }
-#endif
-#ifdef GPT_USR_SECONDARY
-                        else if (sd_id128_equal(type_id, GPT_USR_SECONDARY)) {
-
-                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY|GPT_FLAG_GROWFS);
-
-                                if (pflags & GPT_FLAG_NO_AUTO)
+                                if (verity->designator >= 0 && verity->designator != PARTITION_USR)
                                         continue;
 
-                                /* If a usr ID is specified, ignore everything but the usr id */
-                                if (!sd_id128_is_null(usr_uuid) && !sd_id128_equal(usr_uuid, id))
+                                /* If usr hash is specified, then ignore everything but the usr id */
+                                if (!sd_id128_is_null(usr_verity_uuid) && !sd_id128_equal(usr_verity_uuid, id))
                                         continue;
 
-                                designator = PARTITION_USR_SECONDARY;
-                                architecture = SECONDARY_ARCHITECTURE;
-                                rw = !(pflags & GPT_FLAG_READ_ONLY);
-                                growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
+                                assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+                                designator = PARTITION_VERITY_OF(PARTITION_USR_OF_ARCH(architecture));
+                                fstype = "DM_verity_hash";
+                                rw = false;
 
-                        } else if (sd_id128_equal(type_id, GPT_USR_SECONDARY_VERITY)) {
+                        } else if (gpt_partition_type_is_usr_verity_sig(type_id)) {
 
                                 check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
 
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
-                                m->can_verity = true;
+                                m->has_verity_sig = true;
 
-                                /* Ignore verity unless usr has is specified */
-                                if (sd_id128_is_null(usr_verity_uuid) || !sd_id128_equal(usr_verity_uuid, id))
+                                /* If usr hash is specified explicitly, then ignore any embedded signature */
+                                if (!verity)
+                                        continue;
+                                if (verity->designator >= 0 && verity->designator != PARTITION_USR)
+                                        continue;
+                                if (verity->root_hash)
                                         continue;
 
-                                designator = PARTITION_USR_SECONDARY_VERITY;
-                                fstype = "DM_verity_hash";
-                                architecture = SECONDARY_ARCHITECTURE;
+                                assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+                                designator = PARTITION_VERITY_SIG_OF(PARTITION_USR_OF_ARCH(architecture));
+                                fstype = "verity_hash_signature";
                                 rw = false;
-                        }
-#endif
-                        else if (sd_id128_equal(type_id, GPT_SWAP)) {
+
+                        } else if (sd_id128_equal(type_id, GPT_SWAP)) {
 
                                 check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO);
 
@@ -1214,6 +1228,8 @@ int dissect_image(
                                         .label = TAKE_PTR(l),
                                         .uuid = id,
                                         .mount_options = TAKE_PTR(o),
+                                        .offset = (uint64_t) start * 512,
+                                        .size = (uint64_t) size * 512,
                                 };
                         }
 
@@ -1272,6 +1288,8 @@ int dissect_image(
                                         .node = TAKE_PTR(n),
                                         .uuid = id,
                                         .mount_options = TAKE_PTR(o),
+                                        .offset = (uint64_t) start * 512,
+                                        .size = (uint64_t) size * 512,
                                 };
 
                                 break;
@@ -1280,56 +1298,153 @@ int dissect_image(
         }
 
         if (m->partitions[PARTITION_ROOT].found) {
-                /* If we found the primary arch, then invalidate the secondary arch to avoid any ambiguities,
-                 * since we never want to mount the secondary arch in this case. */
+                /* If we found the primary arch, then invalidate the secondary and other arch to avoid any
+                 * ambiguities, since we never want to mount the secondary or other arch in this case. */
                 m->partitions[PARTITION_ROOT_SECONDARY].found = false;
                 m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found = false;
+                m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG].found = false;
                 m->partitions[PARTITION_USR_SECONDARY].found = false;
                 m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false;
+                m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found = false;
 
-        } else if (m->partitions[PARTITION_ROOT_VERITY].found)
+                m->partitions[PARTITION_ROOT_OTHER].found = false;
+                m->partitions[PARTITION_ROOT_OTHER_VERITY].found = false;
+                m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG].found = false;
+                m->partitions[PARTITION_USR_OTHER].found = false;
+                m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
+                m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
+
+        } else if (m->partitions[PARTITION_ROOT_VERITY].found ||
+                   m->partitions[PARTITION_ROOT_VERITY_SIG].found)
                 return -EADDRNOTAVAIL; /* Verity found but no matching rootfs? Something is off, refuse. */
 
         else if (m->partitions[PARTITION_ROOT_SECONDARY].found) {
 
                 /* No root partition found but there's one for the secondary architecture? Then upgrade
-                 * secondary arch to first */
+                 * secondary arch to first and invalidate the other arch. */
+
+                log_debug("No root partition found of the native architecture, falling back to a root "
+                          "partition of the secondary architecture.");
 
                 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_ROOT_VERITY_SIG] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG];
+                zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG]);
 
                 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_ROOT_SECONDARY_VERITY].found)
+                m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG];
+                zero(m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG]);
+
+                m->partitions[PARTITION_ROOT_OTHER].found = false;
+                m->partitions[PARTITION_ROOT_OTHER_VERITY].found = false;
+                m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG].found = false;
+                m->partitions[PARTITION_USR_OTHER].found = false;
+                m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
+                m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
+
+        } else if (m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found ||
+                   m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG].found)
                 return -EADDRNOTAVAIL; /* as above */
 
-        else if (m->partitions[PARTITION_USR].found) {
+        else if (m->partitions[PARTITION_ROOT_OTHER].found) {
+
+                /* No root or secondary partition found but there's one for another architecture? Then
+                 * upgrade the other architecture to first. */
+
+                log_debug("No root partition found of the native architecture or the secondary architecture, "
+                          "falling back to a root partition of a non-native architecture (%s).",
+                          architecture_to_string(m->partitions[PARTITION_ROOT_OTHER].architecture));
 
-                /* Invalidate secondary arch /usr/ if we found the primary arch */
+                m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_OTHER];
+                zero(m->partitions[PARTITION_ROOT_OTHER]);
+                m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_OTHER_VERITY];
+                zero(m->partitions[PARTITION_ROOT_OTHER_VERITY]);
+                m->partitions[PARTITION_ROOT_VERITY_SIG] = m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG];
+                zero(m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG]);
+
+                m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_OTHER];
+                zero(m->partitions[PARTITION_USR_OTHER]);
+                m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_OTHER_VERITY];
+                zero(m->partitions[PARTITION_USR_OTHER_VERITY]);
+                m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_OTHER_VERITY_SIG];
+                zero(m->partitions[PARTITION_USR_OTHER_VERITY_SIG]);
+        }
+
+        /* Hmm, we found a signature partition but no Verity data? Something is off. */
+        if (m->partitions[PARTITION_ROOT_VERITY_SIG].found && !m->partitions[PARTITION_ROOT_VERITY].found)
+                return -EADDRNOTAVAIL;
+
+        if (m->partitions[PARTITION_USR].found) {
+                /* Invalidate secondary and other arch /usr/ if we found the primary arch */
                 m->partitions[PARTITION_USR_SECONDARY].found = false;
                 m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false;
+                m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found = false;
+
+                m->partitions[PARTITION_USR_OTHER].found = false;
+                m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
+                m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
 
-        } else if (m->partitions[PARTITION_USR_VERITY].found)
+        } else if (m->partitions[PARTITION_USR_VERITY].found ||
+                   m->partitions[PARTITION_USR_VERITY_SIG].found)
                 return -EADDRNOTAVAIL; /* as above */
 
         else if (m->partitions[PARTITION_USR_SECONDARY].found) {
 
+                log_debug("No usr partition found of the native architecture, falling back to a usr "
+                          "partition of the secondary architecture.");
+
                 /* 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]);
+                m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG];
+                zero(m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG]);
+
+                m->partitions[PARTITION_USR_OTHER].found = false;
+                m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
+                m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
 
-        } else if (m->partitions[PARTITION_USR_SECONDARY_VERITY].found)
+        } else if (m->partitions[PARTITION_USR_SECONDARY_VERITY].found ||
+                   m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found)
                 return -EADDRNOTAVAIL; /* as above */
 
-        else if ((flags & DISSECT_IMAGE_GENERIC_ROOT) &&
-                 (!verity || !verity->root_hash)) {
+        else if (m->partitions[PARTITION_USR_OTHER].found) {
+
+                log_debug("No usr partition found of the native architecture or the secondary architecture, "
+                          "falling back to a usr partition of a non-native architecture (%s).",
+                          architecture_to_string(m->partitions[PARTITION_ROOT_OTHER].architecture));
+
+                /* Upgrade other arch to primary */
+                m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_OTHER];
+                zero(m->partitions[PARTITION_USR_OTHER]);
+                m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_OTHER_VERITY];
+                zero(m->partitions[PARTITION_USR_OTHER_VERITY]);
+                m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_OTHER_VERITY_SIG];
+                zero(m->partitions[PARTITION_USR_OTHER_VERITY_SIG]);
+        }
+
+        /* Hmm, we found a signature partition but no Verity data? Something is off. */
+        if (m->partitions[PARTITION_USR_VERITY_SIG].found && !m->partitions[PARTITION_USR_VERITY].found)
+                return -EADDRNOTAVAIL;
+
+        /* If root and /usr are combined then insist that the architecture matches */
+        if (m->partitions[PARTITION_ROOT].found &&
+            m->partitions[PARTITION_USR].found &&
+            (m->partitions[PARTITION_ROOT].architecture >= 0 &&
+             m->partitions[PARTITION_USR].architecture >= 0 &&
+             m->partitions[PARTITION_ROOT].architecture != m->partitions[PARTITION_USR].architecture))
+                return -EADDRNOTAVAIL;
+
+        if (!m->partitions[PARTITION_ROOT].found &&
+            !m->partitions[PARTITION_USR].found &&
+            (flags & DISSECT_IMAGE_GENERIC_ROOT) &&
+            (!verity || !verity->root_hash || verity->designator != PARTITION_USR)) {
 
                 /* OK, we found nothing usable, then check if there's a single generic one distro, and use
                  * that. If the root hash was set however, then we won't fall back to a generic node, because
@@ -1362,6 +1477,8 @@ int dissect_image(
                                 .node = TAKE_PTR(generic_node),
                                 .uuid = generic_uuid,
                                 .mount_options = TAKE_PTR(o),
+                                .offset = UINT64_MAX,
+                                .size = UINT64_MAX,
                         };
                 }
         }
@@ -1371,30 +1488,53 @@ int dissect_image(
             !(m->partitions[PARTITION_ROOT].found || (m->partitions[PARTITION_USR].found && FLAGS_SET(flags, DISSECT_IMAGE_USR_NO_ROOT))))
                 return -ENXIO;
 
-        /* Refuse if we found a verity partition for /usr but no matching file system partition */
-        if (!m->partitions[PARTITION_USR].found && m->partitions[PARTITION_USR_VERITY].found)
-                return -EADDRNOTAVAIL;
+        if (m->partitions[PARTITION_ROOT_VERITY].found) {
+                /* We only support one verity partition per image, i.e. can't do for both /usr and root fs */
+                if (m->partitions[PARTITION_USR_VERITY].found)
+                        return -ENOTUNIQ;
 
-        /* Combinations of verity /usr with verity-less root is OK, but the reverse is not */
-        if (m->partitions[PARTITION_ROOT_VERITY].found && m->partitions[PARTITION_USR].found && !m->partitions[PARTITION_USR_VERITY].found)
-                return -EADDRNOTAVAIL;
+                /* We don't support verity enabled root with a split out /usr. Neither with nor without
+                 * verity there. (Note that we do support verity-less root with verity-full /usr, though.) */
+                if (m->partitions[PARTITION_USR].found)
+                        return -EADDRNOTAVAIL;
+        }
 
-        if (verity && verity->root_hash) {
-                if (verity->designator < 0 || verity->designator == PARTITION_ROOT) {
-                        if (!m->partitions[PARTITION_ROOT_VERITY].found || !m->partitions[PARTITION_ROOT].found)
-                                return -EADDRNOTAVAIL;
+        if (verity) {
+                /* If a verity designator is specified, then insist that the matching partition exists */
+                if (verity->designator >= 0 && !m->partitions[verity->designator].found)
+                        return -EADDRNOTAVAIL;
 
-                        /* If we found a verity setup, then the root partition is necessarily read-only. */
-                        m->partitions[PARTITION_ROOT].rw = false;
-                        m->verity = true;
-                }
+                if (verity->root_hash) {
+                        /* If we have an explicit root hash and found the partitions for it, then we are ready to use
+                         * Verity, set things up for it */
 
-                if (verity->designator == PARTITION_USR) {
-                        if (!m->partitions[PARTITION_USR_VERITY].found || !m->partitions[PARTITION_USR].found)
-                                return -EADDRNOTAVAIL;
+                        if (verity->designator < 0 || verity->designator == PARTITION_ROOT) {
+                                if (!m->partitions[PARTITION_ROOT_VERITY].found || !m->partitions[PARTITION_ROOT].found)
+                                        return -EADDRNOTAVAIL;
+
+                                /* If we found a verity setup, then the root partition is necessarily read-only. */
+                                m->partitions[PARTITION_ROOT].rw = false;
+                                m->verity_ready = true;
 
-                        m->partitions[PARTITION_USR].rw = false;
-                        m->verity = true;
+                        } else {
+                                assert(verity->designator == PARTITION_USR);
+
+                                if (!m->partitions[PARTITION_USR_VERITY].found || !m->partitions[PARTITION_USR].found)
+                                        return -EADDRNOTAVAIL;
+
+                                m->partitions[PARTITION_USR].rw = false;
+                                m->verity_ready = true;
+                        }
+
+                        if (m->verity_ready)
+                                m->verity_sig_ready = verity->root_hash_sig;
+
+                } else if (m->partitions[verity->designator == PARTITION_USR ? PARTITION_USR_VERITY_SIG : PARTITION_ROOT_VERITY_SIG].found) {
+
+                        /* If we found an embedded signature partition, we are ready, too. */
+
+                        m->verity_ready = m->verity_sig_ready = true;
+                        m->partitions[verity->designator == PARTITION_USR ? PARTITION_USR : PARTITION_ROOT].rw = false;
                 }
         }
 
@@ -1667,7 +1807,7 @@ static int mount_partition(
                 (void) fs_grow(node, p);
 
         if (remap_uid_gid) {
-                r = remount_idmap(p, uid_shift, uid_range);
+                r = remount_idmap(p, uid_shift, uid_range, REMOUNT_IDMAP_HOST_ROOT);
                 if (r < 0)
                         return r;
         }
@@ -1742,17 +1882,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;
                 }
         }
 
@@ -2048,6 +2199,145 @@ static inline char* dm_deferred_remove_clean(char *name) {
 }
 DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean);
 
+static int validate_signature_userspace(const VeritySettings *verity) {
+#if HAVE_OPENSSL
+        _cleanup_(sk_X509_free_allp) STACK_OF(X509) *sk = NULL;
+        _cleanup_strv_free_ char **certs = NULL;
+        _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
+        _cleanup_free_ char *s = NULL;
+        _cleanup_(BIO_freep) BIO *bio = NULL; /* 'bio' must be freed first, 's' second, hence keep this order
+                                               * of declaration in place, please */
+        const unsigned char *d;
+        int r;
+
+        assert(verity);
+        assert(verity->root_hash);
+        assert(verity->root_hash_sig);
+
+        /* Because installing a signature certificate into the kernel chain is so messy, let's optionally do
+         * userspace validation. */
+
+        r = conf_files_list_nulstr(&certs, ".crt", NULL, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d"));
+        if (r < 0)
+                return log_debug_errno(r, "Failed to enumerate certificates: %m");
+        if (strv_isempty(certs)) {
+                log_debug("No userspace dm-verity certificates found.");
+                return 0;
+        }
+
+        d = verity->root_hash_sig;
+        p7 = d2i_PKCS7(NULL, &d, (long) verity->root_hash_sig_size);
+        if (!p7)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PKCS7 DER signature data.");
+
+        s = hexmem(verity->root_hash, verity->root_hash_size);
+        if (!s)
+                return log_oom_debug();
+
+        bio = BIO_new_mem_buf(s, strlen(s));
+        if (!bio)
+                return log_oom_debug();
+
+        sk = sk_X509_new_null();
+        if (!sk)
+                return log_oom_debug();
+
+        STRV_FOREACH(i, certs) {
+                _cleanup_(X509_freep) X509 *c = NULL;
+                _cleanup_fclose_ FILE *f = NULL;
+
+                f = fopen(*i, "re");
+                if (!f) {
+                        log_debug_errno(errno, "Failed to open '%s', ignoring: %m", *i);
+                        continue;
+                }
+
+                c = PEM_read_X509(f, NULL, NULL, NULL);
+                if (!c) {
+                        log_debug("Failed to load X509 certificate '%s', ignoring.", *i);
+                        continue;
+                }
+
+                if (sk_X509_push(sk, c) == 0)
+                        return log_oom_debug();
+
+                TAKE_PTR(c);
+        }
+
+        r = PKCS7_verify(p7, sk, NULL, bio, NULL, PKCS7_NOINTERN|PKCS7_NOVERIFY);
+        if (r)
+                log_debug("Userspace PKCS#7 validation succeeded.");
+        else
+                log_debug("Userspace PKCS#7 validation failed: %s", ERR_error_string(ERR_get_error(), NULL));
+
+        return r;
+#else
+        log_debug("Not doing client-side validation of dm-verity root hash signatures, OpenSSL support disabled.");
+        return 0;
+#endif
+}
+
+static int do_crypt_activate_verity(
+                struct crypt_device *cd,
+                const char *name,
+                const VeritySettings *verity) {
+
+        bool check_signature;
+        int r;
+
+        assert(cd);
+        assert(name);
+        assert(verity);
+
+        if (verity->root_hash_sig) {
+                r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIGNATURE");
+                if (r < 0 && r != -ENXIO)
+                        log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIGNATURE");
+
+                check_signature = r != 0;
+        } else
+                check_signature = false;
+
+        if (check_signature) {
+
+#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+                /* First, if we have support for signed keys in the kernel, then try that first. */
+                r = sym_crypt_activate_by_signed_key(
+                                cd,
+                                name,
+                                verity->root_hash,
+                                verity->root_hash_size,
+                                verity->root_hash_sig,
+                                verity->root_hash_sig_size,
+                                CRYPT_ACTIVATE_READONLY);
+                if (r >= 0)
+                        return r;
+
+                log_debug("Validation of dm-verity signature failed via the kernel, trying userspace validation instead.");
+#else
+                log_debug("Activation of verity device with signature requested, but not supported via the kernel by %s due to missing crypt_activate_by_signed_key(), trying userspace validation instead.",
+                          program_invocation_short_name);
+#endif
+
+                /* So this didn't work via the kernel, then let's try userspace validation instead. If that
+                 * works we'll try to activate without telling the kernel the signature. */
+
+                r = validate_signature_userspace(verity);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY),
+                                               "Activation of signed Verity volume worked neither via the kernel nor in userspace, can't activate.");
+        }
+
+        return sym_crypt_activate_by_volume_key(
+                        cd,
+                        name,
+                        verity->root_hash,
+                        verity->root_hash_size,
+                        CRYPT_ACTIVATE_READONLY);
+}
+
 static int verity_partition(
                 PartitionDesignator designator,
                 DissectedPartition *m,
@@ -2119,27 +2409,8 @@ static int verity_partition(
          * In case of ENODEV/ENOENT, which can happen if another process is activating at the exact same time,
          * retry a few times before giving up. */
         for (unsigned i = 0; i < N_DEVICE_NODE_LIST_ATTEMPTS; i++) {
-                if (verity->root_hash_sig) {
-#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
-                        r = sym_crypt_activate_by_signed_key(
-                                        cd,
-                                        name,
-                                        verity->root_hash,
-                                        verity->root_hash_size,
-                                        verity->root_hash_sig,
-                                        verity->root_hash_sig_size,
-                                        CRYPT_ACTIVATE_READONLY);
-#else
-                        r = log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                            "Activation of verity device with signature requested, but not supported by %s due to missing crypt_activate_by_signed_key().", program_invocation_short_name);
-#endif
-                } else
-                        r = sym_crypt_activate_by_volume_key(
-                                        cd,
-                                        name,
-                                        verity->root_hash,
-                                        verity->root_hash_size,
-                                        CRYPT_ACTIVATE_READONLY);
+
+                r = do_crypt_activate_verity(cd, name, verity);
                 /* libdevmapper can return EINVAL when the device is already in the activation stage.
                  * There's no way to distinguish this situation from a genuine error due to invalid
                  * parameters, so immediately fall back to activating the device with a unique name.
@@ -2242,7 +2513,7 @@ int dissected_image_decrypt(
         if (verity && verity->root_hash && verity->root_hash_size < sizeof(sd_id128_t))
                 return -EINVAL;
 
-        if (!m->encrypted && !m->verity) {
+        if (!m->encrypted && !m->verity_ready) {
                 *ret = NULL;
                 return 0;
         }
@@ -2398,6 +2669,12 @@ int verity_settings_load(
         if (is_device_path(image))
                 return 0;
 
+        r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIDECAR");
+        if (r < 0 && r != -ENXIO)
+                log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIDECAR, ignoring: %m");
+        if (r == 0)
+                return 0;
+
         designator = verity->designator;
 
         /* We only fill in what isn't already filled in */
@@ -2418,7 +2695,7 @@ int verity_settings_load(
                          * that doesn't exist for /usr */
 
                         if (designator < 0 || designator == PARTITION_ROOT) {
-                                r = getxattr_malloc(image, "user.verity.roothash", &text, true);
+                                r = getxattr_malloc(image, "user.verity.roothash", &text);
                                 if (r < 0) {
                                         _cleanup_free_ char *p = NULL;
 
@@ -2447,7 +2724,7 @@ int verity_settings_load(
                                  * `usrhash`, because `usrroothash` or `rootusrhash` would just be too
                                  * confusing. We thus drop the reference to the root of the Merkle tree, and
                                  * just indicate which file system it's about. */
-                                r = getxattr_malloc(image, "user.verity.usrhash", &text, true);
+                                r = getxattr_malloc(image, "user.verity.usrhash", &text);
                                 if (r < 0) {
                                         _cleanup_free_ char *p = NULL;
 
@@ -2554,7 +2831,110 @@ int verity_settings_load(
         return 1;
 }
 
-int dissected_image_acquire_metadata(DissectedImage *m) {
+int dissected_image_load_verity_sig_partition(
+                DissectedImage *m,
+                int fd,
+                VeritySettings *verity) {
+
+        _cleanup_free_ void *root_hash = NULL, *root_hash_sig = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        size_t root_hash_size, root_hash_sig_size;
+        _cleanup_free_ char *buf = NULL;
+        PartitionDesignator d;
+        DissectedPartition *p;
+        JsonVariant *rh, *sig;
+        ssize_t n;
+        char *e;
+        int r;
+
+        assert(m);
+        assert(fd >= 0);
+        assert(verity);
+
+        if (verity->root_hash && verity->root_hash_sig) /* Already loaded? */
+                return 0;
+
+        r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_EMBEDDED");
+        if (r < 0 && r != -ENXIO)
+                log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_EMBEDDED, ignoring: %m");
+        if (r == 0)
+                return 0;
+
+        d = PARTITION_VERITY_SIG_OF(verity->designator < 0 ? PARTITION_ROOT : verity->designator);
+        assert(d >= 0);
+
+        p = m->partitions + d;
+        if (!p->found)
+                return 0;
+        if (p->offset == UINT64_MAX || p->size == UINT64_MAX)
+                return -EINVAL;
+
+        if (p->size > 4*1024*1024) /* Signature data cannot possible be larger than 4M, refuse that */
+                return -EFBIG;
+
+        buf = new(char, p->size+1);
+        if (!buf)
+                return -ENOMEM;
+
+        n = pread(fd, buf, p->size, p->offset);
+        if (n < 0)
+                return -ENOMEM;
+        if ((uint64_t) n != p->size)
+                return -EIO;
+
+        e = memchr(buf, 0, p->size);
+        if (e) {
+                /* If we found a NUL byte then the rest of the data must be NUL too */
+                if (!memeqzero(e, p->size - (e - buf)))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature data contains embedded NUL byte.");
+        } else
+                buf[p->size] = 0;
+
+        r = json_parse(buf, 0, &v, NULL, NULL);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse signature JSON data: %m");
+
+        rh = json_variant_by_key(v, "rootHash");
+        if (!rh)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature JSON object lacks 'rootHash' field.");
+        if (!json_variant_is_string(rh))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'rootHash' field of signature JSON object is not a string.");
+
+        r = unhexmem(json_variant_string(rh), SIZE_MAX, &root_hash, &root_hash_size);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse root hash field: %m");
+
+        /* Check if specified root hash matches if it is specified */
+        if (verity->root_hash &&
+            memcmp_nn(verity->root_hash, verity->root_hash_size, root_hash, root_hash_size) != 0) {
+                _cleanup_free_ char *a = NULL, *b = NULL;
+
+                a = hexmem(root_hash, root_hash_size);
+                b = hexmem(verity->root_hash, verity->root_hash_size);
+
+                return log_debug_errno(r, "Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(a), strna(b));
+        }
+
+        sig = json_variant_by_key(v, "signature");
+        if (!sig)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature JSON object lacks 'signature' field.");
+        if (!json_variant_is_string(sig))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'signature' field of signature JSON object is not a string.");
+
+        r = unbase64mem(json_variant_string(sig), SIZE_MAX, &root_hash_sig, &root_hash_sig_size);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse signature field: %m");
+
+        free_and_replace(verity->root_hash, root_hash);
+        verity->root_hash_size = root_hash_size;
+
+        free_and_replace(verity->root_hash_sig, root_hash_sig);
+        verity->root_hash_sig_size = root_hash_sig_size;
+
+        return 1;
+}
+
+int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags) {
 
         enum {
                 META_HOSTNAME,
@@ -2562,6 +2942,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
                 META_MACHINE_INFO,
                 META_OS_RELEASE,
                 META_EXTENSION_RELEASE,
+                META_HAS_INIT_SYSTEM,
                 _META_MAX,
         };
 
@@ -2570,8 +2951,9 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
                 [META_MACHINE_ID]        = "/etc/machine-id\0",
                 [META_MACHINE_INFO]      = "/etc/machine-info\0",
                 [META_OS_RELEASE]        = ("/etc/os-release\0"
-                                           "/usr/lib/os-release\0"),
-                [META_EXTENSION_RELEASE] = "extension-release\0", /* Used only for logging. */
+                                            "/usr/lib/os-release\0"),
+                [META_EXTENSION_RELEASE] = "extension-release\0",    /* Used only for logging. */
+                [META_HAS_INIT_SYSTEM]   = "has-init-system\0",      /* ditto */
         };
 
         _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL;
@@ -2582,6 +2964,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
         _cleanup_free_ char *hostname = NULL;
         unsigned n_meta_initialized = 0;
         int fds[2 * _META_MAX], r, v;
+        int has_init_system = -1;
         ssize_t n;
 
         BLOCK_SIGNALS(SIGCHLD);
@@ -2613,6 +2996,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
         if (r < 0)
                 goto finish;
         if (r == 0) {
+                /* Child in a new mount namespace */
                 error_pipe[0] = safe_close(error_pipe[0]);
 
                 r = dissected_image_mount(
@@ -2620,16 +3004,13 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
                                 t,
                                 UID_INVALID,
                                 UID_INVALID,
-                                DISSECT_IMAGE_READ_ONLY|
-                                DISSECT_IMAGE_MOUNT_ROOT_ONLY|
-                                DISSECT_IMAGE_VALIDATE_OS|
+                                extra_flags |
+                                DISSECT_IMAGE_READ_ONLY |
+                                DISSECT_IMAGE_MOUNT_ROOT_ONLY |
                                 DISSECT_IMAGE_USR_NO_ROOT);
                 if (r < 0) {
-                        /* Let parent know the error */
-                        (void) write(error_pipe[1], &r, sizeof(r));
-
                         log_debug_errno(r, "Failed to mount dissected image: %m");
-                        _exit(EXIT_FAILURE);
+                        goto inner_fail;
                 }
 
                 for (unsigned k = 0; k < _META_MAX; k++) {
@@ -2641,7 +3022,9 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
 
                         fds[2*k] = safe_close(fds[2*k]);
 
-                        if (k == META_EXTENSION_RELEASE) {
+                        switch (k) {
+
+                        case META_EXTENSION_RELEASE:
                                 /* As per the os-release spec, if the image is an extension it will have a file
                                  * named after the image name in extension-release.d/ - we use the image name
                                  * and try to resolve it with the extension-release helpers, as sometimes
@@ -2654,12 +3037,42 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
                                 r = open_extension_release(t, m->image_name, NULL, &fd);
                                 if (r < 0)
                                         fd = r; /* Propagate the error. */
-                        } else
+                                break;
+
+                        case META_HAS_INIT_SYSTEM: {
+                                bool found = false;
+                                const char *init;
+
+                                FOREACH_STRING(init,
+                                               "/usr/lib/systemd/systemd",  /* systemd on /usr merged system */
+                                               "/lib/systemd/systemd",      /* systemd on /usr non-merged systems */
+                                               "/sbin/init") {              /* traditional path the Linux kernel invokes */
+
+                                        r = chase_symlinks(init, t, CHASE_PREFIX_ROOT, NULL, NULL);
+                                        if (r < 0) {
+                                                if (r != -ENOENT)
+                                                        log_debug_errno(r, "Failed to resolve %s, ignoring: %m", init);
+                                        } else {
+                                                found = true;
+                                                break;
+                                        }
+                                }
+
+                                r = loop_write(fds[2*k+1], &found, sizeof(found), false);
+                                if (r < 0)
+                                        goto inner_fail;
+
+                                continue;
+                        }
+
+                        default:
                                 NULSTR_FOREACH(p, paths[k]) {
                                         fd = chase_symlinks_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
                                         if (fd >= 0)
                                                 break;
                                 }
+                        }
+
                         if (fd < 0) {
                                 log_debug_errno(fd, "Failed to read %s file of image, ignoring: %m", paths[k]);
                                 fds[2*k+1] = safe_close(fds[2*k+1]);
@@ -2667,15 +3080,18 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
                         }
 
                         r = copy_bytes(fd, fds[2*k+1], UINT64_MAX, 0);
-                        if (r < 0) {
-                                (void) write(error_pipe[1], &r, sizeof(r));
-                                _exit(EXIT_FAILURE);
-                        }
+                        if (r < 0)
+                                goto inner_fail;
 
                         fds[2*k+1] = safe_close(fds[2*k+1]);
                 }
 
                 _exit(EXIT_SUCCESS);
+
+        inner_fail:
+                /* Let parent know the error */
+                (void) write(error_pipe[1], &r, sizeof(r));
+                _exit(EXIT_FAILURE);
         }
 
         error_pipe[1] = safe_close(error_pipe[1]);
@@ -2699,7 +3115,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
                 case META_HOSTNAME:
                         r = read_etc_hostname_stream(f, &hostname);
                         if (r < 0)
-                                log_debug_errno(r, "Failed to read /etc/hostname: %m");
+                                log_debug_errno(r, "Failed to read /etc/hostname of image: %m");
 
                         break;
 
@@ -2708,17 +3124,17 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
 
                         r = read_line(f, LONG_LINE_MAX, &line);
                         if (r < 0)
-                                log_debug_errno(r, "Failed to read /etc/machine-id: %m");
+                                log_debug_errno(r, "Failed to read /etc/machine-id of image: %m");
                         else if (r == 33) {
                                 r = sd_id128_from_string(line, &machine_id);
                                 if (r < 0)
                                         log_debug_errno(r, "Image contains invalid /etc/machine-id: %s", line);
                         } else if (r == 0)
-                                log_debug("/etc/machine-id file is empty.");
+                                log_debug("/etc/machine-id file of image is empty.");
                         else if (streq(line, "uninitialized"))
-                                log_debug("/etc/machine-id file is uninitialized (likely aborted first boot).");
+                                log_debug("/etc/machine-id file of image is uninitialized (likely aborted first boot).");
                         else
-                                log_debug("/etc/machine-id has unexpected length %i.", r);
+                                log_debug("/etc/machine-id file of image has unexpected length %i.", r);
 
                         break;
                 }
@@ -2726,24 +3142,37 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
                 case META_MACHINE_INFO:
                         r = load_env_file_pairs(f, "machine-info", &machine_info);
                         if (r < 0)
-                                log_debug_errno(r, "Failed to read /etc/machine-info: %m");
+                                log_debug_errno(r, "Failed to read /etc/machine-info of image: %m");
 
                         break;
 
                 case META_OS_RELEASE:
                         r = load_env_file_pairs(f, "os-release", &os_release);
                         if (r < 0)
-                                log_debug_errno(r, "Failed to read OS release file: %m");
+                                log_debug_errno(r, "Failed to read OS release file of image: %m");
 
                         break;
 
                 case META_EXTENSION_RELEASE:
                         r = load_env_file_pairs(f, "extension-release", &extension_release);
                         if (r < 0)
-                                log_debug_errno(r, "Failed to read extension release file: %m");
+                                log_debug_errno(r, "Failed to read extension release file of image: %m");
 
                         break;
-                }
+
+                case META_HAS_INIT_SYSTEM: {
+                        bool b = false;
+                        size_t nr;
+
+                        errno = 0;
+                        nr = fread(&b, 1, sizeof(b), f);
+                        if (nr != sizeof(b))
+                                log_debug_errno(errno_or_else(EIO), "Failed to read has-init-system boolean: %m");
+                        else
+                                has_init_system = b;
+
+                        break;
+                }}
         }
 
         r = wait_for_terminate_and_check("(sd-dissect)", child, 0);
@@ -2767,6 +3196,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
         strv_free_and_replace(m->machine_info, machine_info);
         strv_free_and_replace(m->os_release, os_release);
         strv_free_and_replace(m->extension_release, extension_release);
+        m->has_init_system = has_init_system;
 
 finish:
         for (unsigned k = 0; k < n_meta_initialized; k++)
@@ -2804,20 +3234,32 @@ int dissect_image_and_warn(
                 return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
 
         case -ENOPKG:
-                return log_error_errno(r, "Couldn't identify a suitable partition table or file system in '%s'.", name);
+                return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
+
+        case -ENOMEDIUM:
+                return log_error_errno(r, "%s: The image does not pass validation.", name);
 
         case -EADDRNOTAVAIL:
-                return log_error_errno(r, "No root partition for specified root hash found in '%s'.", name);
+                return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
 
         case -ENOTUNIQ:
-                return log_error_errno(r, "Multiple suitable root partitions found in image '%s'.", name);
+                return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
 
         case -ENXIO:
-                return log_error_errno(r, "No suitable root partition found in image '%s'.", name);
+                return log_error_errno(r, "%s: No suitable root partition found in image.", name);
 
         case -EPROTONOSUPPORT:
                 return log_error_errno(r, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", name);
 
+        case -ENOTBLK:
+                return log_error_errno(r, "%s: Image is not a block device.", name);
+
+        case -EBADR:
+                return log_error_errno(r,
+                                       "Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
+                                       "(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
+                                       name, strna(verity ? verity->data_path : NULL));
+
         default:
                 if (r < 0)
                         return log_error_errno(r, "Failed to dissect image '%s': %m", name);
@@ -2826,23 +3268,57 @@ int dissect_image_and_warn(
         }
 }
 
-bool dissected_image_can_do_verity(const DissectedImage *image, PartitionDesignator partition_designator) {
+bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesignator partition_designator) {
+        assert(image);
+
+        /* Checks if this partition could theoretically do Verity. For non-partitioned images this only works
+         * if there's an external verity file supplied, for which we can consult .has_verity. For partitioned
+         * images we only check the partition type.
+         *
+         * This call is used to decide whether to suppress or show a verity column in tabular output of the
+         * image. */
+
         if (image->single_file_system)
-                return partition_designator == PARTITION_ROOT && image->can_verity;
+                return partition_designator == PARTITION_ROOT && image->has_verity;
 
         return PARTITION_VERITY_OF(partition_designator) >= 0;
 }
 
-bool dissected_image_has_verity(const DissectedImage *image, PartitionDesignator partition_designator) {
-        int k;
+bool dissected_image_verity_ready(const DissectedImage *image, PartitionDesignator partition_designator) {
+        PartitionDesignator k;
+
+        assert(image);
+
+        /* Checks if this partition has verity data available that we can activate. For non-partitioned this
+         * works for the root partition, for others only if the associated verity partition was found. */
+
+        if (!image->verity_ready)
+                return false;
 
         if (image->single_file_system)
-                return partition_designator == PARTITION_ROOT && image->verity;
+                return partition_designator == PARTITION_ROOT;
 
         k = PARTITION_VERITY_OF(partition_designator);
         return k >= 0 && image->partitions[k].found;
 }
 
+bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesignator partition_designator) {
+        PartitionDesignator k;
+
+        assert(image);
+
+        /* Checks if this partition has verity signature data available that we can use. */
+
+        if (!image->verity_sig_ready)
+                return false;
+
+        if (image->single_file_system)
+                return partition_designator == PARTITION_ROOT;
+
+        k = PARTITION_VERITY_SIG_OF(partition_designator);
+        return k >= 0 && image->partitions[k].found;
+}
+
 MountOptions* mount_options_free_all(MountOptions *options) {
         MountOptions *m;
 
@@ -2856,8 +3332,6 @@ MountOptions* mount_options_free_all(MountOptions *options) {
 }
 
 const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator) {
-        const MountOptions *m;
-
         LIST_FOREACH(mount_options, m, options)
                 if (designator == m->partition_designator && !isempty(m->options))
                         return m->options;
@@ -2909,6 +3383,10 @@ int mount_image_privately_interactively(
         if (r < 0)
                 return r;
 
+        r = dissected_image_load_verity_sig_partition(dissected_image, d->fd, &verity);
+        if (r < 0)
+                return r;
+
         r = dissected_image_decrypt_interactively(dissected_image, NULL, &verity, flags, &decrypted_image);
         if (r < 0)
                 return r;
@@ -2945,8 +3423,10 @@ int mount_image_privately_interactively(
 static const char *const partition_designator_table[] = {
         [PARTITION_ROOT] = "root",
         [PARTITION_ROOT_SECONDARY] = "root-secondary",
+        [PARTITION_ROOT_OTHER] = "root-other",
         [PARTITION_USR] = "usr",
         [PARTITION_USR_SECONDARY] = "usr-secondary",
+        [PARTITION_USR_OTHER] = "usr-other",
         [PARTITION_HOME] = "home",
         [PARTITION_SRV] = "srv",
         [PARTITION_ESP] = "esp",
@@ -2954,8 +3434,16 @@ static const char *const partition_designator_table[] = {
         [PARTITION_SWAP] = "swap",
         [PARTITION_ROOT_VERITY] = "root-verity",
         [PARTITION_ROOT_SECONDARY_VERITY] = "root-secondary-verity",
+        [PARTITION_ROOT_OTHER_VERITY] = "root-other-verity",
         [PARTITION_USR_VERITY] = "usr-verity",
         [PARTITION_USR_SECONDARY_VERITY] = "usr-secondary-verity",
+        [PARTITION_USR_OTHER_VERITY] = "usr-other-verity",
+        [PARTITION_ROOT_VERITY_SIG] = "root-verity-sig",
+        [PARTITION_ROOT_SECONDARY_VERITY_SIG] = "root-secondary-verity-sig",
+        [PARTITION_ROOT_OTHER_VERITY_SIG] = "root-other-verity-sig",
+        [PARTITION_USR_VERITY_SIG] = "usr-verity-sig",
+        [PARTITION_USR_SECONDARY_VERITY_SIG] = "usr-secondary-verity-sig",
+        [PARTITION_USR_OTHER_VERITY_SIG] = "usr-other-verity-sig",
         [PARTITION_TMP] = "tmp",
         [PARTITION_VAR] = "var",
 };
@@ -2966,7 +3454,8 @@ int verity_dissect_and_mount(
                 const MountOptions *options,
                 const char *required_host_os_release_id,
                 const char *required_host_os_release_version_id,
-                const char *required_host_os_release_sysext_level) {
+                const char *required_host_os_release_sysext_level,
+                const char *required_sysext_scope) {
 
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
@@ -3015,6 +3504,10 @@ int verity_dissect_and_mount(
         if (r < 0)
                 return log_debug_errno(r, "Failed to dissect image: %m");
 
+        r = dissected_image_load_verity_sig_partition(dissected_image, loop_device->fd, &verity);
+        if (r < 0)
+                return r;
+
         r = dissected_image_decrypt(
                         dissected_image,
                         NULL,
@@ -3038,9 +3531,9 @@ int verity_dissect_and_mount(
         /* If we got os-release values from the caller, then we need to match them with the image's
          * extension-release.d/ content. Return -EINVAL if there's any mismatch.
          * First, check the distro ID. If that matches, then check the new SYSEXT_LEVEL value if
-         * available, or else fallback to VERSION_ID. */
-        if (required_host_os_release_id &&
-            (required_host_os_release_version_id || required_host_os_release_sysext_level)) {
+         * available, or else fallback to VERSION_ID. If neither is present (eg: rolling release),
+         * then a simple match on the ID will be performed. */
+        if (required_host_os_release_id) {
                 _cleanup_strv_free_ char **extension_release = NULL;
 
                 r = load_extension_release_pairs(dest, dissected_image->image_name, &extension_release);
@@ -3048,11 +3541,12 @@ int verity_dissect_and_mount(
                         return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name);
 
                 r = extension_release_validate(
-                        dissected_image->image_name,
-                        required_host_os_release_id,
-                        required_host_os_release_version_id,
-                        required_host_os_release_sysext_level,
-                        extension_release);
+                                dissected_image->image_name,
+                                required_host_os_release_id,
+                                required_host_os_release_version_id,
+                                required_host_os_release_sysext_level,
+                                required_sysext_scope,
+                                extension_release);
                 if (r == 0)
                         return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", dissected_image->image_name);
                 if (r < 0)