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:
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user