Turn the long-dead struct aa_ns_acct into a per-namespace resident /
profile / namespace usage counters.

Charge a profile's resident policy (the construction-time resident_size) to
its namespace when it goes live and uncharge it when it goes dead.

Retained rawdata blobs are also part of the namespace's resident policy
footprint

Track the direct child-namespace count as namespaces are created and
removed.

Signed-off-by: Maxime Bélair <[email protected]>
---
 security/apparmor/apparmorfs.c        |  3 +
 security/apparmor/include/policy.h    | 98 +++++++++++++++++++++++++++
 security/apparmor/include/policy_ns.h | 38 ++++++++---
 security/apparmor/policy.c            | 15 +++-
 security/apparmor/policy_ns.c         | 89 ++++++++++++++++++++++++
 5 files changed, 233 insertions(+), 10 deletions(-)

diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 152c7967ff1b..7d6337c049da 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -1633,6 +1633,7 @@ void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata)
        if (rawdata->ns) {
                remove_rawdata_dents(rawdata);
                list_del_init(&rawdata->list);
+               aa_ns_uncharge_rawdata(rawdata->ns, rawdata);
                aa_put_ns(rawdata->ns);
                rawdata->ns = NULL;
        }
@@ -1700,6 +1701,8 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct 
aa_loaddata *rawdata)
 
        rawdata->ns = aa_get_ns(ns);
        list_add(&rawdata->list, &ns->rawdata_list);
+       /* retained rawdata is resident policy; charged while on the list */
+       aa_ns_charge_rawdata(ns, rawdata);
 
        return 0;
 
diff --git a/security/apparmor/include/policy.h 
b/security/apparmor/include/policy.h
index 8beac1f9d6dc..22c6a80a7596 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -177,6 +177,100 @@ struct aa_data {
        struct rhash_head head;
 };
 
