diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 47ca62c731..e7bd84e144 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -50,6 +50,20 @@ PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags) { return flags; } +PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags) { + /* The reverse of partition_policy_flags_extend(): if some parts of the flags field allow all + * possible options, let's remove it from the flags to make them shorter */ + + if (FLAGS_SET(flags, _PARTITION_POLICY_USE_MASK)) + flags &= ~_PARTITION_POLICY_USE_MASK; + if (FLAGS_SET(flags, _PARTITION_POLICY_READ_ONLY_MASK)) + flags &= ~_PARTITION_POLICY_READ_ONLY_MASK; + if (FLAGS_SET(flags, _PARTITION_POLICY_GROWFS_MASK)) + flags &= ~_PARTITION_POLICY_GROWFS_MASK; + + return flags; +} + static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) { PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags; @@ -676,6 +690,90 @@ int parse_image_policy_argument(const char *s, ImagePolicy **policy) { return free_and_replace_full(*policy, np, image_policy_free); } +static bool partition_policy_flags_has_unspecified(PartitionPolicyFlags flags) { + + if ((flags & _PARTITION_POLICY_USE_MASK) == 0) + return true; + if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0) + return true; + if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0) + return true; + + return false; +} + +int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret) { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + /* Calculates the intersection of the specified policies, i.e. only what is permitted in both. This + * might fail with -ENAVAIL if the intersection is an "impossible policy". For example, if a root + * partition my neither be used, nor be absent, nor be unused then this is considered + * "impossible". */ + + p = image_policy_new(_PARTITION_DESIGNATOR_MAX); + if (!p) + return -ENOMEM; + + p->default_flags = + partition_policy_flags_extend(image_policy_default(a)) & + partition_policy_flags_extend(image_policy_default(b)); + + if (partition_policy_flags_has_unspecified(p->default_flags)) /* Intersection empty? */ + return -ENAVAIL; + + p->default_flags = partition_policy_flags_reduce(p->default_flags); + + for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { + PartitionPolicyFlags x, y, z, df; + + /* If this designator has no entry in either policy we don't need to include it in the intersection either. */ + if (!image_policy_bsearch(a, d) && !image_policy_bsearch(b, d)) + continue; + + /* Expand this policy flags field to the "long" form, i.e. for each part of the flags that + * are left unspcified add in all possible options */ + x = image_policy_get_exhaustively(a, d); + if (x < 0) + return x; + + y = image_policy_get_exhaustively(b, d); + if (y < 0) + return y; + + /* Mask it */ + z = x & y; + + /* Check if the intersection is empty for this partition. If so, generate a clear error */ + if (partition_policy_flags_has_unspecified(z)) + return -ENAVAIL; + + df = partition_policy_normalized_flags( + &(const PartitionPolicy) { + .flags = image_policy_default(p), + .designator = d, + }); + if (df < 0) + return df; + if (df == z) /* Same as default? then let's skip this */ + continue; + + /* image_policy_get_exhaustively() may have extended the flags mask to include all + * read-only/growfs flags if not set. Let's remove them again, if they are both set to + * minimize the policy again. */ + z = partition_policy_flags_reduce(z); + + p->policies[p->n_policies++] = (struct PartitionPolicy) { + .designator = d, + .flags = z, + }; + } + + if (ret) + *ret = TAKE_PTR(p); + + return 0; +} + const ImagePolicy image_policy_allow = { /* Allow policy */ .n_policies = 0, diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index c2a0a5ac40..a1a6afa345 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -80,6 +80,7 @@ static inline size_t image_policy_n_entries(const ImagePolicy *policy) { } PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags); +PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags); PartitionPolicyFlags partition_policy_flags_from_string(const char *s); int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret); @@ -95,6 +96,8 @@ 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. */ +int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret); + static inline ImagePolicy* image_policy_free(ImagePolicy *p) { return mfree(p); } diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c index 1572093c9d..12fc10acf0 100644 --- a/src/test/test-image-policy.c +++ b/src/test/test-image-policy.c @@ -130,4 +130,36 @@ TEST(extend) { assert_se(partition_policy_flags_extend(PARTITION_POLICY_GROWFS_ON) == (PARTITION_POLICY_GROWFS_ON|_PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK)); } +static void test_policy_intersect_one(const char *a, const char *b, const char *c) { + _cleanup_(image_policy_freep) ImagePolicy *x = NULL, *y = NULL, *z = NULL, *t = NULL; + + assert_se(image_policy_from_string(a, &x) >= 0); + assert_se(image_policy_from_string(b, &y) >= 0); + assert_se(image_policy_from_string(c, &z) >= 0); + + assert_se(image_policy_intersect(x, y, &t) >= 0); + + _cleanup_free_ char *s1 = NULL, *s2 = NULL, *s3 = NULL, *s4 = NULL; + assert_se(image_policy_to_string(x, false, &s1) >= 0); + assert_se(image_policy_to_string(y, false, &s2) >= 0); + assert_se(image_policy_to_string(z, false, &s3) >= 0); + assert_se(image_policy_to_string(t, false, &s4) >= 0); + + log_info("%s ^ %s → %s vs. %s", s1, s2, s3, s4); + + assert_se(image_policy_equivalent(z, t) > 0); +} + +TEST(image_policy_intersect) { + test_policy_intersect_one("", "", ""); + test_policy_intersect_one("-", "-", "-"); + test_policy_intersect_one("*", "*", "*"); + test_policy_intersect_one("~", "~", "~"); + test_policy_intersect_one("root=verity+signed", "root=signed+verity", "root=verity+signed"); + test_policy_intersect_one("root=verity+signed", "root=signed", "root=signed"); + test_policy_intersect_one("root=verity+signed", "root=verity", "root=verity"); + test_policy_intersect_one("root=open", "root=verity", "root=verity"); + test_policy_intersect_one("root=open", "=verity+ignore", "root=verity+ignore:=ignore"); +} + DEFINE_TEST_MAIN(LOG_INFO);