Accept the subtree scope: a subtree memory or profiles cap binds the
aggregate usage of the namespace plus all its descendants, and a subtree
max_profile bounds every profile loaded below it. Admission checks the
load's net delta against the target's tentative subtree caps and every
ancestor's standing ones.

Signed-off-by: Maxime Bélair <[email protected]>
---
 security/apparmor/apparmorfs.c        |   3 +-
 security/apparmor/include/policy.h    |   6 +
 security/apparmor/include/policy_ns.h |  19 +++-
 security/apparmor/policy.c            |  17 ++-
 security/apparmor/policy_ns.c         | 158 ++++++++++++++++++++++----
 5 files changed, 176 insertions(+), 27 deletions(-)

diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index d5aa7a23e4ab..3ed00f5b30c4 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -2696,7 +2696,8 @@ static struct aa_sfs_entry aa_sfs_entry_ns_quota[] = {
         * Each follow-up that starts enforcing a construct appends its token
         * (subtree, criu, load_rate, descendants, root, name, mediation).
         */
-       AA_SFS_FILE_STRING("mask", "self children percent local mediation"),
+       AA_SFS_FILE_STRING("mask",
+                          "self children percent local subtree mediation"),
        { }
 };
 
diff --git a/security/apparmor/include/policy.h 
b/security/apparmor/include/policy.h
index 42fd18b12886..a331d3664b22 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -260,6 +260,12 @@ AA_POLICYNS_KEY_ASSERT(AA_POLICYNS_KEY_LOAD_RATE, 
load_rate);
 static_assert(sizeof(struct aa_ns_caps) ==
              AA_POLICYNS_KEY_MAX * sizeof(long));
 
+/* keys the subtree scope may aggregate over: sizes and the profile count */
+#define AA_POLICYNS_SUBTREE_KEYS       ((1u << AA_POLICYNS_KEY_MEMORY) |    \
+                                        (1u << AA_POLICYNS_KEY_MAX_PROFILE) | \
+                                        (1u << AA_POLICYNS_KEY_PROFILES) |  \
+                                        (1u << AA_POLICYNS_KEY_CRIU))
+
 /* initialise every cap to unset; key-driven so no field can be missed */
 static inline void aa_ns_caps_init_unset(struct aa_ns_caps *c)
 {
diff --git a/security/apparmor/include/policy_ns.h 
b/security/apparmor/include/policy_ns.h
index 7398e9ab7f1e..9c5af84ea023 100644
--- a/security/apparmor/include/policy_ns.h
+++ b/security/apparmor/include/policy_ns.h
@@ -29,21 +29,30 @@ struct apparmor_audit_data;
 #define AA_NS_QUOTA_RATELIMIT_BURST    10
 
 /* struct aa_ns_capset - the standing caps a namespace enforces and stamps
- * @limits: caps enforced against this namespace (self)
+ * @limits: caps enforced against this namespace (self, local scope)
+ * @subtree: caps enforced against this namespace plus all its descendants
  * @child: template caps stamped onto namespaces this namespace creates 
(children)
+ * @child_subtree: subtree-scope template for created namespaces
  * @child_percent: percentage (0-100) of the parent's cap, resolved per child 
at creation
+ * @child_subtree_percent: as @child_percent, for @child_subtree
  */
 struct aa_ns_capset {
        struct aa_ns_caps limits;
+       struct aa_ns_caps subtree;
        struct aa_ns_caps child;
+       struct aa_ns_caps child_subtree;
        u32 child_percent;
+       u32 child_subtree_percent;
 };
 
 static inline void aa_ns_capset_init_unset(struct aa_ns_capset *caps)
 {
        aa_ns_caps_init_unset(&caps->limits);
+       aa_ns_caps_init_unset(&caps->subtree);
        aa_ns_caps_init_unset(&caps->child);
+       aa_ns_caps_init_unset(&caps->child_subtree);
        caps->child_percent = 0;
+       caps->child_subtree_percent = 0;
 }
 
 /* struct aa_ns_acct - per-namespace resource accounting and caps
@@ -142,14 +151,16 @@ int aa_ns_admit_create(struct aa_ns *parent);
 int aa_ns_admit_resident(struct aa_ns *ns, struct aa_ns_caps *limits,
                         long delta);
 /* per-profile and count admission, under ns->lock */
-int aa_ns_admit_profile_size(struct aa_ns *ns, struct aa_ns_caps *limits,
-                            long bytes);
+int aa_ns_admit_profile_size(struct aa_ns *ns, long limit, long bytes);
 int aa_ns_admit_count(struct aa_ns *ns, struct aa_ns_caps *limits, long delta);
 /* whole replace-set admission, under ns->lock */
 struct aa_load_ent;
 int aa_ns_admit_load_set(struct aa_ns *ns, struct list_head *lh,
-                        struct aa_ns_caps *limits, struct aa_loaddata *udata,
+                        struct aa_ns_capset *pend, struct aa_loaddata *udata,
                         struct aa_load_ent **fail_ent, const char **info);
+/* serializes subtree-scope cap updates and whole-chain admissions */
+extern struct mutex aa_ns_subtree_lock;
+bool aa_ns_subtree_in_play(struct aa_ns *ns, struct list_head *lh);
 /* apply one parsed "policyns limits" block to a (tentative) capset;
  * returns -EOPNOTSUPP for a construct this kernel does not yet enforce
  */
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index cef057b98b27..824cca42eb8c 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -1276,6 +1276,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
        struct aa_load_ent *ent, *tmp;
        struct aa_loaddata *rawdata_ent;
        struct aa_ns_capset pend_caps;
+       bool subtree_locked = false;
        const char *op;
        ssize_t count, error;
        LIST_HEAD(lh);
@@ -1340,6 +1341,11 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
                goto fail;
        }
 
