]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: reuse existing dm-verity device for single filesystem images pinned by policy 40007/head
authorLuca Boccassi <luca.boccassi@gmail.com>
Thu, 27 Feb 2025 16:58:55 +0000 (16:58 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 19 Dec 2025 11:20:48 +0000 (11:20 +0000)
Loading images is, generally speaking, the slowest part of sd-executor
when spawning a service. This is due to multiple factors. dm-verity is
obviously a big part of the cost, but dissecting in general via libblkid
also can take a lot of time, due to probing the images and their filesystems.

A performance test doing service restarts in a row shows these
results, ran on a production system (low power and slow ARM64 SOC) with
a real production service, show the following service interruption intervals:

Count | P50 (ms)  | P90 (ms) | P95 (ms) | P99 (ms) | P99.9 (ms)
507   | 1,532     | 1,726    | 2,548    | 4,112    | 4,592

One iteration is 507 restarts in this case, but this has ran hundreds
of times and the results are always in line within margin of error.
This also holds true for metrics from live systems, same numbers.
Between 1.0s and 1.2s can be attributed by profiling to the time needed
for the service code itself to start up and sd_notify, the rest is spent
inside systemd's code.

This means there is currently a tradeoff for services - either use secure
images, or make restarting fast. Downtime of services is a very important
metric, as for many cases this directly translates to outages, total or
partial (blackouot or greyout).

In order to facilitate using secure images without downsides, skip the
slow dissect steps (probing, loop devices, etc) when the configured
image is a single filesystem dm-verity image with a policy that pins it
to a single filesystem, and an already existing and open dm-verity device
can be found and reused.

This allows orchestrators to pre-open images on download, before restarting
services, to minimize downtimes.

src/core/namespace.c
src/shared/dissect-image.c
src/shared/dissect-image.h
test/units/TEST-50-DISSECT.dissect.sh

index 19bf2903363505023df684af758c5661b51c6e1f..7a8facce97c00e1352bc73437723bb33865ea4b1 100644 (file)
@@ -2550,65 +2550,82 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
 
                 SET_FLAG(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE, p->verity && p->verity->data_path);
 
-                if (p->runtime_scope == RUNTIME_SCOPE_SYSTEM) {
-                        /* In system mode we mount directly */
-
-                        r = loop_device_make_by_path(
-                                        p->root_image,
-                                        FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
-                                        /* sector_size= */ UINT32_MAX,
-                                        FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
-                                        LOCK_SH,
-                                        &loop_device);
-                        if (r < 0)
-                                return log_debug_errno(r, "Failed to create loop device for root image: %m");
-
-                        r = dissect_loop_device(
-                                        loop_device,
-                                        p->verity,
-                                        p->root_image_options,
-                                        p->root_image_policy,
-                                        /* image_filter= */ NULL,
-                                        dissect_image_flags,
-                                        &dissected_image);
-                        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,
-                                        p->verity);
-                        if (r < 0)
-                                return r;
-
-                        r = dissected_image_guess_verity_roothash(
-                                        dissected_image,
-                                        p->verity);
-                        if (r < 0)
-                                return r;
-
-                        r = dissected_image_decrypt(
-                                        dissected_image,
-                                        NULL,
-                                        p->verity,
-                                        p->root_image_policy,
-                                        dissect_image_flags);
-                        if (r < 0)
-                                return log_debug_errno(r, "Failed to decrypt dissected image: %m");
-                } else {
-                        userns_fd = namespace_open_by_type(NAMESPACE_USER);
-                        if (userns_fd < 0)
-                                return log_debug_errno(userns_fd, "Failed to open our own user namespace: %m");
-
-                        r = mountfsd_mount_image(
-                                        p->root_image,
-                                        userns_fd,
-                                        p->root_image_policy,
-                                        p->verity,
-                                        dissect_image_flags,
-                                        &dissected_image);
-                        if (r < 0)
-                                return r;
+                /* First check if we have a verity device already open and with a fstype pinned by policy. If it
+                * cannot be found, then fallback to the slow path (full dissect). */
+                r = dissected_image_new_from_existing_verity(
+                                p->root_image,
+                                p->verity,
+                                p->root_image_options,
+                                p->root_image_policy,
+                                /* image_filter= */ NULL,
+                                p->runtime_scope,
+                                dissect_image_flags,
+                                &dissected_image);
+                if (r < 0 && !ERRNO_IS_NEG_DEVICE_ABSENT(r) && r != -ENOPKG)
+                        return r;
+                if (r >= 0)
+                        log_debug("Reusing pre-existing verity-protected root image %s", p->root_image);
+                else {
+                        if (p->runtime_scope == RUNTIME_SCOPE_SYSTEM) {
+                                /* In system mode we mount directly */
+
+                                r = loop_device_make_by_path(
+                                                p->root_image,
+                                                FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
+                                                /* sector_size= */ UINT32_MAX,
+                                                FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
+                                                LOCK_SH,
+                                                &loop_device);
+                                if (r < 0)
+                                        return log_debug_errno(r, "Failed to create loop device for root image: %m");
+
+                                r = dissect_loop_device(
+                                                loop_device,
+                                                p->verity,
+                                                p->root_image_options,
+                                                p->root_image_policy,
+                                                /* image_filter= */ NULL,
+                                                dissect_image_flags,
+                                                &dissected_image);
+                                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,
+                                                p->verity);
+                                if (r < 0)
+                                        return r;
+
+                                r = dissected_image_guess_verity_roothash(
+                                                dissected_image,
+                                                p->verity);
+                                if (r < 0)
+                                        return r;
+
+                                r = dissected_image_decrypt(
+                                                dissected_image,
+                                                /* passphrase= */ NULL,
+                                                p->verity,
+                                                p->root_image_policy,
+                                                dissect_image_flags);
+                                if (r < 0)
+                                        return log_debug_errno(r, "Failed to decrypt dissected image: %m");
+                        } else {
+                                userns_fd = namespace_open_by_type(NAMESPACE_USER);
+                                if (userns_fd < 0)
+                                        return log_debug_errno(userns_fd, "Failed to open our own user namespace: %m");
+
+                                r = mountfsd_mount_image(
+                                                p->root_image,
+                                                userns_fd,
+                                                p->root_image_policy,
+                                                p->verity,
+                                                dissect_image_flags,
+                                                &dissected_image);
+                                if (r < 0)
+                                        return r;
+                        }
                 }
         }
 
