core: reuse existing dm-verity device for single filesystem images pinned by policy (#40007)

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 13:19:16 +00:00
committed by GitHub
9 changed files with 655 additions and 179 deletions
+50 -5
View File
@@ -26,11 +26,11 @@
<para>In systemd, whenever a disk image (DDI) implementing the <ulink
url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">UAPI.2
Discoverable Partitions Specification</ulink> is activated, a policy may be specified controlling which
partitions to mount and what kind of cryptographic protection to require. Such a disk image dissection
policy is a string that contains per-partition-type rules, separated by colons
(<literal>:</literal>). The individual rules consist of a partition identifier, an equal sign
(<literal>=</literal>), and one or more flags which may be set per partition. If multiple flags are
specified per partition they are separated by a plus sign (<literal>+</literal>).</para>
partitions to mount and what kind of cryptographic protection to require, and which type of filesystem
is allowed. Such a disk image dissection policy is a string that contains per-partition-type rules,
separated by colons (<literal>:</literal>). The individual rules consist of a partition identifier, an
equal sign (<literal>=</literal>), and one or more flags which may be set per partition. If multiple
flags are specified per partition they are separated by a plus sign (<literal>+</literal>).</para>
<para>The partition identifiers currently defined are: <option>root</option>, <option>usr</option>,
<option>home</option>, <option>srv</option>, <option>esp</option>, <option>xbootldr</option>,
@@ -71,6 +71,46 @@
image.</para></listitem>
</itemizedlist>
<para>The following filesystem policy flags are defined that dictate which filesystem types are allowed
for which partition:</para>
<table>
<title>
Filesystems types supported by the policy
</title>
<tgroup cols='1'>
<thead>
<row>
<entry>Filesystem flag</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>btrfs</literal></entry>
</row>
<row>
<entry><literal>erofs</literal></entry>
</row>
<row>
<entry><literal>ext4</literal></entry>
</row>
<row>
<entry><literal>f2fs</literal></entry>
</row>
<row>
<entry><literal>squashfs</literal></entry>
</row>
<row>
<entry><literal>vfat</literal></entry>
</row>
<row>
<entry><literal>xfs</literal></entry>
</row>
</tbody>
</tgroup>
</table>
<para>By setting a combination of the flags above, alternatives can be declared. For example the
combination <literal>unused+absent</literal> means: the partition may exist (in which case it shall not
be used) or may be absent. The combination of
@@ -175,6 +215,11 @@
does not have to be, and ignores swap partitions, and uses all other partitions if they are available, possibly with encryption.</para>
<programlisting>root=unprotected+encrypted:swap=absent+unused:=unprotected+encrypted+absent</programlisting>
<para>The following image policy string dictates a single root partition that can only be erofs or squashfs,
and ignores swap partitions, and uses all other partitions if they are available, possibly with encryption.</para>
<programlisting>root=erofs+squashfs:swap=absent+unused:=unprotected+encrypted+absent</programlisting>
</refsect1>
<refsect1>
+10 -2
View File
@@ -9,7 +9,7 @@
#include "string-util.h"
static int table_add_designator_line(Table *table, PartitionDesignator d, PartitionPolicyFlags f) {
_cleanup_free_ char *q = NULL;
_cleanup_free_ char *q = NULL, *t = NULL;
const char *color;
int r;
@@ -19,6 +19,9 @@ static int table_add_designator_line(Table *table, PartitionDesignator d, Partit
if (partition_policy_flags_to_string(f & _PARTITION_POLICY_USE_MASK, /* simplify= */ true, &q) < 0)
return log_oom();
if (partition_policy_flags_to_string(f & _PARTITION_POLICY_FSTYPE_MASK, /* simplify= */ true, &t) < 0)
return log_oom();
color = (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE ? ansi_grey() :
((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ==
(PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() :
@@ -72,6 +75,11 @@ static int table_add_designator_line(Table *table, PartitionDesignator d, Partit
if (r < 0)
return table_log_add_error(r);
r = table_add_many(table,
TABLE_STRING, isempty(t) ? "-" : t);
if (r < 0)
return table_log_add_error(r);
return 0;
}
@@ -128,7 +136,7 @@ int verb_image_policy(int argc, char *argv[], void *userdata) {
ansi_highlight(), as_string_simplified, ansi_normal(),
ansi_grey(), as_string, ansi_normal());
table = table_new("partition", "mode", "read-only", "growfs");
table = table_new("partition", "mode", "read-only", "growfs", "fstype");
if (!table)
return log_oom();
+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);
+70 -2
View File
@@ -207,6 +207,20 @@ static PartitionPolicyFlags policy_flag_from_string_one(const char *s) {
return PARTITION_POLICY_GROWFS_ON;
if (streq(s, "growfs-off"))
return PARTITION_POLICY_GROWFS_OFF;
if (streq(s, "btrfs"))
return PARTITION_POLICY_BTRFS;
if (streq(s, "erofs"))
return PARTITION_POLICY_EROFS;
if (streq(s, "ext4"))
return PARTITION_POLICY_EXT4;
if (streq(s, "f2fs"))
return PARTITION_POLICY_F2FS;
if (streq(s, "squashfs"))
return PARTITION_POLICY_SQUASHFS;
if (streq(s, "vfat"))
return PARTITION_POLICY_VFAT;
if (streq(s, "xfs"))
return PARTITION_POLICY_XFS;
return _PARTITION_POLICY_FLAGS_INVALID;
}
@@ -389,7 +403,7 @@ int image_policy_from_string(const char *s, bool graceful, ImagePolicy **ret) {
int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret) {
_cleanup_free_ char *buf = NULL;
const char *l[CONST_LOG2U(_PARTITION_POLICY_MASK + 1) + 1]; /* one string per known flag at most */
const char *l[CONST_LOG2U(_PARTITION_POLICY_MASK + _PARTITION_POLICY_FSTYPE_MASK + 1) + 1]; /* one string per known flag at most */
size_t m = 0;
assert(ret);
@@ -449,6 +463,23 @@ int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify,
l[m++] = "growfs-on";
}
/* These flags must translate to the literal fstype name of each filesystem, as accepted by
* `mount -t`. */
if (flags & PARTITION_POLICY_BTRFS)
l[m++] = "btrfs";
if (flags & PARTITION_POLICY_EROFS)
l[m++] = "erofs";
if (flags & PARTITION_POLICY_EXT4)
l[m++] = "ext4";
if (flags & PARTITION_POLICY_F2FS)
l[m++] = "f2fs";
if (flags & PARTITION_POLICY_SQUASHFS)
l[m++] = "squashfs";
if (flags & PARTITION_POLICY_VFAT)
l[m++] = "vfat";
if (flags & PARTITION_POLICY_XFS)
l[m++] = "xfs";
if (m == 0)
buf = strdup("-");
else {
@@ -461,7 +492,44 @@ int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify,
return -ENOMEM;
*ret = TAKE_PTR(buf);
return 0;
return (int) m;
}
int partition_policy_determine_fstype(
const ImagePolicy *policy,
PartitionDesignator designator,
bool *ret_encrypted,
char **ret_fstype) {
_cleanup_free_ char *fstype = NULL;
PartitionPolicyFlags policy_flags;
int r;
assert(designator >= 0 && designator < _PARTITION_DESIGNATOR_MAX);
assert(ret_fstype);
policy_flags = image_policy_get_exhaustively(policy, designator);
if (policy_flags < 0)
return policy_flags;
/* The policy fstype flags translate to the literal fstype name of each filesystem. */
r = partition_policy_flags_to_string(policy_flags & _PARTITION_POLICY_FSTYPE_MASK, /* simplify= */ true, &fstype);
if (r < 0)
return r;
/* Input must be a single filesystem type, if the policy specifies more than one, return NULL */
if (r != 1) {
if (ret_encrypted)
*ret_encrypted = false;
*ret_fstype = NULL;
return 0;
}
/* If the policy also allows unprotected or verity filesystems, don't set the 'encrypted' flag */
if (ret_encrypted)
*ret_encrypted = (policy_flags & (PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY)) &&
!(policy_flags & (PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED));
*ret_fstype = TAKE_PTR(fstype);
return 1;
}
static bool partition_policy_flags_extended_equal(PartitionPolicyFlags a, PartitionPolicyFlags b) {
+15
View File
@@ -36,6 +36,19 @@ typedef enum PartitionPolicyFlags {
_PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
/* Policies can impose filesystem type requirements on images. These flags translate to the literal
* fstype name of each filesystem. */
PARTITION_POLICY_BTRFS = 1 << 11,
PARTITION_POLICY_EROFS = 1 << 12,
PARTITION_POLICY_EXT4 = 1 << 13,
PARTITION_POLICY_F2FS = 1 << 14,
PARTITION_POLICY_SQUASHFS = 1 << 15,
PARTITION_POLICY_VFAT = 1 << 16,
PARTITION_POLICY_XFS = 1 << 17,
_PARTITION_POLICY_FSTYPE_MASK = PARTITION_POLICY_BTRFS|PARTITION_POLICY_EROFS|PARTITION_POLICY_EXT4|
PARTITION_POLICY_F2FS|PARTITION_POLICY_SQUASHFS|
PARTITION_POLICY_VFAT|PARTITION_POLICY_XFS,
_PARTITION_POLICY_FLAGS_INVALID = -EINVAL,
_PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */
} PartitionPolicyFlags;
@@ -84,6 +97,8 @@ PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags);
PartitionPolicyFlags partition_policy_flags_from_string(const char *s, bool graceful);
int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret);
int partition_policy_determine_fstype(const ImagePolicy *policy, PartitionDesignator designator, bool *ret_encrypted, char **ret_fstype);
int image_policy_from_string(const char *s, bool graceful, ImagePolicy **ret);
int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret);
+68
View File
@@ -92,6 +92,10 @@ TEST_RET(test_image_policy_to_string) {
test_policy_string("swap=open:root=signed+read-only-on+growfs-off:=absent");
test_policy_string("=-");
test_policy_string("=");
test_policy_string("root=ext4+squashfs+verity");
test_policy_string("usr=encrypted+erofs+read-only-off");
test_policy_string("home=unprotected+btrfs");
test_policy_string("=vfat+erofs");
test_policy_equiv("", image_policy_equiv_ignore);
test_policy_equiv("-", image_policy_equiv_ignore);
@@ -197,4 +201,68 @@ TEST(image_policy_ignore_designators) {
test_policy_ignore_designators_one("~", ((const PartitionDesignator[]) { PARTITION_VAR, PARTITION_ESP, PARTITION_VAR }), 2, "var=ignore:esp=ignore:=absent");
}
TEST(partition_policy_determine_fstype) {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
_cleanup_free_ char *fstype = NULL;
bool encrypted;
int r;
ASSERT_OK(image_policy_from_string("root=ext4+encrypted", /* graceful= */ false, &p));
r = partition_policy_determine_fstype(p, PARTITION_ROOT, &encrypted, &fstype);
ASSERT_GT(r, 0);
ASSERT_STREQ(fstype, "ext4");
ASSERT_TRUE(encrypted);
fstype = mfree(fstype);
p = image_policy_free(p);
ASSERT_OK(image_policy_from_string("usr=verity+erofs", /* graceful= */ false, &p));
r = partition_policy_determine_fstype(p, PARTITION_USR, &encrypted, &fstype);
ASSERT_GT(r, 0);
ASSERT_STREQ(fstype, "erofs");
ASSERT_FALSE(encrypted);
fstype = mfree(fstype);
r = partition_policy_determine_fstype(p, PARTITION_ROOT, &encrypted, &fstype);
ASSERT_EQ(r, 0);
ASSERT_FALSE(fstype);
ASSERT_FALSE(encrypted);
fstype = mfree(fstype);
p = image_policy_free(p);
ASSERT_OK(image_policy_from_string("root=ext4+erofs", /* graceful= */ false, &p));
r = partition_policy_determine_fstype(p, PARTITION_ROOT, &encrypted, &fstype);
ASSERT_EQ(r, 0);
ASSERT_FALSE(fstype);
ASSERT_FALSE(encrypted);
fstype = mfree(fstype);
p = image_policy_free(p);
ASSERT_OK(image_policy_from_string("root=encrypted", /* graceful= */ false, &p));
r = partition_policy_determine_fstype(p, PARTITION_ROOT, &encrypted, &fstype);
ASSERT_EQ(r, 0);
ASSERT_FALSE(fstype);
ASSERT_FALSE(encrypted);
fstype = mfree(fstype);
p = image_policy_free(p);
ASSERT_OK(image_policy_from_string("", /* graceful= */ false, &p));
r = partition_policy_determine_fstype(p, PARTITION_ROOT, &encrypted, &fstype);
ASSERT_EQ(r, 0);
ASSERT_FALSE(fstype);
ASSERT_FALSE(encrypted);
fstype = mfree(fstype);
r = partition_policy_determine_fstype(/* policy= */ NULL, PARTITION_ROOT, &encrypted, &fstype);
ASSERT_EQ(r, 0);
ASSERT_FALSE(fstype);
ASSERT_FALSE(encrypted);
fstype = mfree(fstype);
p = image_policy_free(p);
ASSERT_OK(image_policy_from_string("root=encrypted+signed+btrfs", /* graceful= */ false, &p));
r = partition_policy_determine_fstype(p, PARTITION_ROOT, &encrypted, &fstype);
ASSERT_GT(r, 0);
ASSERT_STREQ(fstype, "btrfs");
ASSERT_FALSE(encrypted);
}
DEFINE_TEST_MAIN(LOG_INFO);
+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