+#define AA_NS_NOLIMIT (-1L)
+
+/* struct aa_ns_caps - standing resource caps for a policy namespace
+ * @memory: max resident policy bytes for the namespace
+ * @max_profile: max resident bytes for any single profile loaded into the ns
+ * @profiles: max number of (non-null) profiles in the namespace
+ * @namespaces: max number of direct child namespaces
+ * @depth: max relative nesting depth permitted below the namespace
+ * @criu: max criu reserve bytes
+ * @load_rate: max load/replace ops per minute
+ *
+ * Each field is a maximum (implicit <=).
+ * AA_NS_NOLIMIT (-1) means unlimited and  0 means deny.
+ * Size caps are bytes, count caps are objects.
+ */
+struct aa_ns_caps {
+       long memory;
+       long max_profile;
+       long profiles;
+       long namespaces;
+       long depth;
+       long criu;
+       long load_rate;
+};
+
+
+/*
+ * Wire encoding of a "policyns limits" block. These constants are wire ABI
+ * and MUST match parser/policyns.h;
+ */
+#define AA_POLICYNS_TGT_SELF           0
+#define AA_POLICYNS_TGT_CHILDREN       1
+#define AA_POLICYNS_TGT_DESCENDANTS    2
+#define AA_POLICYNS_TGT_ROOT           3
+#define AA_POLICYNS_TGT_NAME           4
+
+#define AA_POLICYNS_SCOPE_UNSPEC       0
+#define AA_POLICYNS_SCOPE_LOCAL                1
+#define AA_POLICYNS_SCOPE_SUBTREE      2
+
+enum aa_policyns_key {
+       AA_POLICYNS_KEY_MEMORY,
+       AA_POLICYNS_KEY_MAX_PROFILE,
+       AA_POLICYNS_KEY_PROFILES,
+       AA_POLICYNS_KEY_NAMESPACES,
+       AA_POLICYNS_KEY_DEPTH,
+       AA_POLICYNS_KEY_CRIU,
+       AA_POLICYNS_KEY_LOAD_RATE,
+       AA_POLICYNS_KEY_MAX             /* wire value-array length */
+};
+
+#define AA_POLICYNS_KEY_ASSERT(key, field)                             \
+       static_assert(offsetof(struct aa_ns_caps, field) ==             \
+                     (key) * sizeof(long),                             \
+                     "aa_ns_caps." #field " must sit at wire key " #key)
+AA_POLICYNS_KEY_ASSERT(AA_POLICYNS_KEY_MEMORY, memory);
+AA_POLICYNS_KEY_ASSERT(AA_POLICYNS_KEY_MAX_PROFILE, max_profile);
+AA_POLICYNS_KEY_ASSERT(AA_POLICYNS_KEY_PROFILES, profiles);
+AA_POLICYNS_KEY_ASSERT(AA_POLICYNS_KEY_NAMESPACES, namespaces);
+AA_POLICYNS_KEY_ASSERT(AA_POLICYNS_KEY_DEPTH, depth);
+AA_POLICYNS_KEY_ASSERT(AA_POLICYNS_KEY_CRIU, criu);
+AA_POLICYNS_KEY_ASSERT(AA_POLICYNS_KEY_LOAD_RATE, load_rate);
+#undef AA_POLICYNS_KEY_ASSERT
+/* no padding/extra fields: the whole struct is exactly the keyed longs */
+static_assert(sizeof(struct aa_ns_caps) ==
+             AA_POLICYNS_KEY_MAX * sizeof(long));
+
+/* 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)
+{
+       long *cap = (long *)c;
+       int k;
+
+       for (k = 0; k < AA_POLICYNS_KEY_MAX; k++)
+               cap[k] = AA_NS_NOLIMIT;
+}
+
+/* struct aa_ns_budget - one parsed "policyns limits" block from a profile
+ * @target: which namespace the block addresses (AA_POLICYNS_TGT_*)
+ * @scope: local/subtree accounting scope (AA_POLICYNS_SCOPE_*)
+ * @specified: bitmask of keys present (bit k == key k)
+ * @percent: bitmask of keys whose value is a percentage of the parent
+ * @values: cap values by key, valid only where @specified has the bit set
+ * @name: literal ns name for AA_POLICYNS_TGT_NAME (owned), else NULL
+ */
+struct aa_ns_budget {
+       u32 target;
+       u32 scope;
+       u32 specified;
+       u32 percent;
+       long values[AA_POLICYNS_KEY_MAX];
+       char *name;
+};
+
 /* struct aa_ruleset - data covering mediation rules
  * @list: list the rule is on
  * @policy: general match rules governing policy
@@ -274,8 +368,12 @@ struct aa_profile {
 
        int n_rules;
 
+       long acct_resident;
        long resident_size;
 
+       struct aa_ns_budget *budgets;
+       int n_budgets;
+
        /* special - variable length must be last entry in profile */
        struct aa_label label;
 };
diff --git a/security/apparmor/include/policy_ns.h 
b/security/apparmor/include/policy_ns.h
index cc6e84151812..d6eb6c89643f 100644
--- a/security/apparmor/include/policy_ns.h
+++ b/security/apparmor/include/policy_ns.h
@@ -12,26 +12,37 @@
 #define __AA_NAMESPACE_H
 
 #include <linux/kref.h>
+#include <linux/ratelimit.h>
 
 #include "apparmor.h"
 #include "apparmorfs.h"
 #include "label.h"
 #include "policy.h"
 
+struct apparmor_audit_data;
+
 /* Match max depth of user namespaces */
 #define MAX_NS_DEPTH 32
 
