From: Lennart Poettering Date: Wed, 30 Nov 2022 17:56:28 +0000 (+0100) Subject: dissect: actually enforce policy X-Git-Tag: v254-rc1~748^2~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cd22d8562dd085f5c234cf26b4dd773029418833;p=thirdparty%2Fsystemd.git dissect: actually enforce policy --- diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 83b0581ff1a..e1cfb7622f5 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -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; + } - 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, - }; + 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, + }; + } } } @@ -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; diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 3af0c439bcf..af9798b9407 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -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 */