Accept budget blocks targeting a namespace other than the one being
loaded into. They apply as a two-phase transaction and everything
fallible happens before the load commits.

descendants and root are tighten-only. :NAME: overwrites and may raise a
target's caps, but never past its parent's remaining headroom.

Advertise the descendants, root and name tokens.

Signed-off-by: Maxime Bélair <[email protected]>
---
 security/apparmor/apparmorfs.c        |  10 +-
 security/apparmor/include/policy_ns.h |   8 +
 security/apparmor/policy.c            |  15 +-
 security/apparmor/policy_ns.c         | 276 ++++++++++++++++++++++++--
 4 files changed, 276 insertions(+), 33 deletions(-)

diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 235edbe33dcb..71e9c7872348 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -2691,15 +2691,9 @@ static struct aa_sfs_entry aa_sfs_entry_versions[] = {
 };
 
 #define PERMS32STR "allow deny subtree cond kill complain prompt audit quiet 
hide xindex tag label"
+#define NS_QUOTA_MASKSTR "self children descendants root name percent local 
subtree criu mediation"
 static struct aa_sfs_entry aa_sfs_entry_ns_quota[] = {
-       /*
-        * Space-separated list of the policyns constructs this kernel
-        * enforces, so userspace emits a construct only when it is present.
-        * 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 subtree criu 
mediation"),
+       AA_SFS_FILE_STRING("mask", NS_QUOTA_MASKSTR),
        { }
 };
 
diff --git a/security/apparmor/include/policy_ns.h 
b/security/apparmor/include/policy_ns.h
index ed6be9f9b3fa..0add55e2d19c 100644
--- a/security/apparmor/include/policy_ns.h
+++ b/security/apparmor/include/policy_ns.h
@@ -165,6 +165,14 @@ int aa_ns_admit_load_set(struct aa_ns *ns, struct 
list_head *lh,
 /* 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);
+/* stage one budget block: apply self/children to the tentative capset,
+ * queue routed (descendants/root/:NAME:) blocks for post-commit stamping
+ */
+int aa_ns_stage_budget(struct aa_ns *ns, struct aa_ns_capset *pend,
+                      struct aa_ns_budget *b, struct list_head *routed,
+                      const char **info);
+void aa_ns_budget_stamp_routed(struct list_head *routed);
+void aa_ns_budget_free_routed(struct list_head *routed);
 /* 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 824cca42eb8c..e4638bc383dc 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -1280,6 +1280,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
        const char *op;
        ssize_t count, error;
        LIST_HEAD(lh);
+       LIST_HEAD(routed);
 
        op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
        aa_get_profile_loaddata(udata);
@@ -1440,14 +1441,11 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
                        int b;
 
                        for (b = 0; b < ent->new->n_budgets; b++) {
-                               error = aa_ns_apply_budget(&pend_caps,
-                                                          
&ent->new->budgets[b]);
-                               if (error) {
-                                       info = error == -EINVAL ?
-                                               "policyns limits: invalid 
construct" :
-                                               "policyns limits: unsupported 
construct";
+                               error = aa_ns_stage_budget(ns, &pend_caps,
+                                                          
&ent->new->budgets[b],
+                                                          &routed, &info);
+                               if (error)
                                        goto fail_lock;
-                               }
                        }
                }
        } else {
@@ -1561,10 +1559,13 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, 
struct aa_label *label,
        }
        __aa_labelset_update_subtree(ns);
        mutex_unlock(&ns->lock);
+       /* the load committed; stamp its routed budget blocks */
+       aa_ns_budget_stamp_routed(&routed);
        if (subtree_locked)
                mutex_unlock(&aa_ns_subtree_lock);
 
 out:
+       aa_ns_budget_free_routed(&routed);
        aa_put_ns(ns);
 
        ssize_t udata_sz = udata->size;
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c
index 0643c8319627..1c7ba7b175ac 100644
--- a/security/apparmor/policy_ns.c
+++ b/security/apparmor/policy_ns.c
@@ -14,6 +14,7 @@
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/ratelimit.h>
+#include <linux/rculist.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 
@@ -727,8 +728,8 @@ int aa_ns_apply_budget(struct aa_ns_capset *caps, struct 
aa_ns_budget *b)
                        caps->child_percent = b->percent;
                }
                return 0;
