dissect: actually enforce policy

This commit is contained in:
Lennart Poettering 2022-11-30 18:56:28 +01:00
parent 84be0c710d
commit cd22d8562d
2 changed files with 235 additions and 41 deletions

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;
@ -651,6 +762,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);
@ -658,10 +797,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)
@ -682,7 +817,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);
@ -1050,6 +1185,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
@ -1140,6 +1287,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;
@ -1224,41 +1381,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,
};
}
}
@ -1320,7 +1485,35 @@ static int dissect_image(
}
}
r = dissected_image_probe_filesystems(m, fd);
/* 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;
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;
}
}
r = dissected_image_probe_filesystems(m, fd, policy);
if (r < 0)
return r;

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 */