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
