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