-/* struct aa_ns_acct - accounting of profiles in namespace
- * @max_size: maximum space allowed for all profiles in namespace
- * @max_count: maximum number of profiles that can be in this namespace
- * @size: current size of profiles
- * @count: current count of profiles (includes null profiles)
+/* default per-ns audit ratelimit for OP_NS_QUOTA emission */
+#define AA_NS_QUOTA_RATELIMIT_INTERVAL (5 * HZ)
+#define AA_NS_QUOTA_RATELIMIT_BURST    10
+
+/* struct aa_ns_acct - per-namespace resource accounting and caps
+ * @limits: caps enforced against this namespace (self)
+ * @child: template caps stamped onto namespaces this namespace creates 
(children)
+ * @resident: current resident policy bytes charged to this ns (local scope)
+ * @profile_count: current count of non-null profiles in this ns (local)
+ * @ns_count: current number of direct child namespaces
+ * @ratelimit: bounds OP_NS_QUOTA audit emission
  */
 struct aa_ns_acct {
-       int max_size;
-       int max_count;
-       int size;
-       int count;
+       struct aa_ns_caps limits;
+       struct aa_ns_caps child;
+       atomic_long_t resident;
+       atomic_long_t profile_count;
+       atomic_long_t ns_count;
+       struct ratelimit_state ratelimit;
 };
 
 /* struct aa_ns - namespace for a set of profiles
@@ -96,6 +107,15 @@ struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, 
const char *name,
 struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name);
 void __aa_remove_ns(struct aa_ns *ns);
 
+/* policy-namespace resource accounting (see policy-ns quota feature) */
+struct aa_loaddata;
+void aa_ns_acct_init(struct aa_ns *ns);
+void aa_ns_charge_profile(struct aa_profile *profile);
+void aa_ns_uncharge_profile(struct aa_profile *profile);
+/* retained rawdata accounting, at the ns->rawdata_list add/remove points */
+void aa_ns_charge_rawdata(struct aa_ns *ns, struct aa_loaddata *data);
+void aa_ns_uncharge_rawdata(struct aa_ns *ns, struct aa_loaddata *data);
+
 static inline struct aa_profile *aa_deref_parent(struct aa_profile *p)
 {
        return rcu_dereference_protected(p->parent,
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index b9f7312331d7..449d5ad1bcde 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -245,6 +245,8 @@ static void __add_profile(struct list_head *list, struct 
aa_profile *profile)
        l = aa_label_insert(&profile->ns->labels, &profile->label);
        AA_BUG(l != &profile->label);
        aa_put_label(l);
+       /* charge resident policy to the namespace as the profile goes live */
+       aa_ns_charge_profile(profile);
 }
 
 /**
@@ -265,6 +267,8 @@ static void __list_remove_profile(struct aa_profile 
*profile)
        AA_BUG(!profile->ns);
        AA_BUG(!mutex_is_locked(&profile->ns->lock));
 
+       /* release the namespace resident charge as the profile goes dead */
+       aa_ns_uncharge_profile(profile);
        list_del_rcu(&profile->base.list);
        aa_put_profile(profile);
 }
@@ -409,6 +413,9 @@ void aa_free_profile(struct aa_profile *profile)
        if (!profile)
                return;
 
+       /* Resident policy must already be uncharged */
+       AA_BUG(profile->acct_resident);
+
        /* free children profiles */
        aa_policy_destroy(&profile->base);
        aa_put_profile(rcu_access_pointer(profile->parent));
@@ -1172,10 +1179,16 @@ static void __replace_profile(struct aa_profile *old, 
struct aa_profile *new)
        if (list_empty(&new->base.list)) {
                /* new is not on a list already */
                list_replace_rcu(&old->base.list, &new->base.list);
+               /* @new goes live in place of @old: swap their ns charges */
+               aa_ns_charge_profile(new);
+               aa_ns_uncharge_profile(old);
                aa_get_profile(new);
                aa_put_profile(old);
-       } else
+       } else {
+               /* @new is already on a list. Charge it if needed, then drop 
@old. */
+               aa_ns_charge_profile(new);
                __list_remove_profile(old);
+       }
 }
 
 /**
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c
index 5a907a875d8f..fd375eb3f015 100644
--- a/security/apparmor/policy_ns.c
+++ b/security/apparmor/policy_ns.c
@@ -13,14 +13,17 @@
 
 #include <linux/list.h>
 #include <linux/mutex.h>
+#include <linux/ratelimit.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 
 #include "include/apparmor.h"
+#include "include/audit.h"
 #include "include/cred.h"
 #include "include/policy_ns.h"
 #include "include/label.h"
 #include "include/policy.h"
+#include "include/policy_unpack.h"
 
 /* kernel label */
 struct aa_label *kernel_t;
@@ -117,6 +120,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const 
char *name)
        INIT_LIST_HEAD(&ns->rawdata_list);
        mutex_init(&ns->lock);
        init_waitqueue_head(&ns->wait);
+       aa_ns_acct_init(ns);
 
        /* released by aa_free_ns() */
        ns->unconfined = alloc_unconfined("unconfined");
