Enforce load_rate as a fixed-window meter checked first in the load path. An attempt is metered whether or not the load succeeds, and against the committed cap rather than the load's tentative one, so a load installing a tighter rate is still admitted under the rate it found.
Expose the cap as .load_rate, lift the last budget reject and advertise the load_rate token. Signed-off-by: Maxime Bélair <[email protected]> --- security/apparmor/apparmorfs.c | 4 ++- security/apparmor/include/apparmorfs.h | 1 + security/apparmor/include/policy_ns.h | 9 ++++++ security/apparmor/policy.c | 8 +++++ security/apparmor/policy_ns.c | 41 ++++++++++++++++++++++++-- 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 71e9c7872348..d050a39db685 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1504,6 +1504,7 @@ SEQ_NS_ACCT(acct_namespaces, ns->acct.caps.limits.namespaces); SEQ_NS_ACCT(acct_depth, ns->acct.caps.limits.depth); SEQ_NS_ACCT(acct_criu, ns->acct.caps.limits.criu); SEQ_NS_ACCT(acct_criu_size, atomic_long_read(&ns->acct.criu_resident)); +SEQ_NS_ACCT(acct_load_rate, ns->acct.caps.limits.load_rate); /* policy/raw_data/ * file ops */ @@ -2275,6 +2276,7 @@ static const struct aa_ns_acct_file { { ".depth", &seq_ns_acct_depth_fops, AAFS_NS_DEPTH }, { ".criu", &seq_ns_acct_criu_fops, AAFS_NS_CRIU }, { ".criu_size", &seq_ns_acct_criu_size_fops, AAFS_NS_CRIU_SIZE }, + { ".load_rate", &seq_ns_acct_load_rate_fops, AAFS_NS_LOAD_RATE }, }; static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) @@ -2691,7 +2693,7 @@ 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" +#define NS_QUOTA_MASKSTR "self children descendants root name percent local subtree criu load_rate mediation" static struct aa_sfs_entry aa_sfs_entry_ns_quota[] = { AA_SFS_FILE_STRING("mask", NS_QUOTA_MASKSTR), { } diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 4b7cd92fe350..b7e84ba6da9e 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -84,6 +84,7 @@ enum aafs_ns_type { AAFS_NS_DEPTH, AAFS_NS_CRIU, AAFS_NS_CRIU_SIZE, + AAFS_NS_LOAD_RATE, AAFS_NS_SUBTREE_COUNT, AAFS_NS_SUBTREE_SIZE, AAFS_NS_SIZEOF, diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index 0add55e2d19c..c2a03513a0b2 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -28,6 +28,9 @@ struct apparmor_audit_data; #define AA_NS_QUOTA_RATELIMIT_INTERVAL (5 * HZ) #define AA_NS_QUOTA_RATELIMIT_BURST 10 +/* the load_rate cap is load/replace operations per minute */ +#define AA_NS_LOAD_RATE_INTERVAL (60 * HZ) + /* struct aa_ns_capset - the standing caps a namespace enforces and stamps * @limits: caps enforced against this namespace (self, local scope) * @subtree: caps enforced against this namespace plus all its descendants @@ -64,6 +67,8 @@ static inline void aa_ns_capset_init_unset(struct aa_ns_capset *caps) * @subtree_profile_count: non-null profiles of this ns plus all descendants * @criu_resident: retained raw policy bytes charged to this ns (local) * @subtree_criu: retained raw policy bytes of this ns plus all descendants + * @load_stamp: start (jiffies) of the current load_rate window + * @load_count: load/replace operations metered in the current window * @ratelimit: bounds OP_NS_QUOTA audit emission */ struct aa_ns_acct { @@ -75,6 +80,8 @@ struct aa_ns_acct { atomic_long_t subtree_profile_count; atomic_long_t criu_resident; atomic_long_t subtree_criu; + unsigned long load_stamp; + long load_count; struct ratelimit_state ratelimit; }; @@ -157,6 +164,8 @@ int aa_ns_admit_resident(struct aa_ns *ns, struct aa_ns_caps *limits, /* per-profile and count admission, under ns->lock */ 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); +/* load/replace rate meter, under ns->lock */ +int aa_ns_admit_load_rate(struct aa_ns *ns); /* 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, diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index e4638bc383dc..728b691574de 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -1348,6 +1348,14 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, subtree_locked = true; } mutex_lock_nested(&ns->lock, ns->level); + if (aa_g_policy_ns_quota) { + error = aa_ns_admit_load_rate(ns); + if (error) { + info = "namespace load rate exceeded"; + ent = NULL; + goto fail_lock; + } + } /* Tentative copy of the ns caps */ pend_caps = ns->acct.caps; /* check for duplicate rawdata blobs: space and file dedup */ diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 1c7ba7b175ac..111d5ba9d413 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -221,6 +221,8 @@ void aa_ns_acct_init(struct aa_ns *ns) atomic_long_set(&acct->subtree_profile_count, 0); atomic_long_set(&acct->criu_resident, 0); atomic_long_set(&acct->subtree_criu, 0); + acct->load_stamp = 0; + acct->load_count = 0; ratelimit_state_init(&acct->ratelimit, AA_NS_QUOTA_RATELIMIT_INTERVAL, AA_NS_QUOTA_RATELIMIT_BURST); @@ -470,6 +472,42 @@ bool aa_ns_subtree_in_play(struct aa_ns *ns, struct list_head *lh) return false; } +/** + * aa_ns_admit_load_rate - meter a load/replace attempt against load_rate + * @ns: target namespace of the load + * + * Fixed one-minute window, admitting up to two bursts across a boundary. + * An attempt is metered whether or not the load later succeeds, and against + * the committed cap rather than the load's tentative one, so a load + * installing a tighter rate (even 0) is still admitted under the rate it + * found. + * + * Requires: @ns->lock held. + * + * Returns: 0 to admit, -EAGAIN when the window is exhausted. + */ +int aa_ns_admit_load_rate(struct aa_ns *ns) +{ + struct aa_ns_acct *acct = &ns->acct; + long limit = acct->caps.limits.load_rate; + + if (!aa_g_policy_ns_quota || limit == AA_NS_NOLIMIT) + return 0; + + if (!acct->load_count || + time_after(jiffies, acct->load_stamp + AA_NS_LOAD_RATE_INTERVAL)) { + acct->load_stamp = jiffies; + acct->load_count = 0; + } + if (acct->load_count + 1 > limit) + return ns_quota_deny(ns, AA_POLICYNS_KEY_LOAD_RATE, + acct->load_count + 1, + cap_remaining(limit, acct->load_count), + -EAGAIN); + acct->load_count++; + return 0; +} + /** * aa_ns_admit_load_set - admit a whole replace set against @ns's caps * @ns: target namespace @@ -702,9 +740,6 @@ 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->specified & (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; -- 2.51.0
