From: Luca Boccassi Date: Fri, 19 Dec 2025 17:01:32 +0000 (+0000) Subject: image-policy: add helper to create policy from dissected image X-Git-Tag: v260-rc1~360^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2258c9327022fa87530e73da8de46ffe73ec2572;p=thirdparty%2Fsystemd.git image-policy: add helper to create policy from dissected image Pin policies to exactly what was found while dissecting --- diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 813dc04f959..59d1161d046 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "dissect-image.h" #include "extract-word.h" #include "image-policy.h" #include "log.h" @@ -176,6 +177,28 @@ PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, Pa return flags; } +static PartitionPolicyFlags policy_flag_from_fstype(const char *s) { + if (!s) + return _PARTITION_POLICY_FLAGS_INVALID; + + if (streq(s, "btrfs")) + return PARTITION_POLICY_BTRFS; + if (streq(s, "erofs")) + return PARTITION_POLICY_EROFS; + if (streq(s, "ext4")) + return PARTITION_POLICY_EXT4; + if (streq(s, "f2fs")) + return PARTITION_POLICY_F2FS; + if (streq(s, "squashfs")) + return PARTITION_POLICY_SQUASHFS; + if (streq(s, "vfat")) + return PARTITION_POLICY_VFAT; + if (streq(s, "xfs")) + return PARTITION_POLICY_XFS; + + return _PARTITION_POLICY_FLAGS_INVALID; +} + static PartitionPolicyFlags policy_flag_from_string_one(const char *s) { assert(s); @@ -207,22 +230,8 @@ static PartitionPolicyFlags policy_flag_from_string_one(const char *s) { return PARTITION_POLICY_GROWFS_ON; if (streq(s, "growfs-off")) return PARTITION_POLICY_GROWFS_OFF; - if (streq(s, "btrfs")) - return PARTITION_POLICY_BTRFS; - if (streq(s, "erofs")) - return PARTITION_POLICY_EROFS; - if (streq(s, "ext4")) - return PARTITION_POLICY_EXT4; - if (streq(s, "f2fs")) - return PARTITION_POLICY_F2FS; - if (streq(s, "squashfs")) - return PARTITION_POLICY_SQUASHFS; - if (streq(s, "vfat")) - return PARTITION_POLICY_VFAT; - if (streq(s, "xfs")) - return PARTITION_POLICY_XFS; - return _PARTITION_POLICY_FLAGS_INVALID; + return policy_flag_from_fstype(s); } PartitionPolicyFlags partition_policy_flags_from_string(const char *s, bool graceful) { @@ -275,6 +284,60 @@ static ImagePolicy* image_policy_new(size_t n_policies) { return p; } +ImagePolicy* image_policy_new_from_dissected(const DissectedImage *image, const VeritySettings *verity) { + assert(image); + + ImagePolicy *image_policy = image_policy_new(_PARTITION_DESIGNATOR_MAX); + if (!image_policy) + return NULL; + + /* Default to 'absent', only what we find is allowed to be used */ + image_policy->default_flags = PARTITION_POLICY_ABSENT; + + for (PartitionDesignator pd = 0; pd < _PARTITION_DESIGNATOR_MAX; pd++) { + PartitionPolicyFlags f = 0; + + if (!image->partitions[pd].found) + f |= PARTITION_POLICY_ABSENT; + else { + if (streq_ptr(image->partitions[pd].fstype, "crypto_LUKS")) + f |= PARTITION_POLICY_ENCRYPTED; + else { + PartitionPolicyFlags fstype_flag = policy_flag_from_fstype(image->partitions[pd].fstype); + if (fstype_flag >= 0) + f |= fstype_flag; + } + + if (!verity || verity->designator < 0 || verity->designator == pd) { + if (image->verity_sig_ready || (verity && iovec_is_set(&verity->root_hash_sig))) + f |= PARTITION_POLICY_SIGNED; + else if (image->verity_ready || (verity && iovec_is_set(&verity->root_hash))) + f |= PARTITION_POLICY_VERITY; + } + + if (!image->single_file_system) { + if (image->partitions[pd].growfs) + f |= PARTITION_POLICY_GROWFS_ON; + else + f |= PARTITION_POLICY_GROWFS_OFF; + + if (image->partitions[pd].rw) + f |= PARTITION_POLICY_READ_ONLY_OFF; + else + f |= PARTITION_POLICY_READ_ONLY_ON; + } + } + + image_policy->policies[pd] = (PartitionPolicy) { + .designator = pd, + .flags = f, + }; + image_policy->n_policies++; + } + + return image_policy; +} + int image_policy_from_string(const char *s, bool graceful, ImagePolicy **ret) { _cleanup_free_ ImagePolicy *p = NULL; uint64_t dmask = 0; diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index c40192931b7..fe2bf0d2004 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -2,6 +2,7 @@ #pragma once #include "conf-parser-forward.h" +#include "dissect-image.h" #include "shared-forward.h" #include "gpt.h" @@ -113,6 +114,7 @@ int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b); /* ch int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret); int image_policy_union(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret); +ImagePolicy* image_policy_new_from_dissected(const DissectedImage *image, const VeritySettings *verity); ImagePolicy* image_policy_free(ImagePolicy *p); DEFINE_TRIVIAL_CLEANUP_FUNC(ImagePolicy*, image_policy_free); diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c index 12f7fc51e06..14a0da0c9c2 100644 --- a/src/test/test-image-policy.c +++ b/src/test/test-image-policy.c @@ -280,4 +280,382 @@ TEST(partition_policy_determine_fstype) { ASSERT_FALSE(encrypted); } +TEST(image_policy_new_from_dissected) { + _cleanup_(image_policy_freep) ImagePolicy *policy = NULL; + DissectedImage image; + VeritySettings verity; + uint8_t dummy_data[4]; + + /* Test 1: Empty image - all partitions should be absent */ + image = (DissectedImage) {}; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(image_policy_default(policy), PARTITION_POLICY_ABSENT); + ASSERT_EQ(image_policy_n_entries(policy), (size_t) _PARTITION_DESIGNATOR_MAX); + + /* All partitions should have PARTITION_POLICY_ABSENT */ + for (PartitionDesignator pd = 0; pd < _PARTITION_DESIGNATOR_MAX; pd++) + ASSERT_EQ(policy->policies[pd].flags, (PartitionPolicyFlags) PARTITION_POLICY_ABSENT); + + policy = image_policy_free(policy); + + /* Test 2: Image with a single ext4 root partition */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "ext4", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_EXT4 | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_USR].flags, (PartitionPolicyFlags) PARTITION_POLICY_ABSENT); + + policy = image_policy_free(policy); + + /* Test 3: Image with encrypted root partition (LUKS) */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "crypto_LUKS", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_ENCRYPTED | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 4: Image with verity ready (without signature) */ + image = (DissectedImage) { + .verity_ready = true, + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "squashfs", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_SQUASHFS | PARTITION_POLICY_VERITY | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 5: Image with verity signature ready */ + image = (DissectedImage) { + .verity_sig_ready = true, + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "erofs", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_EROFS | PARTITION_POLICY_SIGNED | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 6: Image with growfs enabled */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "btrfs", + .growfs = true, + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_BTRFS | PARTITION_POLICY_GROWFS_ON | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 7: Multiple partitions with different filesystems */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "ext4", + }, + [PARTITION_USR] = { + .found = true, + .fstype = (char*) "xfs", + }, + [PARTITION_HOME] = { + .found = true, + .fstype = (char*) "btrfs", + .growfs = true, + }, + [PARTITION_ESP] = { + .found = true, + .fstype = (char*) "vfat", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_EXT4 | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_USR].flags, (PartitionPolicyFlags) (PARTITION_POLICY_XFS | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_HOME].flags, (PartitionPolicyFlags) (PARTITION_POLICY_BTRFS | PARTITION_POLICY_GROWFS_ON | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_ESP].flags, (PartitionPolicyFlags) (PARTITION_POLICY_VFAT | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_SWAP].flags, (PartitionPolicyFlags) PARTITION_POLICY_ABSENT); + + policy = image_policy_free(policy); + + /* Test 8: VeritySettings with root_hash set (no signature) */ + dummy_data[0] = 0xde; dummy_data[1] = 0xad; dummy_data[2] = 0xbe; dummy_data[3] = 0xef; + verity = (VeritySettings) { + .designator = _PARTITION_DESIGNATOR_INVALID, + .root_hash = IOVEC_MAKE(dummy_data, sizeof(dummy_data)), + }; + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "squashfs", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, &verity); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_SQUASHFS | PARTITION_POLICY_VERITY | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 9: VeritySettings with root_hash_sig set */ + dummy_data[0] = 0x01; dummy_data[1] = 0x02; dummy_data[2] = 0x03; dummy_data[3] = 0x04; + verity = (VeritySettings) { + .designator = _PARTITION_DESIGNATOR_INVALID, + .root_hash_sig = IOVEC_MAKE(dummy_data, sizeof(dummy_data)), + }; + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "erofs", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, &verity); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_EROFS | PARTITION_POLICY_SIGNED | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 10: VeritySettings with designator targeting specific partition */ + dummy_data[0] = 0xab; dummy_data[1] = 0xcd; + verity = (VeritySettings) { + .designator = PARTITION_USR, + .root_hash = IOVEC_MAKE(dummy_data, 2), + }; + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "ext4", + }, + [PARTITION_USR] = { + .found = true, + .fstype = (char*) "squashfs", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, &verity); + ASSERT_NOT_NULL(policy); + /* Root should NOT have verity since verity targets USR */ + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_EXT4 | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + /* USR should have verity */ + ASSERT_EQ(policy->policies[PARTITION_USR].flags, (PartitionPolicyFlags) (PARTITION_POLICY_SQUASHFS | PARTITION_POLICY_VERITY | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 11: Unknown filesystem type (should have no fstype flag) */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "unknown_fs", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + /* Should only have GROWFS_OFF and READ_ONLY_ON for unknown filesystem */ + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 12: NULL filesystem type (should have no fstype flag) */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = NULL, + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + /* Should only have GROWFS_OFF and READ_ONLY_ON for NULL fstype */ + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 13: Combination of verity_ready from image and verity settings - image takes precedence for sig */ + dummy_data[0] = 0x11; dummy_data[1] = 0x22; + verity = (VeritySettings) { + .designator = _PARTITION_DESIGNATOR_INVALID, + .root_hash = IOVEC_MAKE(dummy_data, 2), + }; + image = (DissectedImage) { + .verity_sig_ready = true, /* This should take precedence */ + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "squashfs", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, &verity); + ASSERT_NOT_NULL(policy); + /* verity_sig_ready should result in SIGNED, not just VERITY */ + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_SQUASHFS | PARTITION_POLICY_SIGNED | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 14: All known filesystem types */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { .found = true, .fstype = (char*) "ext4" }, + [PARTITION_USR] = { .found = true, .fstype = (char*) "btrfs" }, + [PARTITION_HOME] = { .found = true, .fstype = (char*) "xfs" }, + [PARTITION_SRV] = { .found = true, .fstype = (char*) "f2fs" }, + [PARTITION_VAR] = { .found = true, .fstype = (char*) "erofs" }, + [PARTITION_TMP] = { .found = true, .fstype = (char*) "squashfs" }, + [PARTITION_ESP] = { .found = true, .fstype = (char*) "vfat" }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_EXT4 | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_USR].flags, (PartitionPolicyFlags) (PARTITION_POLICY_BTRFS | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_HOME].flags, (PartitionPolicyFlags) (PARTITION_POLICY_XFS | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_SRV].flags, (PartitionPolicyFlags) (PARTITION_POLICY_F2FS | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_VAR].flags, (PartitionPolicyFlags) (PARTITION_POLICY_EROFS | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_TMP].flags, (PartitionPolicyFlags) (PARTITION_POLICY_SQUASHFS | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + ASSERT_EQ(policy->policies[PARTITION_ESP].flags, (PartitionPolicyFlags) (PARTITION_POLICY_VFAT | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 15: Encrypted partition with verity (LUKS takes precedence, no verity flag) */ + image = (DissectedImage) { + .verity_ready = true, + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "crypto_LUKS", + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + /* crypto_LUKS check happens first, then verity is added */ + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_ENCRYPTED | PARTITION_POLICY_VERITY | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 16: Multiple flags combined - encrypted + growfs */ + image = (DissectedImage) { + .partitions = { + [PARTITION_HOME] = { + .found = true, + .fstype = (char*) "crypto_LUKS", + .growfs = true, + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_HOME].flags, (PartitionPolicyFlags) (PARTITION_POLICY_ENCRYPTED | PARTITION_POLICY_GROWFS_ON | PARTITION_POLICY_READ_ONLY_ON)); + + policy = image_policy_free(policy); + + /* Test 17: single_file_system=true should NOT set growfs or read-only flags */ + image = (DissectedImage) { + .single_file_system = true, + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "ext4", + .rw = true, + .growfs = true, + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + /* single_file_system=true means no growfs/read-only flags */ + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) PARTITION_POLICY_EXT4); + + policy = image_policy_free(policy); + + /* Test 18: rw=true should set READ_ONLY_OFF instead of READ_ONLY_ON */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "ext4", + .rw = true, + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_EXT4 | PARTITION_POLICY_GROWFS_OFF | PARTITION_POLICY_READ_ONLY_OFF)); + + policy = image_policy_free(policy); + + /* Test 19: rw=true with growfs=true */ + image = (DissectedImage) { + .partitions = { + [PARTITION_ROOT] = { + .found = true, + .fstype = (char*) "btrfs", + .rw = true, + .growfs = true, + }, + }, + }; + + policy = image_policy_new_from_dissected(&image, /* verity= */ NULL); + ASSERT_NOT_NULL(policy); + ASSERT_EQ(policy->policies[PARTITION_ROOT].flags, (PartitionPolicyFlags) (PARTITION_POLICY_BTRFS | PARTITION_POLICY_GROWFS_ON | PARTITION_POLICY_READ_ONLY_OFF)); +} + DEFINE_TEST_MAIN(LOG_INFO);