core: reuse existing dm-verity device for single filesystem images pinned by policy

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.
This commit is contained in:
Luca Boccassi
2025-12-19 11:20:48 +00:00
parent 4be5c2870f
commit 0bd766553c
4 changed files with 442 additions and 170 deletions
+70 -53
View 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 */
/* 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 = 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 = 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_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_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 = 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;
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;
}
}
}
+284 -117
View 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;
if (!root_fstype_string) {
(void) sym_blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
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;
/* 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;
(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);
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");
/* 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,
&dissected_image);
/* 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,
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 = 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 = 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) {
+1
View 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);
+87
View File
@@ -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