@@ -159,6 +163,86 @@ void aa_free_ns(struct aa_ns *ns)
        kfree_sensitive(ns);
 }
 
+/* Policy-namespace resource accounting. */
+
+void aa_ns_acct_init(struct aa_ns *ns)
+{
+       struct aa_ns_acct *acct = &ns->acct;
+
+       aa_ns_caps_init_unset(&acct->limits);
+       aa_ns_caps_init_unset(&acct->child);
+       atomic_long_set(&acct->resident, 0);
+       atomic_long_set(&acct->profile_count, 0);
+       atomic_long_set(&acct->ns_count, 0);
+       ratelimit_state_init(&acct->ratelimit,
+                            AA_NS_QUOTA_RATELIMIT_INTERVAL,
+                            AA_NS_QUOTA_RATELIMIT_BURST);
+}
+
+/**
+ * aa_ns_charge_profile - charge a profile's resident policy to its ns
+ * @profile: the profile being made live  (NOT NULL)
+ *
+ * Null profiles are charged for memory but excluded from profile count.
+ */
+void aa_ns_charge_profile(struct aa_profile *profile)
+{
+       struct aa_ns *ns = profile->ns;
+       long bytes;
+
+       if (!ns || profile->acct_resident)
+               return;
+
+       bytes = profile->resident_size;
+       profile->acct_resident = bytes;
+       atomic_long_add(bytes, &ns->acct.resident);
+       if (!(profile->label.flags & FLAG_NULL))
+               atomic_long_inc(&ns->acct.profile_count);
+}
+
+/**
+ * aa_ns_charge_rawdata - charge a retained rawdata blob to @ns
+ * @ns: the namespace retaining the blob  (NOT NULL)
+ * @data: the blob going onto @ns->rawdata_list  (NOT NULL)
+ *
+ * Requires: @ns->lock held.
+ */
+void aa_ns_charge_rawdata(struct aa_ns *ns, struct aa_loaddata *data)
+{
+       atomic_long_add(aa_loaddata_resident_size(data), &ns->acct.resident);
+}
+
+/**
+ * aa_ns_uncharge_rawdata - reverse aa_ns_charge_rawdata()
+ * @ns: the namespace that retained the blob  (NOT NULL)
+ * @data: the blob leaving @ns->rawdata_list  (NOT NULL)
+ *
+ * Requires: @ns->lock held.
+ */
+void aa_ns_uncharge_rawdata(struct aa_ns *ns, struct aa_loaddata *data)
+{
+       atomic_long_sub(aa_loaddata_resident_size(data), &ns->acct.resident);
+}
+
+/**
+ * aa_ns_uncharge_profile - reverse aa_ns_charge_profile()
+ * @profile: the profile being unloaded  (NOT NULL)
+ *
+ * No-op if the profile was never charged.
+ */
+void aa_ns_uncharge_profile(struct aa_profile *profile)
+{
+       struct aa_ns *ns = profile->ns;
+
+       if (!ns || !profile->acct_resident)
+               return;
+
+       atomic_long_sub(profile->acct_resident, &ns->acct.resident);
+       if (!(profile->label.flags & FLAG_NULL))
+               atomic_long_dec(&ns->acct.profile_count);
+       profile->acct_resident = 0;
+}
+
 /**
  * __aa_lookupn_ns - lookup the namespace matching @hname
  * @view: namespace to search in  (NOT NULL)
@@ -240,6 +324,8 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, 
const char *name,
        }
        ns->parent = aa_get_ns(parent);
        list_add_rcu(&ns->base.list, &parent->sub_ns);
+       /* account the new direct child against the parent's breadth cap */
+       atomic_long_inc(&parent->acct.ns_count);
        /* add list ref */
        aa_get_ns(ns);
        mutex_unlock(&ns->lock);
@@ -337,6 +423,9 @@ void __aa_remove_ns(struct aa_ns *ns)
 {
        /* remove ns from namespace list */
        list_del_rcu(&ns->base.list);
+       /* release the parent's breadth accounting for this direct child */
+       if (ns->parent)
+               atomic_long_dec(&ns->parent->acct.ns_count);
        destroy_ns(ns);
        aa_put_ns(ns);
 }
-- 
2.51.0


Reply via email to