index 0fe3a5b3e01377631ce7d44d95b36c483b4e2cd4..e155970e313cc5627e6a3161bc2652b4922affd9 100644 (file)
@@ -843,12 +843,15 @@ static int open_partition(
         int r;
 
         assert(node);
-        assert(loop);
+        assert(loop || !is_partition);
 
         fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
         if (fd < 0)
                 return -errno;
 
+        if (!loop)
+                return TAKE_FD(fd);
+
         /* Check if the block device is a child of (or equivalent to) the originally provided one. */
         r = block_device_new_from_fd(fd, is_partition ? BLOCK_DEVICE_LOOKUP_WHOLE_DISK : 0, &dev);
         if (r < 0)
@@ -911,6 +914,85 @@ static bool image_filter_test(const ImageFilter *filter, PartitionDesignator d,
         return fnmatch(filter->pattern[d], strempty(label),  FNM_NOESCAPE) == 0;
 }
 
+static int dissect_image_from_unpartitioned(
+                const char *devname,
+                uint64_t diskseq,
+                const sd_id128_t *uuid,
+                bool encrypted,
+                const VeritySettings *verity,
+                const MountOptions *mount_options,
+                const ImagePolicy *policy,
+                const ImageFilter *filter,
+                int *mount_node_fd, /* taken over on success */
+                char **fstype, /* taken over on success */
+                DissectedImage *m,
+                DissectImageFlags flags,
+                PartitionPolicyFlags found_flags) {
+
+        _cleanup_free_ char *n = NULL, *o = NULL;
+        const char *options = NULL;
+        int r;
+
+        assert(devname);
+        assert(m);
+        assert(fstype);
+
+        if (!image_filter_test(filter, PARTITION_ROOT, /* label= */ NULL)) /* do a filter check with an empty partition label */
+                return -ECOMM;
+
+        r = image_policy_may_use(policy, PARTITION_ROOT);
+        if (r < 0)
+                return r;
+        if (r == 0) /* policy says ignore this, so we ignore it */
+                return -ENOPKG;
+
+        r = image_policy_check_protection(policy, PARTITION_ROOT, found_flags);
+        if (r < 0)
+                return r;
+
+        r = image_policy_check_partition_flags(policy, PARTITION_ROOT, 0); /* we have no gpt partition flags, hence check against all bits off */
+        if (r < 0)
+                return r;
+
+        r = make_partition_devname(devname, diskseq, /* nr= */ -1, flags, &n);
+        if (r < 0)
+                return r;
+
+        m->single_file_system = true;
+        m->encrypted = encrypted;
+
+        m->has_verity = verity && verity->data_path;
+        m->verity_ready = verity_settings_data_covers(verity, PARTITION_ROOT);
+
+        m->has_verity_sig = false; /* signature not embedded, must be specified */
+        m->verity_sig_ready = m->verity_ready && iovec_is_set(&verity->root_hash);
+
+        if (uuid)
+                m->image_uuid = *uuid;
+
+        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 = !m->verity_ready && !fstype_is_ro(*fstype),
+                .partno = -1,
+                .architecture = _ARCHITECTURE_INVALID,
+                .fstype = TAKE_PTR(*fstype),
+                .node = TAKE_PTR(n),
+                .mount_options = TAKE_PTR(o),
+                .mount_node_fd = mount_node_fd ? TAKE_FD(*mount_node_fd) : -EBADF,
+                .size = UINT64_MAX,
+                .fsmount_fd = -EBADF,
+        };
+
+        return 0;
+}
+
 static int dissect_image(
                 DissectedImage *m,
                 int fd,
@@ -1030,38 +1112,41 @@ static int dissect_image(
         if ((!(flags & DISSECT_IMAGE_GPT_ONLY) &&
             (flags & DISSECT_IMAGE_GENERIC_ROOT)) ||
             (flags & DISSECT_IMAGE_NO_PARTITION_TABLE)) {
+                _cleanup_free_ char *root_fstype_string = NULL;
                 const char *usage = NULL;
+                bool encrypted;
+
+                r = partition_policy_determine_fstype(policy, PARTITION_ROOT, &encrypted, &root_fstype_string);
+                if (r < 0)
+                        return r;
 
                 /* If flags permit this, also allow using non-partitioned single-filesystem images */
 
-                (void) sym_blkid_probe_lookup_value(b, "USAGE", &usage, NULL);
+                if (root_fstype_string)
+                        usage = encrypted ? "crypto" : "filesystem";
+                else
+                        (void) sym_blkid_probe_lookup_value(b, "USAGE", &usage, NULL);
                 if (STRPTR_IN_SET(usage, "filesystem", "crypto")) {
-                        _cleanup_free_ char *t = NULL, *n = NULL, *o = NULL;
-                        const char *fstype = NULL, *options = NULL;
+                        _cleanup_free_ char *t = NULL;
+                        const char *fstype = NULL;
                         _cleanup_close_ int mount_node_fd = -EBADF;
                         sd_id128_t uuid = SD_ID128_NULL;
                         PartitionPolicyFlags found_flags;
-                        bool encrypted;
 
                         /* OK, we have found a file system, that's our root partition then. */
 
-                        if (!image_filter_test(filter, PARTITION_ROOT, /* label= */ NULL)) /* do a filter check with an empty partition label */
-                                return -ECOMM;
-
-                        r = image_policy_may_use(policy, PARTITION_ROOT);
-                        if (r < 0)
-                                return r;
-                        if (r == 0) /* policy says ignore this, so we ignore it */
-                                return -ENOPKG;
-
-                        (void) sym_blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+                        if (!root_fstype_string) {
+                                (void) sym_blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
 
-                        /* blkid will return FAT's serial number as UUID, hence it is quite possible that
-                         * parsing this will fail. We'll ignore the ID, since it's just too short to be
-                         * useful as true identifier. */
-                        (void) blkid_probe_lookup_value_id128(b, "UUID", &uuid);
+                                /* blkid will return FAT's serial number as UUID, hence it is quite possible that
+                                * parsing this will fail. We'll ignore the ID, since it's just too short to be
+                                * useful as true identifier. */
+                                (void) blkid_probe_lookup_value_id128(b, "UUID", &uuid);
+                        } else
+                                /* The policy fstype flags translate to the literal fstype name of each filesystem. */
+                                fstype = root_fstype_string;
 
-                        encrypted = streq_ptr(fstype, "crypto_LUKS");
+                        encrypted = encrypted || streq_ptr(fstype, "crypto_LUKS");
 
                         if (verity_settings_data_covers(verity, PARTITION_ROOT))
                                 found_flags = iovec_is_set(&verity->root_hash_sig) ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY;
@@ -1074,14 +1159,6 @@ static int dissect_image(
                         } else
                                 found_flags = PARTITION_POLICY_UNPROTECTED;
 
-                        r = image_policy_check_protection(policy, PARTITION_ROOT, found_flags);
-                        if (r < 0)
-                                return r;
-
-                        r = image_policy_check_partition_flags(policy, PARTITION_ROOT, 0); /* we have no gpt partition flags, hence check against all bits off */
-                        if (r < 0)
-                                return r;
-
                         if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
                                 mount_node_fd = open_partition(devname, /* is_partition= */ false, m->loop);
                                 if (mount_node_fd < 0)
@@ -1094,43 +1171,20 @@ static int dissect_image(
                                         return -ENOMEM;
                         }
 
-                        r = make_partition_devname(devname, diskseq, -1, flags, &n);
-                        if (r < 0)
-                                return r;
-
-                        m->single_file_system = true;
-                        m->encrypted = encrypted;
-
-                        m->has_verity = verity && verity->data_path;
-                        m->verity_ready = verity_settings_data_covers(verity, PARTITION_ROOT);
-
-                        m->has_verity_sig = false; /* signature not embedded, must be specified */
-                        m->verity_sig_ready = m->verity_ready && iovec_is_set(&verity->root_hash);
-
-                        m->image_uuid = uuid;
-
-                        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 = !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),
-                                .mount_node_fd = TAKE_FD(mount_node_fd),
-                                .offset = 0,
-                                .size = UINT64_MAX,
-                                .fsmount_fd = -EBADF,
-                        };
-
-                        return 0;
+                        return dissect_image_from_unpartitioned(
+                                        devname,
+                                        diskseq,
+                                        &uuid,
+                                        encrypted,
+                                        verity,
+                                        mount_options,
+                                        policy,
+                                        filter,
+                                        &mount_node_fd,
+                                        &t,
+                                        m,
+                                        flags,
+                                        found_flags);
                 }
         }
 