+       /* whole-chain admission serializes before any ns->lock */
+       if (aa_g_policy_ns_quota && aa_ns_subtree_in_play(ns, &lh)) {
+               mutex_lock(&aa_ns_subtree_lock);
+               subtree_locked = true;
+       }
        mutex_lock_nested(&ns->lock, ns->level);
        /* Tentative copy of the ns caps */
        pend_caps = ns->acct.caps;
@@ -1437,7 +1443,9 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
                                error = aa_ns_apply_budget(&pend_caps,
                                                           
&ent->new->budgets[b]);
                                if (error) {
-                                       info = "policyns limits: unsupported 
construct";
+                                       info = error == -EINVAL ?
+                                               "policyns limits: invalid 
construct" :
+                                               "policyns limits: unsupported 
construct";
                                        goto fail_lock;
                                }
                        }
@@ -1458,8 +1466,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
         * Admission: check the whole load set against the tentative caps
         * before installing anything, so a breach rejects the whole set.
         */
-       error = aa_ns_admit_load_set(ns, &lh, &pend_caps.limits, udata, &ent,
-                                    &info);
+       error = aa_ns_admit_load_set(ns, &lh, &pend_caps, udata, &ent, &info);
        if (error)
                goto fail_lock;
 
@@ -1554,6 +1561,8 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
        }
        __aa_labelset_update_subtree(ns);
        mutex_unlock(&ns->lock);
+       if (subtree_locked)
+               mutex_unlock(&aa_ns_subtree_lock);
 
 out:
        aa_put_ns(ns);
@@ -1569,6 +1578,8 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
 
 fail_lock:
        mutex_unlock(&ns->lock);
+       if (subtree_locked)
+               mutex_unlock(&aa_ns_subtree_lock);
 
        /* audit cause of failure */
        op = (ent && !ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c
index 38234a21c570..225384273209 100644
--- a/security/apparmor/policy_ns.c
+++ b/security/apparmor/policy_ns.c
@@ -202,6 +202,12 @@ static long cap_percent(long base, long pct)
        return (long)((u64)base * (u64)pct / 100);
 }
 
