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;
+ }
}
}
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)
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,
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;
} 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)
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);
}
}
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) {
}
#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,
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) {
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
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