@@ -1555,10 +1609,15 @@ static int dissect_image(
                                 if (r < 0)
                                         return r;
 
+                                /* Local override via env var or designator type wins */
                                 if (fstype) {
                                         t = strdup(fstype);
                                         if (!t)
                                                 return -ENOMEM;
+                                } else {
+                                        r = partition_policy_determine_fstype(policy, type.designator, /* ret_encrypted= */ NULL, &t);
+                                        if (r < 0)
+                                                return r;
                                 }
 
                                 if (label) {
@@ -1896,6 +1955,97 @@ static int dissect_image(
 }
 #endif
 
+int dissected_image_new_from_existing_verity(
+                const char *src,
+                const VeritySettings *verity,
+                const MountOptions *options,
+                const ImagePolicy *image_policy,
+                const ImageFilter *image_filter,
+                RuntimeScope runtime_scope,
+                DissectImageFlags dissect_image_flags,
+                DissectedImage **ret) {
+
+        /* Look for an already set up dm-verity device with a single filesystem, according to our naming
+         * scheme and image policy, and if it is pinned by filesystem type set up the image directly. */
+
+#if HAVE_BLKID
+        _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
+        _cleanup_free_ char *node = NULL, *root_hash_encoded = NULL, *root_fstype_string = NULL;
+        _cleanup_close_ int mount_node_fd = -EBADF;
+        PartitionPolicyFlags found_flags;
+        bool encrypted = false;
+        int r;
+
+        assert(!verity || verity->designator < 0 || IN_SET(verity->designator, PARTITION_ROOT, PARTITION_USR));
+        assert(!verity || iovec_is_valid(&verity->root_hash));
+        assert(!verity || iovec_is_valid(&verity->root_hash_sig));
+        assert(!verity || iovec_is_set(&verity->root_hash) || !iovec_is_set(&verity->root_hash_sig));
+        assert(ret);
+
+        /* Shortcut: this deals only with verity images and requires a policy, and only for system services */
+        if (runtime_scope != RUNTIME_SCOPE_SYSTEM ||
+            !FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_VERITY_SHARE) ||
+            !image_policy ||
+            !verity ||
+            !verity->data_path ||
+            (verity->designator >= 0 && verity->designator != PARTITION_ROOT) ||
+            !iovec_is_set(&verity->root_hash))
+                return -ENOPKG;
+
+        /* The policy fstype flags translate to the literal fstype name of each filesystem.
+         * Input must be a single filesystem image, if the policy specifies more than one, we need to dissect */
+        r = partition_policy_determine_fstype(image_policy, PARTITION_ROOT, &encrypted, &root_fstype_string);
+        if (r < 0)
+                return r;
+        if (!root_fstype_string)
+                return -ENOPKG;
+
+        if (verity_settings_data_covers(verity, PARTITION_ROOT))
+                found_flags = iovec_is_set(&verity->root_hash_sig) ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY;
+        else
+                found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : PARTITION_POLICY_UNPROTECTED;
+
+        root_hash_encoded = hexmem(verity->root_hash.iov_base, verity->root_hash.iov_len);
+        if (!root_hash_encoded)
+                return -ENOMEM;
+
+        node = strjoin("/dev/mapper/", root_hash_encoded, "-verity");
+        if (!node)
+                return -ENOMEM;
+
+        r = dissected_image_new(src, &dissected_image);
+        if (r < 0)
+                return r;
+
+        mount_node_fd = open_partition(node, /* is_partition= */ false, /* loop= */ NULL);
+        if (mount_node_fd < 0)
+                return mount_node_fd;
+
+        r = dissect_image_from_unpartitioned(
+                        node,
+                        /* diskseq= */ 0,
+                        /* uuid= */ NULL,
+                        encrypted,
+                        verity,
+                        options,
+                        image_policy,
+                        image_filter,
+                        &mount_node_fd,
+                        &root_fstype_string,
+                        dissected_image,
+                        dissect_image_flags,
+                        found_flags);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(dissected_image);
+
+        return 0;
+#else
+        return -EOPNOTSUPP;
+#endif
+}
+
 int dissect_image_file(
                 const char *path,
                 const VeritySettings *verity,
@@ -4636,70 +4786,87 @@ int verity_dissect_and_mount(
                 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
                 DISSECT_IMAGE_VERITY_SHARE;
 
-        if (runtime_scope == RUNTIME_SCOPE_SYSTEM) {
-                /* Note that we don't use loop_device_make here, as the FD is most likely O_PATH which would not be
-                * accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */
-                r = loop_device_make_by_path(
-                                src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
-                                /* open_flags= */ -1,
-                                /* sector_size= */ UINT32_MAX,
-                                verity->data_path ? 0 : LO_FLAGS_PARTSCAN,
-                                LOCK_SH,
-                                &loop_device);
-                if (r < 0)
-                        return log_debug_errno(r, "Failed to create loop device for image: %m");
-
-                r = dissect_loop_device(
-                                loop_device,
-                                verity,
-                                options,
-                                image_policy,
-                                image_filter,
-                                dissect_image_flags,
-                                &dissected_image);
-                /* No partition table? Might be a single-filesystem image, try again */
-                if (!verity->data_path && r == -ENOPKG)
+        /* First check if we have a verity device already open and with a fstype pinned by policy. If it
+         * cannot be found, then fallback to the slow path (full dissect). */
+        r = dissected_image_new_from_existing_verity(
+                        src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
+                        verity,
+                        options,
+                        image_policy,
+                        image_filter,
+                        runtime_scope,
+                        dissect_image_flags,
+                        &dissected_image);
+        if (r < 0 && !ERRNO_IS_NEG_DEVICE_ABSENT(r) && r != -ENOPKG)
+                return r;
+        if (r >= 0)
+                log_debug("Reusing pre-existing verity-protected image %s", src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src);
+        else {
+                if (runtime_scope == RUNTIME_SCOPE_SYSTEM) {
+                        /* Note that we don't use loop_device_make here, as the FD is most likely O_PATH which would not be
+                        * accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */
+                        r = loop_device_make_by_path(
+                                        src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
+                                        /* open_flags= */ -1,
+                                        /* sector_size= */ UINT32_MAX,
+                                        verity->data_path ? 0 : LO_FLAGS_PARTSCAN,
+                                        LOCK_SH,
+                                        &loop_device);
+                        if (r < 0)
+                                return log_debug_errno(r, "Failed to create loop device for image: %m");
+
                         r = dissect_loop_device(
                                         loop_device,
                                         verity,
                                         options,
                                         image_policy,
                                         image_filter,
-                                        dissect_image_flags | DISSECT_IMAGE_NO_PARTITION_TABLE,
+                                        dissect_image_flags,
                                         &dissected_image);
-                if (r < 0)
-                        return log_debug_errno(r, "Failed to dissect image: %m");
+                        /* No partition table? Might be a single-filesystem image, try again */
+                        if (!verity->data_path && r == -ENOPKG)
+                                r = dissect_loop_device(
+                                                loop_device,
+                                                verity,
+                                                options,
+                                                image_policy,
+                                                image_filter,
+                                                dissect_image_flags | DISSECT_IMAGE_NO_PARTITION_TABLE,
+                                                &dissected_image);
+                        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_load_verity_sig_partition(dissected_image, loop_device->fd, verity);
+                        if (r < 0)
+                                return r;
 
-                r = dissected_image_guess_verity_roothash(dissected_image, verity);
-                if (r < 0)
-                        return r;
+                        r = dissected_image_guess_verity_roothash(dissected_image, verity);
+                        if (r < 0)
+                                return r;
 
-                r = dissected_image_decrypt(
-                                dissected_image,
-                                NULL,
-                                verity,
-                                image_policy,
-                                dissect_image_flags);
-                if (r < 0)
-                        return log_debug_errno(r, "Failed to decrypt dissected image: %m");
-        } else {
-                userns_fd = namespace_open_by_type(NAMESPACE_USER);
-                if (userns_fd < 0)
-                        return log_debug_errno(userns_fd, "Failed to open our own user namespace: %m");
-
-                r = mountfsd_mount_image(
-                                src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
-                                userns_fd,
-                                image_policy,
-                                verity,
-                                dissect_image_flags,
-                                &dissected_image);
-                if (r < 0)
-                        return r;
+                        r = dissected_image_decrypt(
+                                        dissected_image,
+                                        NULL,
+                                        verity,
+                                        image_policy,
+                                        dissect_image_flags);
+                        if (r < 0)
+                                return log_debug_errno(r, "Failed to decrypt dissected image: %m");
+                } else {
+                        userns_fd = namespace_open_by_type(NAMESPACE_USER);
+                        if (userns_fd < 0)
+                                return log_debug_errno(userns_fd, "Failed to open our own user namespace: %m");
+
+                        r = mountfsd_mount_image(
+                                        src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
+                                        userns_fd,
+                                        image_policy,
+                                        verity,
+                                        dissect_image_flags,
+                                        &dissected_image);
+                        if (r < 0)
+                                return r;
+                }
         }
 
         if (dest) {
index 951ae6605566ec39e1a7601b4f05cb23cf2536a4..9650dce2a49f2e870c9eb6fb2c9ba39bc1e81160 100644 (file)
@@ -165,6 +165,7 @@ int dissect_image_file(const char *path, const VeritySettings *verity, const Mou
 int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *filter, DissectImageFlags flags, DissectedImage **ret);
 int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *image_filter, DissectImageFlags flags, DissectedImage **ret);
 int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *image_filter, DissectImageFlags flags, DissectedImage **ret);
+int dissected_image_new_from_existing_verity(const char *src, const VeritySettings *verity, const MountOptions *options, const ImagePolicy *image_policy, const ImageFilter *image_filter, RuntimeScope runtime_scope, DissectImageFlags dissect_image_flags, DissectedImage **ret);
 
 void dissected_image_close(DissectedImage *m);
 DissectedImage* dissected_image_unref(DissectedImage *m);
index 47ea82c3b77c2b75bbd36e9160513e11e9f6e9eb..f16c6856cece70e878f4946717c0fec01bca6270 100755 (executable)
@@ -507,10 +507,54 @@ NONEXISTENT_VDIR="/tmp/$VBASE-nonexistent.v"
 mkdir "$VDIR" "$EMPTY_VDIR"
 
 ln -s /tmp/app0.raw "$VDIR/${VBASE}_0.raw"
+ln -s /tmp/app0.verity "$VDIR/${VBASE}_0.verity"
+ln -s /tmp/app0.roothash "$VDIR/${VBASE}_0.roothash"
 ln -s /tmp/app1.raw "$VDIR/${VBASE}_1.raw"
 
 systemd-run -P -p ExtensionImages="$VDIR -$EMPTY_VDIR -$NONEXISTENT_VDIR" bash -o pipefail -c '/opt/script1.sh | grep ID'
 
+# Check dissect shortcut for verity images
+# No verity for the second img (previous test benefits from variations), remove it for the next one
+rm -f "$VDIR/${VBASE}_1.raw"
+cat >/run/systemd/system/testservice-50e-vpick.service <<EOF
+[Service]
+Type=notify
+NotifyAccess=all
+MountAPIVFS=yes
+TemporaryFileSystem=/run /var/lib
+StateDirectory=app-vpick
+RootImage=$MINIMAL_IMAGE.raw
+RootImagePolicy=root=squashfs
+ExtensionImages=$VDIR:x-systemd.relax-extension-release-check -$EMPTY_VDIR -$NONEXISTENT_VDIR
+ExtensionImagePolicy=root=squashfs
+# Relevant only for sanitizer runs
+UnsetEnvironment=LD_PRELOAD
+ExecStartPre=bash -c '/opt/script0.sh | grep ID'
+ExecStart=sh -c 'echo "READY=1" | socat -t 5 - UNIX-SENDTO:\$\$NOTIFY_SOCKET; sleep infinity'
+EOF
+systemctl start testservice-50e-vpick.service
+systemctl is-active testservice-50e-vpick.service
+# Ensure the device is preopened for reuse. Note that sd-dissect -M does not work on older
+# kernels, <something> makes the devices disappear on Ubuntu 24.04/C9S, so set them
+# up by hand. Don't fail if it's already set up.
+veritysetup open "$MINIMAL_IMAGE.raw" "$(cat "$MINIMAL_IMAGE.roothash")-verity" "$MINIMAL_IMAGE.verity" --root-hash-file "$MINIMAL_IMAGE.roothash" ||:
+veritysetup open "$VDIR/${VBASE}_0.raw" "$(cat "$VDIR/${VBASE}_0.roothash")-verity" "$VDIR/${VBASE}_0.verity" --root-hash-file "$VDIR/${VBASE}_0.roothash" ||:
+mkdir -p /tmp/img /tmp/ext
+mount -o ro "/dev/mapper/$(cat "$MINIMAL_IMAGE.roothash")-verity" /tmp/img
+mount -o ro "/dev/mapper/$(cat "$VDIR/${VBASE}_0.roothash")-verity" /tmp/ext
+journalctl --sync
+since="$(date '+%H:%M:%S')"
+systemctl restart testservice-50e-vpick.service
+systemctl is-active testservice-50e-vpick.service
+journalctl --sync
+timeout -v 30 journalctl --since "$since" -n all --follow | grep -m 2 -F 'Reusing pre-existing verity-protected root image'
+timeout -v 30 journalctl --since "$since" -n all --follow | grep -m 2 -F 'Reusing pre-existing verity-protected image'
+systemctl stop testservice-50e-vpick.service
+umount -R /tmp/img
+umount -R /tmp/ext
+veritysetup close "$(cat "$MINIMAL_IMAGE.roothash")-verity" ||:
+veritysetup close "$(cat "$VDIR/${VBASE}_0.roothash")-verity" ||:
+
 rm -rf "$VDIR" "$EMPTY_VDIR"
 
 # ExtensionDirectories will set up an overlay
@@ -787,6 +831,49 @@ rm -f /run/systemd/system/testservice-50k.service
 systemctl daemon-reload
 rm -rf "$VDIR" "$VDIR2" /tmp/vpickminimg /tmp/markers/
 
+# Check dissect shortcut for verity images
+cat >/run/systemd/system/testservice-50m.service <<EOF
+[Service]
+Type=notify
+NotifyAccess=all
+TemporaryFileSystem=/run /var/lib
+StateDirectory=app0
+RootImage=$MINIMAL_IMAGE.raw
+RootImagePolicy=root=squashfs
+ExtensionImages=/tmp/app0.raw /tmp/app1.raw
+ExtensionImagePolicy=root=squashfs
+MountImages=/tmp/app0.raw:/var/lib/app
+MountImagePolicy=root=squashfs
+# Relevant only for sanitizer runs
+UnsetEnvironment=LD_PRELOAD
+ExecStartPre=bash -o pipefail -c '/opt/script0.sh | grep ID'
+ExecStartPre=bash -o pipefail -c '/opt/script1.sh | grep ID'
+ExecStartPre=test -e "/dev/mapper/$(</tmp/app0.roothash)-verity"
+ExecStart=sh -c 'echo "READY=1" | socat -t 5 - UNIX-SENDTO:\$\$NOTIFY_SOCKET; sleep infinity'
+EOF
+systemctl start testservice-50m.service
+systemctl is-active testservice-50m.service
+# Ensure the device is preopened for reuse. Note that sd-dissect -M does not work on older
+# kernels, <something> makes the devices disappear on Ubuntu 24.04/C9S, so set them
+# up by hand. Don't fail if it's already set up.
+veritysetup open "$MINIMAL_IMAGE.raw" "$(cat "$MINIMAL_IMAGE.roothash")-verity" "$MINIMAL_IMAGE.verity" --root-hash-file "$MINIMAL_IMAGE.roothash" ||:
+veritysetup open /tmp/app0.raw "$(cat /tmp/app0.roothash)-verity" /tmp/app0.verity --root-hash-file /tmp/app0.roothash ||:
+mkdir -p /tmp/img /tmp/ext
+mount -o ro "/dev/mapper/$(cat "$MINIMAL_IMAGE.roothash")-verity" /tmp/img
+mount -o ro "/dev/mapper/$(cat /tmp/app0.roothash)-verity" /tmp/ext
+journalctl --sync
+since="$(date '+%H:%M:%S')"
+systemctl restart testservice-50m.service
+systemctl is-active testservice-50m.service
+journalctl --sync
+timeout -v 30 journalctl --since "$since" -n all --follow | grep -m 4 -F 'Reusing pre-existing verity-protected root image'
+timeout -v 30 journalctl --since "$since" -n all --follow | grep -m 8 -F 'Reusing pre-existing verity-protected image'
+systemctl stop testservice-50m.service
+umount /tmp/img
+umount /tmp/ext
+veritysetup close "$(cat "$MINIMAL_IMAGE.roothash")-verity" ||:
+veritysetup close "$(cat /tmp/app0.roothash)-verity" ||:
+
 # Test that an extension consisting of an empty directory under /etc/extensions/ takes precedence
 mkdir -p /var/lib/extensions/
 ln -s /tmp/app-nodistro.raw /var/lib/extensions/app-nodistro.raw