+/*
+ * Serializes subtree cap updates and whole-chain admissions, so admission
+ * and charge are atomic against concurrent loads anywhere in the subtree.
+ */
+DEFINE_MUTEX(aa_ns_subtree_lock);
+
 void aa_ns_acct_init(struct aa_ns *ns)
 {
        struct aa_ns_acct *acct = &ns->acct;
@@ -338,17 +344,14 @@ int aa_ns_admit_resident(struct aa_ns *ns, struct 
aa_ns_caps *limits,
 
 /**
  * aa_ns_admit_profile_size - per-profile byte cap (max_profile)
- * @ns: target namespace (audit)
- * @limits: caps to check against, typically the load's tentative caps
+ * @ns: the namespace whose cap is checked (audit attribution)
+ * @limit: the effective max_profile cap in bytes
  * @bytes: the profile's resident size (precomputed by the caller)
  *
  * Returns 0 to admit, -ENOSPC if the profile exceeds max_profile.
  */
-int aa_ns_admit_profile_size(struct aa_ns *ns, struct aa_ns_caps *limits,
-                            long bytes)
+int aa_ns_admit_profile_size(struct aa_ns *ns, long limit, long bytes)
 {
-       long limit = limits->max_profile;
-
        if (!aa_g_policy_ns_quota || limit == AA_NS_NOLIMIT)
                return 0;
        if (bytes > limit)
@@ -372,11 +375,99 @@ int aa_ns_admit_count(struct aa_ns *ns, struct aa_ns_caps 
*limits, long delta)
                               &ns->acct.profile_count, delta, -EDQUOT);
 }
 
+/*
+ * effective_max_profile - chain-min of the per-profile byte cap
+ *
+ * A subtree-scoped max_profile bounds every profile loaded below it, so the
+ * effective cap is the min over @pend and every ancestor's standing subtree
+ * cap; *@owner returns the namespace whose cap binds (audit attribution).
+ */
+static long effective_max_profile(struct aa_ns *ns, struct aa_ns_capset *pend,
+                                 struct aa_ns **owner)
+{
+       long eff = cap_min(pend->limits.max_profile,
+                          pend->subtree.max_profile);
+       struct aa_ns *a;
+
+       *owner = ns;
+       for (a = ns->parent; a; a = a->parent) {
+               long m = cap_min(eff, a->acct.caps.subtree.max_profile);
+
+               if (m != eff) {
+                       eff = m;
+                       *owner = a;
+               }
+       }
+       return eff;
+}
+
+/*
+ * admit_subtree_agg - admit a load's net aggregate delta against the chain
+ *
+ * Checks @ns's tentative subtree caps and every ancestor's standing ones
+ * against their aggregates; the caller holds aa_ns_subtree_lock whenever any
+ * of these caps is set.
+ */
+static int admit_subtree_agg(struct aa_ns *ns, struct aa_ns_capset *pend,
+                            long bytes, long profiles)
+{
+       struct aa_ns *a;
+       int error;
+
+       for (a = ns; a; a = a->parent) {
+               struct aa_ns_caps *sc = (a == ns) ? &pend->subtree
+                                                 : &a->acct.caps.subtree;
+
+               error = cap_admit_delta(a, AA_POLICYNS_KEY_MEMORY, sc->memory,
+                                       &a->acct.subtree_resident, bytes,
+                                       -ENOSPC);
+               if (error)
+                       return error;
+               error = cap_admit_delta(a, AA_POLICYNS_KEY_PROFILES,
+                                       sc->profiles,
+                                       &a->acct.subtree_profile_count,
+                                       profiles, -EDQUOT);
+               if (error)
+                       return error;
+       }
+       return 0;
+}
+
+/**
+ * aa_ns_subtree_in_play - must a load into @ns serialize on the subtree lock
+ * @ns: target namespace of the load
+ * @lh: the load set, a list of struct aa_load_ent
+ *
+ * True if the load carries a subtree-scoped block or the chain up from @ns
+ * has a subtree cap set.
+ */
+bool aa_ns_subtree_in_play(struct aa_ns *ns, struct list_head *lh)
+{
+       struct aa_load_ent *ent;
+       struct aa_ns *a;
+       int i, k;
+
+       list_for_each_entry(ent, lh, list)
+               for (i = 0; i < ent->new->n_budgets; i++)
+                       if (ent->new->budgets[i].scope ==
+                           AA_POLICYNS_SCOPE_SUBTREE)
+                               return true;
+
+       for (a = ns; a; a = a->parent) {
+               const long *cap = (const long *)&a->acct.caps.subtree;
+
+               for (k = 0; k < AA_POLICYNS_KEY_MAX; k++)
+                       if (READ_ONCE(cap[k]) != AA_NS_NOLIMIT)
+                               return true;
+       }
+       return false;
+}
+
 /**
  * aa_ns_admit_load_set - admit a whole replace set against @ns's caps
  * @ns: target namespace
  * @lh: the load set, a list of struct aa_load_ent
- * @limits: the tentative caps the set is admitted against
+ * @pend: the tentative capset the set is admitted against
  * @udata: the load's raw data (for the retained-rawdata memory term)
  * @fail_ent: out - the profile that broke a per-profile cap, or NULL for a
  *           whole-set (memory/count) breach; only set when denying
@@ -387,22 +478,26 @@ int aa_ns_admit_count(struct aa_ns *ns, struct aa_ns_caps 
*limits, long delta)
  * per-profile, memory and count caps, so a breach rejects the set atomically
  * before anything installs. Null profiles count for memory but not the count.
  *
- * Requires: @ns->lock held.
+ * Requires: @ns->lock held; aa_ns_subtree_lock held when subtree caps are in
+ *          play.
  *
  * Returns: 0 to admit the set, or a negative errno with *fail_ent and *info 
set.
  */
 int aa_ns_admit_load_set(struct aa_ns *ns, struct list_head *lh,
-                        struct aa_ns_caps *limits, struct aa_loaddata *udata,
+                        struct aa_ns_capset *pend, struct aa_loaddata *udata,
                         struct aa_load_ent **fail_ent, const char **info)
 {
        long new_bytes = 0, old_bytes = 0;
        long new_count = 0, old_count = 0;
        struct aa_load_ent *ent;
+       struct aa_ns *mp_owner;
+       long mp_limit;
        int error;
 
        if (!aa_g_policy_ns_quota)
                return 0;
 
+       mp_limit = effective_max_profile(ns, pend, &mp_owner);
        list_for_each_entry(ent, lh, list) {
                long bytes;
 
@@ -412,7 +507,8 @@ int aa_ns_admit_load_set(struct aa_ns *ns, struct list_head 
*lh,
 
                bytes = ent->new->resident_size;
                if (!(ent->new->label.flags & FLAG_NULL)) {
-                       error = aa_ns_admit_profile_size(ns, limits, bytes);
+                       error = aa_ns_admit_profile_size(mp_owner, mp_limit,
+                                                        bytes);
                        if (error) {
                                *fail_ent = ent;
                                *info = "profile exceeds max_profile cap";
@@ -436,18 +532,25 @@ int aa_ns_admit_load_set(struct aa_ns *ns, struct 
list_head *lh,
        if (!udata->dents[AAFS_LOADDATA_DIR] && aa_g_export_binary)
                new_bytes += aa_loaddata_resident_size(udata);
 
-       error = aa_ns_admit_resident(ns, limits, new_bytes - old_bytes);
+       error = aa_ns_admit_resident(ns, &pend->limits, new_bytes - old_bytes);
        if (error) {
                *fail_ent = NULL;       /* whole-set breach, not one profile */
                *info = "namespace memory cap exceeded";
                return error;
        }
-       error = aa_ns_admit_count(ns, limits, new_count - old_count);
+       error = aa_ns_admit_count(ns, &pend->limits, new_count - old_count);
        if (error) {
                *fail_ent = NULL;
                *info = "namespace profile cap exceeded";
                return error;
        }
+       error = admit_subtree_agg(ns, pend, new_bytes - old_bytes,
+                                 new_count - old_count);
+       if (error) {
+               *fail_ent = NULL;
+               *info = "subtree cap exceeded";
+               return error;
+       }
        return 0;
 }
 
@@ -575,24 +678,34 @@ static void apply_budget_keys(struct aa_ns_caps *dst, 
struct aa_ns_budget *b,
  */
 int aa_ns_apply_budget(struct aa_ns_capset *caps, struct aa_ns_budget *b)
 {
+       bool subtree = b->scope == AA_POLICYNS_SCOPE_SUBTREE;
+
        /* Some features remains to be implemented and are rejected with 
-EOPNOTSUPP. */
-       if (b->scope == AA_POLICYNS_SCOPE_SUBTREE)
-               return -EOPNOTSUPP;
        if (b->specified & ((1u << AA_POLICYNS_KEY_CRIU) |
                            (1u << AA_POLICYNS_KEY_LOAD_RATE)))
                return -EOPNOTSUPP;
+       /* the parser rejects subtree scope on the other keys at parse time */
+       if (subtree && (b->specified & ~AA_POLICYNS_SUBTREE_KEYS))
+               return -EINVAL;
 
        switch (b->target) {
        case AA_POLICYNS_TGT_SELF:
                if (b->percent)         /* % is a per-child ratio only */
                        return -EOPNOTSUPP;
-               apply_budget_keys(&caps->limits, b, true);
+               apply_budget_keys(subtree ? &caps->subtree : &caps->limits, b,
+                                 true);
                return 0;
        case AA_POLICYNS_TGT_CHILDREN:
-               /* the block is the template: last children block wins */
-               aa_ns_caps_init_unset(&caps->child);
-               apply_budget_keys(&caps->child, b, false);
-               caps->child_percent = b->percent;
+               /* the block is the template for its scope: last block wins */
+               if (subtree) {
+                       aa_ns_caps_init_unset(&caps->child_subtree);
+                       apply_budget_keys(&caps->child_subtree, b, false);
+                       caps->child_subtree_percent = b->percent;
+               } else {
+                       aa_ns_caps_init_unset(&caps->child);
+                       apply_budget_keys(&caps->child, b, false);
+                       caps->child_percent = b->percent;
+               }
                return 0;
        default:        /* descendants/root/:NAME: not yet enforced */
                return -EOPNOTSUPP;
@@ -619,6 +732,7 @@ static void inherit_child_caps(struct aa_ns *child, struct 
aa_ns *parent)
        struct aa_ns_caps *cl = &child->acct.caps.limits;
        struct aa_ns_acct *pa = &parent->acct;
        struct aa_ns_caps t = parent->acct.caps.child;
+       struct aa_ns_caps st;
 
        /*
         * Resolve percentage keys against the parent's cap in a local copy of
@@ -640,6 +754,12 @@ static void inherit_child_caps(struct aa_ns *child, struct 
aa_ns *parent)
        cl->criu = cap_min(t.criu, pl->criu);
        cl->load_rate = cap_min(t.load_rate, pl->load_rate);
        cl->depth = cap_min(t.depth, cap_dec(pl->depth));
+
+       /* subtree template: percentages resolve against the parent's own cap */
+       st = parent->acct.caps.child_subtree;
+       resolve_percent_caps(&st, parent->acct.caps.child_subtree_percent,
+                            &parent->acct.caps.subtree);
+       child->acct.caps.subtree = st;
 }
 
 /*
-- 
2.51.0


Reply via email to