Merge pull request #25608 from poettering/dissect-moar

dissect: add dissection policies
This commit is contained in:
Lennart Poettering
2023-04-12 13:46:08 +02:00
committed by GitHub
73 changed files with 2569 additions and 269 deletions
+4 -1
View File
@@ -959,7 +959,10 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ProcSubset",
"NetworkNamespacePath",
"IPCNamespacePath",
"LogNamespace"))
"LogNamespace",
"RootImagePolicy",
"MountImagePolicy",
"ExtensionImagePolicy"))
return bus_append_string(m, field, eq);
if (STR_IN_SET(field, "IgnoreSIGPIPE",
+29 -5
View File
@@ -28,6 +28,7 @@
#include "hashmap.h"
#include "hostname-setup.h"
#include "id128-util.h"
#include "initrd-util.h"
#include "lock-util.h"
#include "log.h"
#include "loop-util.h"
@@ -73,6 +74,19 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
"/usr/lib/confexts\0",
};
/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension
* search dir) */
static const char* const image_search_path_initrd[_IMAGE_CLASS_MAX] = {
/* (entries that aren't listed here will get the same search path as for the non initrd-case) */
[IMAGE_EXTENSION] = "/etc/extensions\0" /* only place symlinks here */
"/run/extensions\0" /* and here too */
"/var/lib/extensions\0" /* the main place for images */
"/usr/local/lib/extensions\0"
"/usr/lib/extensions\0"
"/.extra/sysext\0" /* put sysext picked up by systemd-stub last, since not trusted */
};
static Image *image_free(Image *i) {
assert(i);
@@ -446,6 +460,14 @@ static int image_make(
return -EMEDIUMTYPE;
}
static const char *pick_image_search_path(ImageClass class) {
if (class < 0 || class >= _IMAGE_CLASS_MAX)
return NULL;
/* Use the initrd search path if there is one, otherwise use the common one */
return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
}
int image_find(ImageClass class,
const char *name,
const char *root,
@@ -461,7 +483,7 @@ int image_find(ImageClass class,
if (!image_name_is_valid(name))
return -ENOENT;
NULSTR_FOREACH(path, image_search_path[class]) {
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
struct stat st;
@@ -560,7 +582,7 @@ int image_discover(
assert(class < _IMAGE_CLASS_MAX);
assert(h);
NULSTR_FOREACH(path, image_search_path[class]) {
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
@@ -1138,7 +1160,7 @@ int image_set_limit(Image *i, uint64_t referenced_max) {
return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
}
int image_read_metadata(Image *i) {
int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
_cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
int r;
@@ -1219,7 +1241,9 @@ int image_read_metadata(Image *i) {
r = dissect_loop_device(
d,
NULL, NULL,
/* verity= */ NULL,
/* mount_options= */ NULL,
image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |
@@ -1287,7 +1311,7 @@ bool image_in_search_path(
assert(image);
NULSTR_FOREACH(path, image_search_path[class]) {
NULSTR_FOREACH(path, pick_image_search_path(class)) {
const char *p, *q;
size_t k;
+2 -1
View File
@@ -7,6 +7,7 @@
#include "sd-id128.h"
#include "hashmap.h"
#include "image-policy.h"
#include "lock-util.h"
#include "macro.h"
#include "os-util.h"
@@ -75,7 +76,7 @@ int image_name_lock(const char *name, int operation, LockFile *ret);
int image_set_limit(Image *i, uint64_t referenced_max);
int image_read_metadata(Image *i);
int image_read_metadata(Image *i, const ImagePolicy *image_policy);
bool image_in_search_path(ImageClass class, const char *root, const char *image);
+335 -93
View File
@@ -301,7 +301,99 @@ not_found:
}
#if HAVE_BLKID
static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
static int image_policy_may_use(
const ImagePolicy *policy,
PartitionDesignator designator) {
PartitionPolicyFlags f;
/* For each partition we find in the partition table do a first check if it may exist at all given
* the policy, or if it shall be ignored. */
f = image_policy_get_exhaustively(policy, designator);
if (f < 0)
return f;
if ((f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
/* only flag set in policy is "absent"? then this partition may not exist at all */
return log_debug_errno(
SYNTHETIC_ERRNO(ERFKILL),
"Partition of designator '%s' exists, but not allowed by policy, refusing.",
partition_designator_to_string(designator));
if ((f & _PARTITION_POLICY_USE_MASK & ~PARTITION_POLICY_ABSENT) == PARTITION_POLICY_UNUSED) {
/* only "unused" or "unused" + "absent" are set? then don't use it */
log_debug("Partition of designator '%s' exists, and policy dictates to ignore it, doing so.",
partition_designator_to_string(designator));
return false; /* ignore! */
}
return true; /* use! */
}
static int image_policy_check_protection(
const ImagePolicy *policy,
PartitionDesignator designator,
PartitionPolicyFlags found_flags) {
PartitionPolicyFlags policy_flags;
/* Checks if the flags in the policy for the designated partition overlap the flags of what we found */
if (found_flags < 0)
return found_flags;
policy_flags = image_policy_get_exhaustively(policy, designator);
if (policy_flags < 0)
return policy_flags;
if ((found_flags & policy_flags) == 0) {
_cleanup_free_ char *found_flags_string = NULL, *policy_flags_string = NULL;
(void) partition_policy_flags_to_string(found_flags, /* simplify= */ true, &found_flags_string);
(void) partition_policy_flags_to_string(policy_flags, /* simplify= */ true, &policy_flags_string);
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s discovered with policy '%s' but '%s' was required, refusing.",
partition_designator_to_string(designator),
strnull(found_flags_string), strnull(policy_flags_string));
}
return 0;
}
static int image_policy_check_partition_flags(
const ImagePolicy *policy,
PartitionDesignator designator,
uint64_t gpt_flags) {
PartitionPolicyFlags policy_flags;
bool b;
/* Checks if the partition flags in the policy match reality */
policy_flags = image_policy_get_exhaustively(policy, designator);
if (policy_flags < 0)
return policy_flags;
b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_READ_ONLY);
if ((policy_flags & _PARTITION_POLICY_READ_ONLY_MASK) == (b ? PARTITION_POLICY_READ_ONLY_OFF : PARTITION_POLICY_READ_ONLY_ON))
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'read-only' flag incorrectly set (must be %s, is %s), refusing.",
partition_designator_to_string(designator),
one_zero(!b), one_zero(b));
b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_GROWFS);
if ((policy_flags & _PARTITION_POLICY_GROWFS_MASK) == (b ? PARTITION_POLICY_GROWFS_OFF : PARTITION_POLICY_GROWFS_ON))
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'growfs' flag incorrectly set (must be %s, is %s), refusing.",
partition_designator_to_string(designator),
one_zero(!b), one_zero(b));
return 0;
}
static int dissected_image_probe_filesystems(
DissectedImage *m,
int fd,
const ImagePolicy *policy) {
int r;
assert(m);
@@ -310,6 +402,7 @@ static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
DissectedPartition *p = m->partitions + i;
PartitionPolicyFlags found_flags;
if (!p->found)
continue;
@@ -325,14 +418,34 @@ static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
return r;
}
if (streq_ptr(p->fstype, "crypto_LUKS"))
if (streq_ptr(p->fstype, "crypto_LUKS")) {
m->encrypted = true;
found_flags = PARTITION_POLICY_ENCRYPTED; /* found this one, and its definitely encrypted */
} else
/* found it, but it's definitely not encrypted, hence mask the encrypted flag, but
* set all other ways that indicate "present". */
found_flags = PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED;
if (p->fstype && fstype_is_ro(p->fstype))
p->rw = false;
if (!p->rw)
p->growfs = false;
/* We might have learnt more about the file system now (i.e. whether it is encrypted or not),
* hence we need to validate this against policy again, to see if the policy still matches
* with this new information. Note that image_policy_check_protection() will check for
* overlap between what's allowed in the policy and what we pass as 'found_policy' here. In
* the unencrypted case we thus might pass an overly unspecific mask here (i.e. unprotected
* OR verity OR signed), but that's fine since the earlier policy check already checked more
* specific which of those three cases where OK. Keep in mind that this function here only
* looks at specific partitions (and thus can only deduce encryption or not) but not the
* overall partition table (and thus cannot deduce verity or not). The earlier dissection
* checks already did the relevant checks that look at the whole partition table, and
* enforced policy there as needed. */
r = image_policy_check_protection(policy, i, found_flags);
if (r < 0)
return r;
}
return 0;
@@ -363,9 +476,7 @@ static void check_partition_flags(
log_debug("Unexpected partition flag %llu set on %s!", bit, node);
}
}
#endif
#if HAVE_BLKID
static int dissected_image_new(const char *path, DissectedImage **ret) {
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_free_ char *name = NULL;
@@ -543,6 +654,7 @@ static int dissect_image(
const char *devname,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *policy,
DissectImageFlags flags) {
sd_id128_t root_uuid = SD_ID128_NULL, root_verity_uuid = SD_ID128_NULL;
@@ -572,7 +684,11 @@ static int dissect_image(
* Returns -ENOPKG if no suitable partition table or file system could be found.
* Returns -EADDRNOTAVAIL if a root hash was specified but no matching root/verity partitions found.
* Returns -ENXIO if we couldn't find any partition suitable as root or /usr partition
* Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that */
* Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that
* Returns -ERFKILL if image doesn't match image policy
* Returns -EBADR if verity data was provided externally for an image that has a GPT partition table (i.e. is not just a naked fs)
* Returns -EPROTONOSUPPORT if DISSECT_IMAGE_ADD_PARTITION_DEVICES is set but the block device does not have partition logic enabled
* Returns -ENOMSG if we didn't find a single usable partition (and DISSECT_IMAGE_REFUSE_EMPTY is set) */
uint64_t diskseq = m->loop ? m->loop->diskseq : 0;
@@ -650,6 +766,34 @@ static int dissect_image(
const char *fstype = NULL, *options = NULL, *suuid = 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. */
r = image_policy_may_use(policy, PARTITION_ROOT);
if (r < 0)
return r;
if (r == 0) /* policy says ignore this, so we ignore it */
return -ENOPKG;
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
(void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
encrypted = streq_ptr(fstype, "crypto_LUKS");
if (verity_settings_data_covers(verity, PARTITION_ROOT))
found_flags = verity->root_hash_sig ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY;
else
found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : 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);
@@ -657,10 +801,6 @@ static int dissect_image(
return mount_node_fd;
}
/* OK, we have found a file system, that's our root partition then. */
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
(void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
if (fstype) {
t = strdup(fstype);
if (!t)
@@ -681,7 +821,7 @@ static int dissect_image(
return r;
m->single_file_system = true;
m->encrypted = streq_ptr(fstype, "crypto_LUKS");
m->encrypted = encrypted;
m->has_verity = verity && verity->data_path;
m->verity_ready = verity_settings_data_covers(verity, PARTITION_ROOT);
@@ -1049,6 +1189,18 @@ static int dissect_image(
_cleanup_close_ int mount_node_fd = -EBADF;
const char *options = NULL;
r = image_policy_may_use(policy, type.designator);
if (r < 0)
return r;
if (r == 0) {
/* Policy says: ignore; Remember this fact, so that we later can distinguish between "found but ignored" and "not found at all" */
if (!m->partitions[type.designator].found)
m->partitions[type.designator].ignored = true;
continue;
}
if (m->partitions[type.designator].found) {
/* For most partition types the first one we see wins. Except for the
* rootfs and /usr, where we do a version compare of the label, and
@@ -1139,6 +1291,16 @@ static int dissect_image(
sd_id128_t id = SD_ID128_NULL;
const char *options = NULL;
r = image_policy_may_use(policy, PARTITION_XBOOTLDR);
if (r < 0)
return r;
if (r == 0) { /* policy says: ignore */
if (!m->partitions[PARTITION_XBOOTLDR].found)
m->partitions[PARTITION_XBOOTLDR].ignored = true;
continue;
}
/* First one wins */
if (m->partitions[PARTITION_XBOOTLDR].found)
continue;
@@ -1223,41 +1385,49 @@ static int dissect_image(
/* If we didn't find a generic node, then we can't fix this up either */
if (generic_node) {
_cleanup_close_ int mount_node_fd = -EBADF;
_cleanup_free_ char *o = NULL, *n = NULL;
const char *options;
if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
if (mount_node_fd < 0)
return mount_node_fd;
}
r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
r = image_policy_may_use(policy, PARTITION_ROOT);
if (r < 0)
return r;
if (r == 0)
/* Policy says: ignore; remember that we did */
m->partitions[PARTITION_ROOT].ignored = true;
else {
_cleanup_close_ int mount_node_fd = -EBADF;
_cleanup_free_ char *o = NULL, *n = NULL;
const char *options;
options = mount_options_from_designator(mount_options, PARTITION_ROOT);
if (options) {
o = strdup(options);
if (!o)
return -ENOMEM;
if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
if (mount_node_fd < 0)
return mount_node_fd;
}
r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
if (r < 0)
return r;
options = mount_options_from_designator(mount_options, PARTITION_ROOT);
if (options) {
o = strdup(options);
if (!o)
return -ENOMEM;
}
assert(generic_nr >= 0);
m->partitions[PARTITION_ROOT] = (DissectedPartition) {
.found = true,
.rw = generic_rw,
.growfs = generic_growfs,
.partno = generic_nr,
.architecture = _ARCHITECTURE_INVALID,
.node = TAKE_PTR(n),
.uuid = generic_uuid,
.mount_options = TAKE_PTR(o),
.mount_node_fd = TAKE_FD(mount_node_fd),
.offset = UINT64_MAX,
.size = UINT64_MAX,
};
}
assert(generic_nr >= 0);
m->partitions[PARTITION_ROOT] = (DissectedPartition) {
.found = true,
.rw = generic_rw,
.growfs = generic_growfs,
.partno = generic_nr,
.architecture = _ARCHITECTURE_INVALID,
.node = TAKE_PTR(n),
.uuid = generic_uuid,
.mount_options = TAKE_PTR(o),
.mount_node_fd = TAKE_FD(mount_node_fd),
.offset = UINT64_MAX,
.size = UINT64_MAX,
};
}
}
@@ -1319,7 +1489,42 @@ static int dissect_image(
}
}
r = dissected_image_probe_filesystems(m, fd);
bool any = false;
/* After we discovered all partitions let's see if the verity requirements match the policy. (Note:
* we don't check encryption requirements here, because we haven't probed the file system yet, hence
* don't know if this is encrypted or not) */
for (PartitionDesignator di = 0; di < _PARTITION_DESIGNATOR_MAX; di++) {
PartitionDesignator vi, si;
PartitionPolicyFlags found_flags;
any = any || m->partitions[di].found;
vi = partition_verity_of(di);
si = partition_verity_sig_of(di);
/* Determine the verity protection level for this partition. */
found_flags = m->partitions[di].found ?
(vi >= 0 && m->partitions[vi].found ?
(si >= 0 && m->partitions[si].found ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY) :
PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED) :
(m->partitions[di].ignored ? PARTITION_POLICY_UNUSED : PARTITION_POLICY_ABSENT);
r = image_policy_check_protection(policy, di, found_flags);
if (r < 0)
return r;
if (m->partitions[di].found) {
r = image_policy_check_partition_flags(policy, di, m->partitions[di].gpt_flags);
if (r < 0)
return r;
}
}
if (!any && !FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_EMPTY))
return -ENOMSG;
r = dissected_image_probe_filesystems(m, fd, policy);
if (r < 0)
return r;
@@ -1331,6 +1536,7 @@ int dissect_image_file(
const char *path,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
@@ -1340,7 +1546,6 @@ int dissect_image_file(
int r;
assert(path);
assert(ret);
fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (fd < 0)
@@ -1358,17 +1563,81 @@ int dissect_image_file(
if (r < 0)
return r;
r = dissect_image(m, fd, path, verity, mount_options, flags);
r = dissect_image(m, fd, path, verity, mount_options, image_policy, flags);
if (r < 0)
return r;
*ret = TAKE_PTR(m);
if (ret)
*ret = TAKE_PTR(m);
return 0;
#else
return -EOPNOTSUPP;
#endif
}
static int dissect_log_error(int r, const char *name, const VeritySettings *verity) {
assert(name);
switch (r) {
case 0 ... INT_MAX: /* success! */
return r;
case -EOPNOTSUPP:
return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
case -ENOPKG:
return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
case -ENOMEDIUM:
return log_error_errno(r, "%s: The image does not pass os-release/extension-release validation.", name);
case -EADDRNOTAVAIL:
return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
case -ENOTUNIQ:
return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
case -ENXIO:
return log_error_errno(r, "%s: No suitable root partition found in image.", name);
case -EPROTONOSUPPORT:
return log_error_errno(r, "Device '%s' is a loopback block device with partition scanning turned off, please turn it on.", name);
case -ENOTBLK:
return log_error_errno(r, "%s: Image is not a block device.", name);
case -EBADR:
return log_error_errno(r,
"Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
"(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
name, strna(verity ? verity->data_path : NULL));
case -ERFKILL:
return log_error_errno(r, "%s: image does not match image policy.", name);
case -ENOMSG:
return log_error_errno(r, "%s: no suitable partitions found.", name);
default:
return log_error_errno(r, "Failed to dissect image '%s': %m", name);
}
}
int dissect_image_file_and_warn(
const char *path,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
return dissect_log_error(
dissect_image_file(path, verity, mount_options, image_policy, flags, ret),
path,
verity);
}
DissectedImage* dissected_image_unref(DissectedImage *m) {
if (!m)
return NULL;
@@ -3250,6 +3519,7 @@ int dissect_loop_device(
LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
@@ -3258,7 +3528,6 @@ int dissect_loop_device(
int r;
assert(loop);
assert(ret);
r = dissected_image_new(loop->backing_file ?: loop->node, &m);
if (r < 0)
@@ -3267,11 +3536,13 @@ int dissect_loop_device(
m->loop = loop_device_ref(loop);
m->sector_size = m->loop->sector_size;
r = dissect_image(m, loop->fd, loop->node, verity, mount_options, flags);
r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags);
if (r < 0)
return r;
*ret = TAKE_PTR(m);
if (ret)
*ret = TAKE_PTR(m);
return 0;
#else
return -EOPNOTSUPP;
@@ -3282,56 +3553,17 @@ int dissect_loop_device_and_warn(
LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
const char *name;
int r;
assert(loop);
assert(loop->fd >= 0);
name = ASSERT_PTR(loop->backing_file ?: loop->node);
return dissect_log_error(
dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret),
loop->backing_file ?: loop->node,
verity);
r = dissect_loop_device(loop, verity, mount_options, flags, ret);
switch (r) {
case -EOPNOTSUPP:
return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
case -ENOPKG:
return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
case -ENOMEDIUM:
return log_error_errno(r, "%s: The image does not pass validation.", name);
case -EADDRNOTAVAIL:
return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
case -ENOTUNIQ:
return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
case -ENXIO:
return log_error_errno(r, "%s: No suitable root partition found in image.", name);
case -EPROTONOSUPPORT:
return log_error_errno(r, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", name);
case -ENOTBLK:
return log_error_errno(r, "%s: Image is not a block device.", name);
case -EBADR:
return log_error_errno(r,
"Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
"(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
name, strna(verity ? verity->data_path : NULL));
default:
if (r < 0)
return log_error_errno(r, "Failed to dissect image '%s': %m", name);
return r;
}
}
bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesignator partition_designator) {
@@ -3407,6 +3639,7 @@ const char* mount_options_from_designator(const MountOptions *options, Partition
int mount_image_privately_interactively(
const char *image,
const ImagePolicy *image_policy,
DissectImageFlags flags,
char **ret_directory,
int *ret_dir_fd,
@@ -3449,7 +3682,13 @@ int mount_image_privately_interactively(
if (r < 0)
return log_error_errno(r, "Failed to set up loopback device for %s: %m", image);
r = dissect_loop_device_and_warn(d, &verity, NULL, flags, &dissected_image);
r = dissect_loop_device_and_warn(
d,
&verity,
/* mount_options= */ NULL,
image_policy,
flags,
&dissected_image);
if (r < 0)
return r;
@@ -3513,6 +3752,7 @@ int verity_dissect_and_mount(
const char *src,
const char *dest,
const MountOptions *options,
const ImagePolicy *image_policy,
const char *required_host_os_release_id,
const char *required_host_os_release_version_id,
const char *required_host_os_release_sysext_level,
@@ -3556,6 +3796,7 @@ int verity_dissect_and_mount(
loop_device,
&verity,
options,
image_policy,
dissect_image_flags,
&dissected_image);
/* No partition table? Might be a single-filesystem image, try again */
@@ -3564,6 +3805,7 @@ int verity_dissect_and_mount(
loop_device,
&verity,
options,
image_policy,
dissect_image_flags | DISSECT_IMAGE_NO_PARTITION_TABLE,
&dissected_image);
if (r < 0)
+12 -10
View File
@@ -19,6 +19,7 @@ typedef struct VeritySettings VeritySettings;
struct DissectedPartition {
bool found:1;
bool ignored:1;
bool rw:1;
bool growfs:1;
int partno; /* -1 if there was no partition and the images contains a file system directly */
@@ -79,6 +80,7 @@ typedef enum DissectImageFlags {
DISSECT_IMAGE_PIN_PARTITION_DEVICES = 1 << 21, /* Open dissected partitions and decrypted partitions and pin them by fd */
DISSECT_IMAGE_RELAX_SYSEXT_CHECK = 1 << 22, /* Don't insist that the extension-release file name matches the image name */
DISSECT_IMAGE_DISKSEQ_DEVNODE = 1 << 23, /* Prefer /dev/disk/by-diskseq/… device nodes */
DISSECT_IMAGE_ALLOW_EMPTY = 1 << 24, /* Allow that no usable partitions is present */
} DissectImageFlags;
struct DissectedImage {
@@ -133,6 +135,9 @@ struct VeritySettings {
.designator = _PARTITION_DESIGNATOR_INVALID \
}
/* We include image-policy.h down here, since ImagePolicy wants a complete definition of PartitionDesignator first. */
#include "image-policy.h"
MountOptions* mount_options_free_all(MountOptions *options);
DEFINE_TRIVIAL_CLEANUP_FUNC(MountOptions*, mount_options_free_all);
const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator);
@@ -141,14 +146,11 @@ int probe_filesystem_full(int fd, const char *path, uint64_t offset, uint64_t si
static inline int probe_filesystem(const char *path, char **ret_fstype) {
return probe_filesystem_full(-1, path, 0, UINT64_MAX, ret_fstype);
}
int dissect_image_file(
const char *path,
const VeritySettings *verity,
const MountOptions *mount_options,
DissectImageFlags flags,
DissectedImage **ret);
int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
int dissect_image_file(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
@@ -185,9 +187,9 @@ bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesi
bool dissected_image_verity_ready(const DissectedImage *image, PartitionDesignator d);
bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesignator d);
int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
int mount_image_privately_interactively(const char *path, const ImagePolicy *image_policy, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const ImagePolicy *image_policy, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
int dissect_fstype_ok(const char *fstype);
+689
View File
@@ -0,0 +1,689 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "extract-word.h"
#include "image-policy.h"
#include "logarithm.h"
#include "sort-util.h"
#include "string-util.h"
#include "strv.h"
/* Rationale for the chosen syntax:
*
* → one line, so that it can be reasonably added to a shell command line, for example via `systemd-dissect
* --image-policy=…` or to the kernel command line via `systemd.image_policy=`.
*
* → no use of "," or ";" as separators, so that it can be included in mount/fstab-style option strings and
* doesn't require escaping. Instead, separators are ":", "=", "+" which should be fine both in shell
* command lines and in mount/fstab style option strings.
*/
static int partition_policy_compare(const PartitionPolicy *a, const PartitionPolicy *b) {
return CMP(ASSERT_PTR(a)->designator, ASSERT_PTR(b)->designator);
}
static PartitionPolicy* image_policy_bsearch(const ImagePolicy *policy, PartitionDesignator designator) {
if (!policy)
return NULL;
return typesafe_bsearch(
&(PartitionPolicy) { .designator = designator },
ASSERT_PTR(policy)->policies,
ASSERT_PTR(policy)->n_policies,
partition_policy_compare);
}
static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) {
PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags;
/* This normalizes the per-partition policy flags. This means if the user left some things
* unspecified, we'll fill in the appropriate "dontcare" policy instead. We'll also mask out bits
* that do not make any sense for specific partition types. */
/* If no protection flag is set, then this means all are set */
if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
flags |= PARTITION_POLICY_OPEN;
/* If this is a verity or verity signature designator, then mask off all protection bits, this after
* all needs no protection, because it *is* the protection */
if (partition_verity_to_data(policy->designator) >= 0 ||
partition_verity_sig_to_data(policy->designator) >= 0)
flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED);
/* if this designator has no verity concept, then mask off verity protection flags */
if (partition_verity_of(policy->designator) < 0)
flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED);
if ((flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
/* If the partition must be absent, then the gpt flags don't matter */
flags &= ~(_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK);
else {
/* If the gpt flags bits are not specified, set both options for each */
if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
flags |= PARTITION_POLICY_READ_ONLY_ON|PARTITION_POLICY_READ_ONLY_OFF;
if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
flags |= PARTITION_POLICY_GROWFS_ON|PARTITION_POLICY_GROWFS_OFF;
}
return flags;
}
PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator) {
PartitionDesignator data_designator = _PARTITION_DESIGNATOR_INVALID;
PartitionPolicy *pp;
/* No policy means: everything may be used in any mode */
if (!policy)
return partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = PARTITION_POLICY_OPEN,
.designator = designator,
});
pp = image_policy_bsearch(policy, designator);
if (pp)
return partition_policy_normalized_flags(pp);
/* Hmm, so this didn't work, then let's see if we can derive some policy from the underlying data
* partition in case of verity/signature partitions */
data_designator = partition_verity_to_data(designator);
if (data_designator >= 0) {
PartitionPolicyFlags data_flags;
/* So we are asked for the policy for a verity partition, and there's no explicit policy for
* that case. Let's synthesize a policy from the protection setting for the underlying data
* partition. */
data_flags = image_policy_get(policy, data_designator);
if (data_flags < 0)
return data_flags;
/* We need verity if verity or verity with sig is requested */
if (!(data_flags & (PARTITION_POLICY_SIGNED|PARTITION_POLICY_VERITY)))
return _PARTITION_POLICY_FLAGS_INVALID;
/* If the data partition may be unused or absent, then the verity partition may too. Also, inherit the partition flags policy */
return partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
(data_flags & _PARTITION_POLICY_PFLAGS_MASK),
.designator = designator,
});
}
data_designator = partition_verity_sig_to_data(designator);
if (data_designator >= 0) {
PartitionPolicyFlags data_flags;
/* Similar case as for verity partitions, but slightly more strict rules */
data_flags = image_policy_get(policy, data_designator);
if (data_flags < 0)
return data_flags;
if (!(data_flags & PARTITION_POLICY_SIGNED))
return _PARTITION_POLICY_FLAGS_INVALID;
return partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
(data_flags & _PARTITION_POLICY_PFLAGS_MASK),
.designator = designator,
});
}
return _PARTITION_POLICY_FLAGS_INVALID; /* got nothing */
}
PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator) {
PartitionPolicyFlags flags;
/* This is just like image_policy_get() but whenever there is no policy for a specific designator, we
* return the default policy. */
flags = image_policy_get(policy, designator);
if (flags < 0)
return partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = image_policy_default(policy),
.designator = designator,
});
return flags;
}
static PartitionPolicyFlags policy_flag_from_string_one(const char *s) {
assert(s);
/* This is a bitmask (i.e. not dense), hence we don't use the "string-table.h" stuff here. */
if (streq(s, "verity"))
return PARTITION_POLICY_VERITY;
if (streq(s, "signed"))
return PARTITION_POLICY_SIGNED;
if (streq(s, "encrypted"))
return PARTITION_POLICY_ENCRYPTED;
if (streq(s, "unprotected"))
return PARTITION_POLICY_UNPROTECTED;
if (streq(s, "unused"))
return PARTITION_POLICY_UNUSED;
if (streq(s, "absent"))
return PARTITION_POLICY_ABSENT;
if (streq(s, "open")) /* shortcut alias */
return PARTITION_POLICY_OPEN;
if (streq(s, "ignore")) /* ditto */
return PARTITION_POLICY_IGNORE;
if (streq(s, "read-only-on"))
return PARTITION_POLICY_READ_ONLY_ON;
if (streq(s, "read-only-off"))
return PARTITION_POLICY_READ_ONLY_OFF;
if (streq(s, "growfs-on"))
return PARTITION_POLICY_GROWFS_ON;
if (streq(s, "growfs-off"))
return PARTITION_POLICY_GROWFS_OFF;
return _PARTITION_POLICY_FLAGS_INVALID;
}
PartitionPolicyFlags partition_policy_flags_from_string(const char *s) {
PartitionPolicyFlags flags = 0;
int r;
assert(s);
if (empty_or_dash(s))
return 0;
for (;;) {
_cleanup_free_ char *f = NULL;
PartitionPolicyFlags ff;
r = extract_first_word(&s, &f, "+", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0)
break;
ff = policy_flag_from_string_one(strstrip(f));
if (ff < 0)
return -EBADRQC; /* recognizable error */
flags |= ff;
}
return flags;
}
static ImagePolicy* image_policy_new(size_t n_policies) {
ImagePolicy *p;
if (n_policies > (SIZE_MAX - offsetof(ImagePolicy, policies)) / sizeof(PartitionPolicy)) /* overflow check */
return NULL;
p = malloc(offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * n_policies);
if (!p)
return NULL;
*p = (ImagePolicy) {
.default_flags = PARTITION_POLICY_IGNORE,
};
return p;
}
int image_policy_from_string(const char *s, ImagePolicy **ret) {
_cleanup_free_ ImagePolicy *p = NULL;
uint64_t dmask = 0;
ImagePolicy *t;
PartitionPolicyFlags symbolic_policy;
int r;
assert(s);
assert_cc(sizeof(dmask) * 8 >= _PARTITION_DESIGNATOR_MAX);
/* Recognizable errors:
*
* ENOTUNIQ → Two or more rules for the same partition
* EBADSLT → Unknown partition designator
* EBADRQC → Unknown policy flags
*/
/* First, let's handle "symbolic" policies, i.e. "-", "*", "~" */
if (empty_or_dash(s))
/* ignore policy: everything may exist, but nothing used */
symbolic_policy = PARTITION_POLICY_IGNORE;
else if (streq(s, "*"))
/* allow policy: everything is allowed */
symbolic_policy = PARTITION_POLICY_OPEN;
else if (streq(s, "~"))
/* deny policy: nothing may exist */
symbolic_policy = PARTITION_POLICY_ABSENT;
else
symbolic_policy = _PARTITION_POLICY_FLAGS_INVALID;
if (symbolic_policy >= 0) {
if (!ret)
return 0;
p = image_policy_new(0);
if (!p)
return -ENOMEM;
p->default_flags = symbolic_policy;
*ret = TAKE_PTR(p);
return 0;
}
/* Allocate the policy at maximum size, i.e. for all designators. We might overshoot a bit, but the
* items are cheap, and we can return unused space to libc once we know we don't need it */
p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
if (!p)
return -ENOMEM;
const char *q = s;
bool default_specified = false;
for (;;) {
_cleanup_free_ char *e = NULL, *d = NULL;
PartitionDesignator designator;
PartitionPolicyFlags flags;
char *f, *ds, *fs;
r = extract_first_word(&q, &e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0)
break;
f = e;
r = extract_first_word((const char**) &f, &d, "=", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Expected designator name followed by '='; got instead: %s", e);
if (!f) /* no separator? */
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing '=' in policy expression: %s", e);
ds = strstrip(d);
if (isempty(ds)) {
/* Not partition name? then it's the default policy */
if (default_specified)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Default partition policy flags specified more than once.");
designator = _PARTITION_DESIGNATOR_INVALID;
default_specified = true;
} else {
designator = partition_designator_from_string(ds);
if (designator < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT), "Unknown partition designator: %s", ds); /* recognizable error */
if (dmask & (UINT64_C(1) << designator))
return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition designator specified more than once: %s", ds);
dmask |= UINT64_C(1) << designator;
}
fs = strstrip(f);
flags = partition_policy_flags_from_string(fs);
if (flags == -EBADRQC)
return log_debug_errno(flags, "Unknown partition policy flag: %s", fs);
if (flags < 0)
return log_debug_errno(flags, "Failed to parse partition policy flags '%s': %m", fs);
if (designator < 0)
p->default_flags = flags;
else {
p->policies[p->n_policies++] = (PartitionPolicy) {
.designator = designator,
.flags = flags,
};
}
};
assert(p->n_policies <= _PARTITION_DESIGNATOR_MAX);
/* Return unused space to libc */
t = realloc(p, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * p->n_policies);
if (t)
p = t;
typesafe_qsort(p->policies, p->n_policies, partition_policy_compare);
if (ret)
*ret = TAKE_PTR(p);
return 0;
}
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]; /* one string per known flag at most */
size_t m = 0;
assert(ret);
if (flags < 0)
return -EINVAL;
/* If 'simplify' is false we'll output the precise value of every single flag.
*
* If 'simplify' is true we'll try to make the output shorter, by doing the following:
*
* → we'll spell the long form "verity+signed+encrypted+unprotected+unused+absent" via its
* equivalent shortcut form "open" (which we happily parse btw, see above)
*
* → we'll spell the long form "unused+absent" via its shortcut "ignore" (which we are also happy
* to parse)
*
* → if the read-only/growfs policy flags are both set, we suppress them. this thus removes the
* distinction between "user explicitly declared don't care" and "we implied don't care because
* user didn't say anything".
*
* net result: the resulting string is shorter, but the effective policy declared that way will have
* the same results as the long form. */
if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_OPEN)
l[m++] = "open";
else if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE)
l[m++] = "ignore";
else {
if (flags & PARTITION_POLICY_VERITY)
l[m++] = "verity";
if (flags & PARTITION_POLICY_SIGNED)
l[m++] = "signed";
if (flags & PARTITION_POLICY_ENCRYPTED)
l[m++] = "encrypted";
if (flags & PARTITION_POLICY_UNPROTECTED)
l[m++] = "unprotected";
if (flags & PARTITION_POLICY_UNUSED)
l[m++] = "unused";
if (flags & PARTITION_POLICY_ABSENT)
l[m++] = "absent";
}
if (!simplify || (!(flags & PARTITION_POLICY_READ_ONLY_ON) != !(flags & PARTITION_POLICY_READ_ONLY_OFF))) {
if (flags & PARTITION_POLICY_READ_ONLY_ON)
l[m++] = "read-only-on";
if (flags & PARTITION_POLICY_READ_ONLY_OFF)
l[m++] = "read-only-off";
}
if (!simplify || (!(flags & PARTITION_POLICY_GROWFS_ON) != !(flags & PARTITION_POLICY_GROWFS_OFF))) {
if (flags & PARTITION_POLICY_GROWFS_OFF)
l[m++] = "growfs-off";
if (flags & PARTITION_POLICY_GROWFS_ON)
l[m++] = "growfs-on";
}
if (m == 0)
buf = strdup("-");
else {
assert(m+1 < ELEMENTSOF(l));
l[m] = NULL;
buf = strv_join((char**) l, "+");
}
if (!buf)
return -ENOMEM;
*ret = TAKE_PTR(buf);
return 0;
}
static int image_policy_flags_all_match(const ImagePolicy *policy, PartitionPolicyFlags expected) {
if (expected < 0)
return -EINVAL;
if (image_policy_default(policy) != expected)
return false;
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
PartitionPolicyFlags f, w;
f = image_policy_get_exhaustively(policy, d);
if (f < 0)
return f;
w = partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = expected,
.designator = d,
});
if (w < 0)
return w;
if (f != w)
return false;
}
return true;
}
bool image_policy_equiv_ignore(const ImagePolicy *policy) {
/* Checks if this is the ignore policy (or equivalent to it), i.e. everything is ignored, aka '-', aka '' */
return image_policy_flags_all_match(policy, PARTITION_POLICY_IGNORE);
}
bool image_policy_equiv_allow(const ImagePolicy *policy) {
/* Checks if this is the allow policy (or equivalent to it), i.e. everything is allowed, aka '*' */
return image_policy_flags_all_match(policy, PARTITION_POLICY_OPEN);
}
bool image_policy_equiv_deny(const ImagePolicy *policy) {
/* Checks if this is the deny policy (or equivalent to it), i.e. everything must be absent, aka '~' */
return image_policy_flags_all_match(policy, PARTITION_POLICY_ABSENT);
}
int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret) {
_cleanup_free_ char *s = NULL;
int r;
assert(ret);
if (simplify) {
const char *fixed;
if (image_policy_equiv_allow(policy))
fixed = "*";
else if (image_policy_equiv_ignore(policy))
fixed = "-";
else if (image_policy_equiv_deny(policy))
fixed = "~";
else
fixed = NULL;
if (fixed) {
s = strdup(fixed);
if (!s)
return -ENOMEM;
*ret = TAKE_PTR(s);
return 0;
}
}
for (size_t i = 0; i < image_policy_n_entries(policy); i++) {
const PartitionPolicy *p = policy->policies + i;
_cleanup_free_ char *f = NULL;
const char *t;
assert(i == 0 || p->designator > policy->policies[i-1].designator); /* Validate perfect ordering */
assert_se(t = partition_designator_to_string(p->designator));
if (simplify) {
/* Skip policy entries that match the default anyway */
PartitionPolicyFlags df;
df = partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = image_policy_default(policy),
.designator = p->designator,
});
if (df < 0)
return df;
if (df == p->flags)
continue;
}
r = partition_policy_flags_to_string(p->flags, simplify, &f);
if (r < 0)
return r;
if (!strextend(&s, isempty(s) ? "" : ":", t, "=", f))
return -ENOMEM;
}
if (!simplify || image_policy_default(policy) != PARTITION_POLICY_IGNORE) {
_cleanup_free_ char *df = NULL;
r = partition_policy_flags_to_string(image_policy_default(policy), simplify, &df);
if (r < 0)
return r;
if (!strextend(&s, isempty(s) ? "" : ":", "=", df))
return -ENOMEM;
}
if (isempty(s)) { /* no rule and default policy? then let's return "-" */
s = strdup("-");
if (!s)
return -ENOMEM;
}
*ret = TAKE_PTR(s);
return 0;
}
bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b) {
if (a == b)
return true;
if (image_policy_n_entries(a) != image_policy_n_entries(b))
return false;
if (image_policy_default(a) != image_policy_default(b))
return false;
for (size_t i = 0; i < image_policy_n_entries(a); i++) {
if (a->policies[i].designator != b->policies[i].designator)
return false;
if (a->policies[i].flags != b->policies[i].flags)
return false;
}
return true;
}
int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b) {
/* The image_policy_equal() function checks if the policy is defined the exact same way. This
* function here instead looks at the outcome of the two policies instead. Where does this come to
* different results you ask? We imply some logic regarding Verity/Encryption: when no rule is
* defined for a verity partition we can synthesize it from the protection level of the data
* partition it protects. Or: any per-partition rule that is identical to the default rule is
* redundant, and will be recognized as such by image_policy_equivalent() but not by
* image_policy_equal()- */
if (image_policy_default(a) != image_policy_default(b))
return false;
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
PartitionPolicyFlags f, w;
f = image_policy_get_exhaustively(a, d);
if (f < 0)
return f;
w = image_policy_get_exhaustively(b, d);
if (w < 0)
return w;
if (f != w)
return false;
}
return true;
}
const ImagePolicy image_policy_allow = {
/* Allow policy */
.n_policies = 0,
.default_flags = PARTITION_POLICY_OPEN,
};
const ImagePolicy image_policy_deny = {
/* Allow policy */
.n_policies = 0,
.default_flags = PARTITION_POLICY_ABSENT,
};
const ImagePolicy image_policy_ignore = {
/* Allow policy */
.n_policies = 0,
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_sysext = {
/* For system extensions, honour root file system, and /usr/ and ignore everything else. After all,
* we are only interested in /usr/ + /opt/ trees anyway, and that's really the only place they can
* be. */
.n_policies = 2,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_sysext_strict = {
/* For system extensions, requiring signing */
.n_policies = 2,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_container = {
/* For systemd-nspawn containers we use all partitions, with the exception of swap */
.n_policies = 8,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_host = {
/* For the host policy we basically use everything */
.n_policies = 9,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_service = {
/* For RootImage= in services we skip ESP/XBOOTLDR and swap */
.n_policies = 6,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
+97
View File
@@ -0,0 +1,97 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
typedef struct ImagePolicy ImagePolicy;
#include "dissect-image.h"
#include "errno-list.h"
typedef enum PartitionPolicyFlags {
/* Not all policy flags really make sense on all partition types, see comments. But even if they
* don't make sense we'll parse them anyway, because maybe one day we'll add them for more partition
* types, too. Moreover, we allow configuring a "default" policy for all partition types for which no
* explicit policy is specified. It's useful if we can use policy flags in there and apply this
* default policy gracefully even to partition types where they don't really make too much sense
* on. Example: a default policy of "verity+encrypted" certainly makes sense, but for /home/
* partitions this gracefully degrades to "encrypted" (as we do not have a concept of verity for
* /home/), and so on. */
PARTITION_POLICY_VERITY = 1 << 0, /* must exist, activate with verity (only applies to root/usr partitions) */
PARTITION_POLICY_SIGNED = 1 << 1, /* must exist, activate with signed verity (only applies to root/usr partitions) */
PARTITION_POLICY_ENCRYPTED = 1 << 2, /* must exist, activate with LUKS encryption (applies to any data partition, but not to verity/signature partitions */
PARTITION_POLICY_UNPROTECTED = 1 << 3, /* must exist, activate without encryption/verity */
PARTITION_POLICY_UNUSED = 1 << 4, /* must exist, don't use */
PARTITION_POLICY_ABSENT = 1 << 5, /* must not exist */
PARTITION_POLICY_OPEN = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|
PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
PARTITION_POLICY_IGNORE = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
_PARTITION_POLICY_USE_MASK = PARTITION_POLICY_OPEN,
PARTITION_POLICY_READ_ONLY_OFF = 1 << 6, /* State of GPT partition flag "read-only" must be on */
PARTITION_POLICY_READ_ONLY_ON = 1 << 7,
_PARTITION_POLICY_READ_ONLY_MASK = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON,
PARTITION_POLICY_GROWFS_OFF = 1 << 8, /* State of GPT partition flag "growfs" must be on */
PARTITION_POLICY_GROWFS_ON = 1 << 9,
_PARTITION_POLICY_GROWFS_MASK = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON,
_PARTITION_POLICY_PFLAGS_MASK = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
_PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
_PARTITION_POLICY_FLAGS_INVALID = -EINVAL,
_PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */
} PartitionPolicyFlags;
assert_cc((_PARTITION_POLICY_USE_MASK | _PARTITION_POLICY_PFLAGS_MASK) >= 0); /* ensure flags don't collide with errno range */
typedef struct PartitionPolicy {
PartitionDesignator designator;
PartitionPolicyFlags flags;
} PartitionPolicy;
struct ImagePolicy {
PartitionPolicyFlags default_flags; /* for any designator not listed in the list below */
size_t n_policies;
PartitionPolicy policies[]; /* sorted by designator, hence suitable for binary search */
};
/* Default policies for various usecases */
extern const ImagePolicy image_policy_allow;
extern const ImagePolicy image_policy_deny;
extern const ImagePolicy image_policy_ignore;
extern const ImagePolicy image_policy_sysext; /* No verity required */
extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */
extern const ImagePolicy image_policy_container;
extern const ImagePolicy image_policy_service;
extern const ImagePolicy image_policy_host;
PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator);
PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator);
/* We want that the NULL image policy means "everything" allowed, hence use these simple accessors to make
* NULL policies work reasonably */
static inline PartitionPolicyFlags image_policy_default(const ImagePolicy *policy) {
return policy ? policy->default_flags : PARTITION_POLICY_OPEN;
}
static inline size_t image_policy_n_entries(const ImagePolicy *policy) {
return policy ? policy->n_policies : 0;
}
PartitionPolicyFlags partition_policy_flags_from_string(const char *s);
int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret);
int image_policy_from_string(const char *s, ImagePolicy **ret);
int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret);
/* Recognizes three special policies by equivalence */
bool image_policy_equiv_ignore(const ImagePolicy *policy);
bool image_policy_equiv_allow(const ImagePolicy *policy);
bool image_policy_equiv_deny(const ImagePolicy *policy);
bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b); /* checks if defined the same way, i.e. has literally the same ruleset */
int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b); /* checks if the outcome is the same, i.e. for all partitions results in the same decisions. */
static inline ImagePolicy* image_policy_free(ImagePolicy *p) {
return mfree(p);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(ImagePolicy*, image_policy_free);
+1
View File
@@ -81,6 +81,7 @@ shared_sources = files(
'id128-print.c',
'idn-util.c',
'ima-util.c',
'image-policy.c',
'import-util.c',
'in-addr-prefix-util.c',
'install-file.c',
+6 -4
View File
@@ -805,6 +805,7 @@ static int mount_in_namespace(
bool read_only,
bool make_file_or_directory,
const MountOptions *options,
const ImagePolicy *image_policy,
bool is_image) {
_cleanup_close_pair_ int errno_pipe_fd[2] = PIPE_EBADF;
@@ -892,7 +893,7 @@ static int mount_in_namespace(
mount_tmp_created = true;
if (is_image)
r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, NULL, NULL, NULL, NULL);
r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, image_policy, NULL, NULL, NULL, NULL);
else
r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, NULL, MS_BIND, NULL);
if (r < 0)
@@ -1042,7 +1043,7 @@ int bind_mount_in_namespace(
bool read_only,
bool make_file_or_directory) {
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, NULL, false);
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, /* options= */ NULL, /* image_policy= */ NULL, /* is_image= */ false);
}
int mount_image_in_namespace(
@@ -1053,9 +1054,10 @@ int mount_image_in_namespace(
const char *dest,
bool read_only,
bool make_file_or_directory,
const MountOptions *options) {
const MountOptions *options,
const ImagePolicy *image_policy) {
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, true);
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, image_policy, /* is_image=*/ true);
}
int make_mount_point(const char *path) {
+1 -1
View File
@@ -81,7 +81,7 @@ static inline char* umount_and_rmdir_and_free(char *p) {
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, umount_and_rmdir_and_free);
int bind_mount_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory);
int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options);
int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options, const ImagePolicy *image_policy);
int make_mount_point(const char *path);