Merge pull request #25608 from poettering/dissect-moar
dissect: add dissection policies
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user