1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-31 05:47:30 +03:00

image-policy: add a new image_policy_intersect() call

This new call takes two image policy objects and generates an
"intersection" policy, i.e. only allows what is allowed by both. Or in
other words it conceptually implements a binary AND of the policy flags.
(Except that it's a bit harder, due to normalization, and underspecified
flags).

We can use this later for mountfsd: a client can specify a policy, and
mountfsd can specify another policy, and we'll then apply only what both
allow.

Note that a policy generated like this might be invalid. For example, if
one policy says root must exist and be verity or luks protected, and the
other policy says root must be absent, then the intersection is invalid,
since one policy only allows what the other prohibits and vice versa.
We'll return a clear error code in that case (ENAVAIL). (This is because
we simply don't allow encoding such impossible policies in an
ImagePolicy structure, for good reasons.)
This commit is contained in:
Lennart Poettering 2023-04-26 21:51:53 +02:00
parent b219dcd45a
commit 2251e4ef90
3 changed files with 133 additions and 0 deletions

View File

@ -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,

View File

@ -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);
}

View File

@ -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);