-       default:        /* descendants/root/:NAME: not yet enforced */
-               return -EOPNOTSUPP;
+       default:        /* routed targets go through aa_ns_budget_route() */
+               return -EINVAL;
        }
 }
 
@@ -745,14 +746,265 @@ static void resolve_percent_caps(struct aa_ns_caps 
*caps, u32 percent,
                        cap[k] = cap_percent(b[k], cap[k]);
 }
 
+/*
+ * Budget blocks targeting another namespace (descendants, root, :NAME:)
+ * apply as a two-phase transaction: everything fallible happens in
+ * aa_ns_budget_route() before the load commits, the infallible stamping in
+ * aa_ns_budget_stamp_routed() after the load's ns->lock is released, one
+ * target lock at a time.
+ */
+
+/* one routed budget block resolved to its target namespaces */
+struct aa_ns_routed_budget {
+       struct list_head list;
+       struct aa_ns_budget b;          /* value copy; name not carried over */
+       int nr_targets;
+       struct aa_ns *targets[] __counted_by(nr_targets);
+};
+
+/* next namespace in a depth-first walk below @root, NULL when done */
+static struct aa_ns *next_ns_dfs(struct aa_ns *root, struct aa_ns *cur)
+{
+       struct aa_ns *ns;
+
+       ns = list_first_or_null_rcu(&cur->sub_ns, struct aa_ns, base.list);
+       if (ns)
+               return ns;
+       while (cur != root) {
+               ns = list_next_or_null_rcu(&cur->parent->sub_ns,
+                                          &cur->base.list, struct aa_ns,
+                                          base.list);
+               if (ns)
+                       return ns;
+               cur = cur->parent;
+       }
+       return NULL;
+}
+
+/**
+ * aa_ns_budget_route - resolve a routed budget block's targets (fallible)
+ * @ns: the namespace the load carrying @b targets
+ * @b: a budget block aimed at descendants, root or a named namespace
+ * @routed: list the resolved aa_ns_routed_budget is appended to
+ * @info: out - audit cause string; only set when failing
+ *
+ * Requires: @ns->lock held.
+ *
+ * Returns: 0 with the block queued on @routed, or a negative errno.
+ */
+static int aa_ns_budget_route(struct aa_ns *ns, struct aa_ns_budget *b,
+                             struct list_head *routed, const char **info)
+{
+       struct aa_ns_routed_budget *r;
+       struct aa_ns *target, *cur;
+       int n, i;
+
+       /* the parser rejects subtree scope on the other keys at parse time */
+       if (b->scope == AA_POLICYNS_SCOPE_SUBTREE &&
+           (b->specified & ~AA_POLICYNS_SUBTREE_KEYS)) {
+               *info = "policyns limits: invalid construct";
+               return -EINVAL;
+       }
+
+       switch (b->target) {
+       case AA_POLICYNS_TGT_ROOT:
+               /* only a host policy admin may cap the root namespace, and
+                * root has no parent to resolve a percentage against
+                */
+               if (b->percent) {
+                       *info = "policyns limits: invalid construct";
+                       return -EINVAL;
+               }
+               if (!aa_current_policy_admin_capable(root_ns)) {
+                       *info = "policyns limits: not permitted to target the 
root namespace";
+                       return -EPERM;
+               }
+               target = aa_get_ns(root_ns);
+               n = 1;
+               break;
+       case AA_POLICYNS_TGT_NAME:
+               target = aa_lookupn_ns(ns, b->name, strlen(b->name));
+               if (!target) {
+                       *info = "policyns limits: target namespace not found";
+                       return -ENOENT;
+               }
+               n = 1;
+               break;
+       case AA_POLICYNS_TGT_DESCENDANTS:
+               /* count, then collect refs; the tree may change in between */
+               n = 0;
+               rcu_read_lock();
+               for (cur = next_ns_dfs(ns, ns); cur;
+                    cur = next_ns_dfs(ns, cur))
+                       n++;
+               rcu_read_unlock();
+               target = NULL;
+               break;
+       default:
+               *info = "policyns limits: invalid construct";
+               return -EINVAL;
+       }
+
+       r = kzalloc(struct_size(r, targets, n), GFP_KERNEL);
+       if (!r) {
+               aa_put_ns(target);
+               *info = "policyns limits: out of memory";
+               return -ENOMEM;
+       }
+       r->b = *b;
+       r->b.name = NULL;       /* owned by the profile, not needed to stamp */
+       r->nr_targets = n;
+       if (target) {
+               r->targets[0] = target;
+       } else {
+               rcu_read_lock();
+               for (cur = next_ns_dfs(ns, ns), i = 0; cur && i < n;
+                    cur = next_ns_dfs(ns, cur), i++)
+                       r->targets[i] = aa_get_ns(cur);
+               rcu_read_unlock();
+               /* the tree may have shrunk between the passes */
+               r->nr_targets = i;
+       }
+       list_add_tail(&r->list, routed);
+       return 0;
+}
+
+/* per-key headroom under @p's local caps. Shared by child inheritance and the
+ * :NAME: routed clamp.
+ */
+static long parent_headroom(struct aa_ns *p, int k)
+{
+       struct aa_ns_caps *pl = &p->acct.caps.limits;
+
+       switch (k) {
+       case AA_POLICYNS_KEY_MEMORY:
+               return cap_remaining(pl->memory,
+                                    atomic_long_read(&p->acct.resident));
+       case AA_POLICYNS_KEY_PROFILES:
+               return cap_remaining(pl->profiles,
+                                    atomic_long_read(&p->acct.profile_count));
+       case AA_POLICYNS_KEY_NAMESPACES:
+               return cap_remaining(pl->namespaces,
+                                    atomic_long_read(&p->acct.ns_count));
+       case AA_POLICYNS_KEY_DEPTH:
+               return cap_dec(pl->depth);
+       default:        /* max_profile, criu, load_rate: the parent's cap */
+               return ((long *)pl)[k];
+       }
+}
+
+/* stamp one routed block onto @t; requires no other ns lock be held */
+static void stamp_routed_budget(struct aa_ns *t, struct aa_ns_budget *b)
+{
+       bool subtree = b->scope == AA_POLICYNS_SCOPE_SUBTREE;
+       struct aa_ns *p = t->parent;
+       struct aa_ns_budget rb = *b;
+       int k;
+
+       /* parent-then-target, the namespace-creation nesting order; root as
+        * a target has no parent to lock or resolve against
+        */
+       if (p)
+               mutex_lock_nested(&p->lock, p->level);
+       mutex_lock_nested(&t->lock, t->level);
+       for (k = 0; k < AA_POLICYNS_KEY_MAX; k++) {
+               if (!(rb.specified & (1u << k)))
+                       continue;
+               if (rb.percent & (1u << k))
+                       rb.values[k] = cap_percent(((const long *)
+                                       (subtree ? &p->acct.caps.subtree
+                                                : &p->acct.caps.limits))[k],
+                                       rb.values[k]);
+               /*
+                * :NAME: overwrites and may raise a local cap, but never past
+                * the parent's remaining headroom to avoid privlege gains.
+                */
+               if (b->target == AA_POLICYNS_TGT_NAME && !subtree)
+                       rb.values[k] = cap_min(rb.values[k],
+                                              parent_headroom(p, k));
+       }
+       apply_budget_keys(subtree ? &t->acct.caps.subtree
+                                 : &t->acct.caps.limits, &rb,
+                         b->target != AA_POLICYNS_TGT_NAME);
+       mutex_unlock(&t->lock);
+       if (p)
+               mutex_unlock(&p->lock);
+}
+
+/**
+ * aa_ns_budget_stamp_routed - apply resolved routed blocks (infallible)
+ * @routed: list of aa_ns_routed_budget built by aa_ns_budget_route()
+ *
+ * Phase two of the routed transaction; called once the load has committed
+ * and released its ns->lock. Requires aa_ns_subtree_lock be held when any
+ * block carries subtree scope.
+ */
+void aa_ns_budget_stamp_routed(struct list_head *routed)
+{
+       struct aa_ns_routed_budget *r;
+       int i;
+
+       list_for_each_entry(r, routed, list)
+               for (i = 0; i < r->nr_targets; i++)
+                       stamp_routed_budget(r->targets[i], &r->b);
+}
+
+/**
+ * aa_ns_budget_free_routed - put the target refs and free a routed list
+ * @routed: list of aa_ns_routed_budget; empty on return
+ */
+void aa_ns_budget_free_routed(struct list_head *routed)
+{
+       struct aa_ns_routed_budget *r, *tmp;
+       int i;
+
+       list_for_each_entry_safe(r, tmp, routed, list) {
+               for (i = 0; i < r->nr_targets; i++)
+                       aa_put_ns(r->targets[i]);
+               list_del(&r->list);
+               kfree(r);
+       }
+}
+
+/**
+ * aa_ns_stage_budget - stage one budget block of a policy load
+ * @ns: the namespace the load targets
+ * @pend: the load's tentative capset for @ns
+ * @b: the parsed budget block
+ * @routed: list routed (descendants/root/:NAME:) blocks are queued on
+ * @info: out - audit cause string; only set when failing
+ *
+ * Requires: @ns->lock held.
+ */
+int aa_ns_stage_budget(struct aa_ns *ns, struct aa_ns_capset *pend,
+                      struct aa_ns_budget *b, struct list_head *routed,
+                      const char **info)
+{
+       int error;
+
+       switch (b->target) {
+       case AA_POLICYNS_TGT_SELF:
+       case AA_POLICYNS_TGT_CHILDREN:
+               error = aa_ns_apply_budget(pend, b);
+               if (error)
+                       *info = error == -EINVAL ?
+                               "policyns limits: invalid construct" :
+                               "policyns limits: unsupported construct";
+               return error;
+       default:
+               return aa_ns_budget_route(ns, b, routed, info);
+       }
+}
+
 /* inherit_child_caps - compute a new child's caps from @parent's template */
 static void inherit_child_caps(struct aa_ns *child, struct aa_ns *parent)
 {
        struct aa_ns_caps *pl = &parent->acct.caps.limits;
-       struct aa_ns_caps *cl = &child->acct.caps.limits;
-       struct aa_ns_acct *pa = &parent->acct;
+       long *cl = (long *)&child->acct.caps.limits;
        struct aa_ns_caps t = parent->acct.caps.child;
+       long *tp = (long *)&t;
        struct aa_ns_caps st;
+       int k;
 
        /*
         * Resolve percentage keys against the parent's cap in a local copy of
@@ -760,20 +1012,8 @@ static void inherit_child_caps(struct aa_ns *child, 
struct aa_ns *parent)
         * they stand at its creation.
         */
        resolve_percent_caps(&t, parent->acct.caps.child_percent, pl);
-
-       cl->memory = cap_min(t.memory,
-                            cap_remaining(pl->memory,
-                                          atomic_long_read(&pa->resident)));
-       cl->profiles = cap_min(t.profiles,
-                              cap_remaining(pl->profiles,
-                                            
atomic_long_read(&pa->profile_count)));
-       cl->namespaces = cap_min(t.namespaces,
-                                cap_remaining(pl->namespaces,
-                                              
atomic_long_read(&pa->ns_count)));
-       cl->max_profile = cap_min(t.max_profile, pl->max_profile);
-       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));
+       for (k = 0; k < AA_POLICYNS_KEY_MAX; k++)
+               cl[k] = cap_min(tp[k], parent_headroom(parent, k));
 
        /* subtree template: percentages resolve against the parent's own cap */
        st = parent->acct.caps.child_subtree;
-- 
2.51.0


Reply via email to