Reject at unpack a policyns block whose percentage exceeds 100 or marks a key the block does not specify.
Extend the KUnit suite over the grown budget semantics leaving the capset untouched on a reject. Signed-off-by: Maxime Bélair <[email protected]> --- security/apparmor/policy_ns.c | 2 + security/apparmor/policy_unpack.c | 6 + security/apparmor/policy_unpack_test.c | 170 +++++++++++++++++++++++++ 3 files changed, 178 insertions(+) diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 111d5ba9d413..d7d115a8a36d 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -11,6 +11,7 @@ * to be loaded for tasks within the namespace. */ +#include <kunit/visibility.h> #include <linux/list.h> #include <linux/mutex.h> #include <linux/ratelimit.h> @@ -767,6 +768,7 @@ int aa_ns_apply_budget(struct aa_ns_capset *caps, struct aa_ns_budget *b) return -EINVAL; } } +EXPORT_SYMBOL_IF_KUNIT(aa_ns_apply_budget); /* resolve_percent_caps - resolve raw percentage keys against @base's caps */ static void resolve_percent_caps(struct aa_ns_caps *caps, u32 percent, diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index d0c31761b022..f6f31b282335 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -666,6 +666,9 @@ VISIBLE_IF_KUNIT int unpack_policyns_block(struct aa_ext *e, goto fail; if (v > INT_MAX) /* parser bounds caps at INT_MAX */ goto fail; /* out of range */ + /* a percentage is a ratio (bounded at 100) */ + if ((b->percent & (1u << k)) && v > 100) + goto fail; b->values[k] = (long)v; } if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) @@ -684,6 +687,9 @@ VISIBLE_IF_KUNIT int unpack_policyns_block(struct aa_ext *e, /* reject specified/percent bits outside the known key range */ if ((b->specified | b->percent) & ~((1u << AA_POLICYNS_KEY_MAX) - 1)) goto fail; + /* a percentage bit is only coherent on a key the block specifies */ + if (b->percent & ~b->specified) + goto fail; return 1; diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c index 8c2fd58e774b..f27693729692 100644 --- a/security/apparmor/policy_unpack_test.c +++ b/security/apparmor/policy_unpack_test.c @@ -7,6 +7,7 @@ #include <kunit/visibility.h> #include "include/policy.h" +#include "include/policy_ns.h" #include "include/policy_unpack.h" #include <linux/limits.h> @@ -982,6 +983,170 @@ static void policy_unpack_test_policyns_sequential_blocks(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, b->e.pos, b->e.end); } +static void policy_unpack_test_policyns_percent_over_100(struct kunit *test) +{ + struct pn_block_shape s = PN_WELLFORMED_SHAPE; + struct pn_blob *b = pn_blob_alloc(test); + struct aa_ns_budget budget = {}; + + s.percent = BIT(AA_POLICYNS_KEY_MEMORY); + s.value0 = 101; + pn_put_block(test, b, &s); + pn_blob_seal(b); + KUNIT_EXPECT_EQ(test, unpack_policyns_block(&b->e, &budget), -EPROTO); + KUNIT_EXPECT_PTR_EQ(test, b->e.pos, b->e.start); + + b = pn_blob_alloc(test); + s.value0 = 100; + pn_put_block(test, b, &s); + pn_blob_seal(b); + KUNIT_EXPECT_EQ(test, unpack_policyns_block(&b->e, &budget), 1); + KUNIT_EXPECT_EQ(test, budget.values[AA_POLICYNS_KEY_MEMORY], 100L); +} + +static void policy_unpack_test_policyns_percent_not_specified(struct kunit *test) +{ + struct pn_block_shape s = PN_WELLFORMED_SHAPE; + struct pn_blob *b = pn_blob_alloc(test); + struct aa_ns_budget budget = {}; + + /* a percentage bit on a key the block does not specify is incoherent */ + s.percent = BIT(AA_POLICYNS_KEY_PROFILES); + pn_put_block(test, b, &s); + pn_blob_seal(b); + KUNIT_EXPECT_EQ(test, unpack_policyns_block(&b->e, &budget), -EPROTO); + KUNIT_EXPECT_PTR_EQ(test, b->e.pos, b->e.start); +} + +/* + * aa_ns_apply_budget() semantic tests, on a synthetic capset. The budget + * struct is the post-unpack form, so blocks are built directly. + */ + +static void policy_unpack_test_policyns_apply_scopes(struct kunit *test) +{ + struct aa_ns_budget b = {}; + struct aa_ns_capset caps; + + aa_ns_capset_init_unset(&caps); + + /* self + local tightens the local caps and leaves subtree alone */ + b.target = AA_POLICYNS_TGT_SELF; + b.scope = AA_POLICYNS_SCOPE_LOCAL; + b.specified = BIT(AA_POLICYNS_KEY_MEMORY); + b.values[AA_POLICYNS_KEY_MEMORY] = SZ_1M; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), 0); + KUNIT_EXPECT_EQ(test, caps.limits.memory, (long)SZ_1M); + KUNIT_EXPECT_EQ(test, caps.subtree.memory, AA_NS_NOLIMIT); + + /* self + subtree stamps the subtree caps, not the local ones */ + b.scope = AA_POLICYNS_SCOPE_SUBTREE; + b.values[AA_POLICYNS_KEY_MEMORY] = SZ_2M; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), 0); + KUNIT_EXPECT_EQ(test, caps.subtree.memory, (long)SZ_2M); + KUNIT_EXPECT_EQ(test, caps.limits.memory, (long)SZ_1M); + + /* self is tighten-only: a looser value cannot raise the cap */ + b.scope = AA_POLICYNS_SCOPE_LOCAL; + b.values[AA_POLICYNS_KEY_MEMORY] = SZ_8M; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), 0); + KUNIT_EXPECT_EQ(test, caps.limits.memory, (long)SZ_1M); + + /* criu and load_rate are enforced keys now, applied like the rest */ + b.specified = BIT(AA_POLICYNS_KEY_CRIU) | + BIT(AA_POLICYNS_KEY_LOAD_RATE); + b.values[AA_POLICYNS_KEY_CRIU] = SZ_4M; + b.values[AA_POLICYNS_KEY_LOAD_RATE] = 20; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), 0); + KUNIT_EXPECT_EQ(test, caps.limits.criu, (long)SZ_4M); + KUNIT_EXPECT_EQ(test, caps.limits.load_rate, 20L); +} + +static void policy_unpack_test_policyns_apply_child_percent(struct kunit *test) +{ + struct aa_ns_budget b = {}; + struct aa_ns_capset caps; + + aa_ns_capset_init_unset(&caps); + + /* children percentages are stored raw, with the mask beside them */ + b.target = AA_POLICYNS_TGT_CHILDREN; + b.scope = AA_POLICYNS_SCOPE_LOCAL; + b.specified = BIT(AA_POLICYNS_KEY_MEMORY) | + BIT(AA_POLICYNS_KEY_PROFILES); + b.percent = BIT(AA_POLICYNS_KEY_MEMORY); + b.values[AA_POLICYNS_KEY_MEMORY] = 50; + b.values[AA_POLICYNS_KEY_PROFILES] = 10; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), 0); + KUNIT_EXPECT_EQ(test, caps.child.memory, 50L); + KUNIT_EXPECT_EQ(test, caps.child.profiles, 10L); + KUNIT_EXPECT_EQ(test, caps.child_percent, + (u32)BIT(AA_POLICYNS_KEY_MEMORY)); + + /* the subtree children template is independent of the local one */ + b.scope = AA_POLICYNS_SCOPE_SUBTREE; + b.specified = BIT(AA_POLICYNS_KEY_MEMORY); + b.values[AA_POLICYNS_KEY_MEMORY] = 25; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), 0); + KUNIT_EXPECT_EQ(test, caps.child_subtree.memory, 25L); + KUNIT_EXPECT_EQ(test, caps.child_subtree_percent, + (u32)BIT(AA_POLICYNS_KEY_MEMORY)); + KUNIT_EXPECT_EQ(test, caps.child.memory, 50L); + + /* a later children block replaces its scope's whole template */ + b.scope = AA_POLICYNS_SCOPE_LOCAL; + b.specified = BIT(AA_POLICYNS_KEY_PROFILES); + b.percent = 0; + b.values[AA_POLICYNS_KEY_PROFILES] = 3; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), 0); + KUNIT_EXPECT_EQ(test, caps.child.profiles, 3L); + KUNIT_EXPECT_EQ(test, caps.child.memory, AA_NS_NOLIMIT); + KUNIT_EXPECT_EQ(test, caps.child_percent, (u32)0); + KUNIT_EXPECT_EQ(test, caps.child_subtree.memory, 25L); +} + +static void policy_unpack_test_policyns_apply_rejects(struct kunit *test) +{ + struct aa_ns_budget b = {}; + struct aa_ns_capset caps; + + aa_ns_capset_init_unset(&caps); + + /* a percentage on self stays a semantic reject */ + b.target = AA_POLICYNS_TGT_SELF; + b.scope = AA_POLICYNS_SCOPE_LOCAL; + b.specified = BIT(AA_POLICYNS_KEY_MEMORY); + b.percent = BIT(AA_POLICYNS_KEY_MEMORY); + b.values[AA_POLICYNS_KEY_MEMORY] = 50; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), -EOPNOTSUPP); + + /* subtree scope is invalid on depth, namespaces and load_rate */ + b.percent = 0; + b.scope = AA_POLICYNS_SCOPE_SUBTREE; + b.specified = BIT(AA_POLICYNS_KEY_DEPTH); + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), -EINVAL); + b.specified = BIT(AA_POLICYNS_KEY_NAMESPACES); + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), -EINVAL); + b.specified = BIT(AA_POLICYNS_KEY_LOAD_RATE); + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), -EINVAL); + + /* routed targets resolve through aa_ns_budget_route(), not here */ + b.scope = AA_POLICYNS_SCOPE_LOCAL; + b.specified = BIT(AA_POLICYNS_KEY_MEMORY); + b.target = AA_POLICYNS_TGT_DESCENDANTS; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), -EINVAL); + b.target = AA_POLICYNS_TGT_ROOT; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), -EINVAL); + b.target = AA_POLICYNS_TGT_NAME; + KUNIT_EXPECT_EQ(test, aa_ns_apply_budget(&caps, &b), -EINVAL); + + /* nothing above may have touched the capset */ + KUNIT_EXPECT_EQ(test, caps.limits.memory, AA_NS_NOLIMIT); + KUNIT_EXPECT_EQ(test, caps.subtree.memory, AA_NS_NOLIMIT); + KUNIT_EXPECT_EQ(test, caps.child.memory, AA_NS_NOLIMIT); + KUNIT_EXPECT_EQ(test, caps.child_subtree.memory, AA_NS_NOLIMIT); +} + static struct kunit_case apparmor_policy_unpack_test_cases[] = { KUNIT_CASE(policy_unpack_test_inbounds_when_inbounds), KUNIT_CASE(policy_unpack_test_inbounds_when_out_of_bounds), @@ -1028,6 +1193,11 @@ static struct kunit_case apparmor_policy_unpack_test_cases[] = { KUNIT_CASE(policy_unpack_test_policyns_header_truncated), KUNIT_CASE(policy_unpack_test_policyns_unterminated_name), KUNIT_CASE(policy_unpack_test_policyns_sequential_blocks), + KUNIT_CASE(policy_unpack_test_policyns_percent_over_100), + KUNIT_CASE(policy_unpack_test_policyns_percent_not_specified), + KUNIT_CASE(policy_unpack_test_policyns_apply_scopes), + KUNIT_CASE(policy_unpack_test_policyns_apply_child_percent), + KUNIT_CASE(policy_unpack_test_policyns_apply_rejects), {}, }; -- 2.51.0
