Introduce the AA_CLASS_POLICY_NS mediation class for policy-namespace
resource controls, and the OP_NS_QUOTA operation with limit, requested
and available audit fields. Register the class in unconfined_mediates[]
so unconfined container managers can mediate it. Add the
apparmor_policy_ns_quota sysctl/tunable that gates quota enforcement.

Signed-off-by: Maxime Bélair <[email protected]>
---
 security/apparmor/audit.c            |  2 +-
 security/apparmor/include/apparmor.h |  2 ++
 security/apparmor/include/audit.h    |  5 +++++
 security/apparmor/lsm.c              | 31 ++++++++++++++++++++++++++++
 security/apparmor/policy.c           |  3 ++-
 5 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c
index 4a60b6fda75f..0272ca6a3282 100644
--- a/security/apparmor/audit.c
+++ b/security/apparmor/audit.c
@@ -60,7 +60,7 @@ static const char *const aa_class_names[] = {
        "lsm",
        "namespace",
        "io_uring",
-       "unknown",
+       "policy_namespace",
        "unknown",
        "unknown",
        "unknown",
diff --git a/security/apparmor/include/apparmor.h 
b/security/apparmor/include/apparmor.h
index cc6e3df1bc62..21aea22a3365 100644
--- a/security/apparmor/include/apparmor.h
+++ b/security/apparmor/include/apparmor.h
@@ -35,6 +35,7 @@
 #define AA_CLASS_DISPLAY_LSM   20
 #define AA_CLASS_NS            21
 #define AA_CLASS_IO_URING      22
+#define AA_CLASS_POLICY_NS     23
 
 #define AA_CLASS_X             31
 #define AA_CLASS_DBUS          32
@@ -52,6 +53,7 @@ extern int aa_g_rawdata_compression_level;
 extern bool aa_g_lock_policy;
 extern bool aa_g_logsyscall;
 extern bool aa_g_paranoid_load;
+extern int aa_g_policy_ns_quota;
 extern unsigned int aa_g_path_max;
 
 #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
diff --git a/security/apparmor/include/audit.h 
b/security/apparmor/include/audit.h
index aa00b34404f9..cf62c2994233 100644
--- a/security/apparmor/include/audit.h
+++ b/security/apparmor/include/audit.h
@@ -105,6 +105,8 @@ enum audit_type {
 
 #define OP_USERNS_CREATE "userns_create"
 
+#define OP_NS_QUOTA "ns_quota"
+
 #define OP_URING_OVERRIDE "uring_override"
 #define OP_URING_SQPOLL "uring_sqpoll"
 
@@ -153,6 +155,9 @@ struct apparmor_audit_data {
                        struct aa_profile *profile;
                        const char *ns;
                        long pos;
+                       const char *limit;      /* name of the exceeded cap */
+                       long requested;         /* what the operation asked for 
*/
+                       long available;         /* headroom remaining under the 
cap */
                } iface;
                struct {
                        const char *src_name;
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 88d12e89d115..c12a43e6544b 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -1890,6 +1890,12 @@ module_param_named(path_max, aa_g_path_max, aauint, 
S_IRUSR);
 bool aa_g_paranoid_load = IS_ENABLED(CONFIG_SECURITY_APPARMOR_PARANOID_LOAD);
 module_param_named(paranoid_load, aa_g_paranoid_load, aabool, S_IRUGO);
 
+/* Policyns resource quota enforcement.
+ * Default on so a configured children/:NAME: cap binds out of the box.
+ * With no cap configured nothing is limited.
+ */
+int aa_g_policy_ns_quota = 1;
+
 static int param_get_aaintbool(char *buffer, const struct kernel_param *kp);
 static int param_set_aaintbool(const char *val, const struct kernel_param *kp);
 #define param_check_aaintbool param_check_int
@@ -2332,6 +2338,24 @@ static int apparmor_dointvec(const struct ctl_table 
*table, int write,
        return proc_dointvec(table, write, buffer, lenp, ppos);
 }
 
+/*
+ * The quota knob constrains admins of child policy namespaces, so admin
+ * over the caller's own ns is not enough: a confined-but-root container
+ * manager could flip it and lift its own caps. Require admin over the
+ * root ns, which a task confined to a child ns can never hold.
+ */
+static int apparmor_dointvec_root_admin(const struct ctl_table *table,
+                                       int write, void *buffer,
+                                       size_t *lenp, loff_t *ppos)
+{
+       if (!aa_current_policy_admin_capable(root_ns))
+               return -EPERM;
+       if (!apparmor_enabled)
+               return -EINVAL;
+
+       return proc_dointvec(table, write, buffer, lenp, ppos);
+}
+
 static const struct ctl_table apparmor_sysctl_table[] = {
 #ifdef CONFIG_USER_NS
        {
@@ -2356,6 +2380,13 @@ static const struct ctl_table apparmor_sysctl_table[] = {
                .mode           = 0600,
                .proc_handler   = apparmor_dointvec,
        },
+       {
+               .procname       = "apparmor_policy_ns_quota",
+               .data           = &aa_g_policy_ns_quota,
+               .maxlen         = sizeof(int),
+               .mode           = 0600,
+               .proc_handler   = apparmor_dointvec_root_admin,
+       },
 };
 
 static int __init apparmor_init_sysctl(void)
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 23dfb62e76b8..b9f7312331d7 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -510,7 +510,8 @@ static inline bool ANY_RULE_MEDIATES(struct aa_profile 
*profile,
 }
 
 /* set of rules that are mediated by unconfined */
-static int unconfined_mediates[] = { AA_CLASS_NS, AA_CLASS_IO_URING, 0 };
+static int unconfined_mediates[] = { AA_CLASS_NS, AA_CLASS_IO_URING,
+                                    AA_CLASS_POLICY_NS, 0 };
 
 /* must be called after profile rulesets and start information is setup */
 void aa_compute_profile_mediates(struct aa_profile *profile)
-- 
2.51.0


Reply via email to