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


Reply via email to