Expose the per-namespace accounting and caps through read-only securityfs files.
Advertise the feature as a construct mask: policy/ns_quota/mask lists the policyns constructs this kernel enforces, so the parser emits a construct only when its token is present. A bare boolean could not express this, since aa_features_supports() matches on path existence, not value. Follow-up patches append their token as each enforces its construct. Signed-off-by: Maxime Bélair <[email protected]> --- security/apparmor/apparmorfs.c | 95 ++++++++++++++++++++++++++ security/apparmor/include/apparmorfs.h | 4 ++ 2 files changed, 99 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 7d6337c049da..7509e703f3df 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1460,6 +1460,47 @@ SEQ_NS_FOPS(name); SEQ_NS_FOPS(compress_min); SEQ_NS_FOPS(compress_max); +/* + * Per-namespace resource accounting / cap introspection files. Unlike the + * files above (which report the current task's ns), these report the ns + * owning the dentry. A cap of AA_NS_NOLIMIT is shown as "max". + */ +static void seq_ns_print_cap(struct seq_file *seq, long v) +{ + if (v == AA_NS_NOLIMIT) + seq_puts(seq, "max\n"); + else + seq_printf(seq, "%ld\n", v); +} + +/* + * Caps and counters are read under ns->lock, so each file is a consistent + * snapshot. Usage counters are >= 0, so they never print as "max". + */ +#define SEQ_NS_ACCT(NAME, EXPR) \ +static int seq_ns_ ##NAME ##_show(struct seq_file *seq, void *v) \ +{ \ + struct aa_ns *ns = get_ns_common_ref(seq->private); \ + \ + if (ns) { \ + mutex_lock_nested(&ns->lock, ns->level); \ + seq_ns_print_cap(seq, (EXPR)); \ + mutex_unlock(&ns->lock); \ + aa_put_ns(ns); \ + } \ + return 0; \ +} \ +SEQ_NS_FOPS(NAME) + +SEQ_NS_ACCT(acct_count, atomic_long_read(&ns->acct.profile_count)); +SEQ_NS_ACCT(acct_size, atomic_long_read(&ns->acct.resident)); +SEQ_NS_ACCT(acct_max_count, ns->acct.limits.profiles); +SEQ_NS_ACCT(acct_max_size, ns->acct.limits.memory); +SEQ_NS_ACCT(acct_max_profile, ns->acct.limits.max_profile); +SEQ_NS_ACCT(acct_namespaces, ns->acct.limits.namespaces); +SEQ_NS_ACCT(acct_depth, ns->acct.limits.depth); +SEQ_NS_ACCT(acct_criu, ns->acct.limits.criu); + /* policy/raw_data/ * file ops */ #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY @@ -2194,9 +2235,26 @@ void __aafs_ns_rmdir(struct aa_ns *ns) } /* assumes cleanup in caller */ +/* per-namespace resource accounting / cap introspection files (read-only) */ +static const struct aa_ns_acct_file { + const char *name; + const struct file_operations *fops; + enum aafs_ns_type slot; +} aa_ns_acct_files[] = { + { ".count", &seq_ns_acct_count_fops, AAFS_NS_COUNT }, + { ".max_count", &seq_ns_acct_max_count_fops, AAFS_NS_MAX_COUNT }, + { ".size", &seq_ns_acct_size_fops, AAFS_NS_SIZE }, + { ".max_size", &seq_ns_acct_max_size_fops, AAFS_NS_MAX_SIZE }, + { ".max_profile", &seq_ns_acct_max_profile_fops, AAFS_NS_MAX_PROFILE }, + { ".namespaces", &seq_ns_acct_namespaces_fops, AAFS_NS_NAMESPACES }, + { ".depth", &seq_ns_acct_depth_fops, AAFS_NS_DEPTH }, + { ".criu", &seq_ns_acct_criu_fops, AAFS_NS_CRIU }, +}; + static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) { struct dentry *dent; + int i; AA_BUG(!ns); AA_BUG(!dir); @@ -2239,6 +2297,16 @@ static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) return PTR_ERR(dent); ns_subremove(ns) = dent; + /* per-namespace resource accounting and cap introspection files */ + for (i = 0; i < ARRAY_SIZE(aa_ns_acct_files); i++) { + dent = aafs_create_file(aa_ns_acct_files[i].name, 0444, dir, + &ns->unconfined->label.count, + aa_ns_acct_files[i].fops); + if (IS_ERR(dent)) + return PTR_ERR(dent); + ns->dents[aa_ns_acct_files[i].slot] = dent; + } + /* use create_dentry so we can supply private data */ dent = aafs_create("namespaces", S_IFDIR | 0755, dir, &ns->unconfined->label.count, @@ -2597,6 +2665,17 @@ static struct aa_sfs_entry aa_sfs_entry_versions[] = { }; #define PERMS32STR "allow deny subtree cond kill complain prompt audit quiet hide xindex tag label" +static struct aa_sfs_entry aa_sfs_entry_ns_quota[] = { + /* + * Space-separated list of the policyns constructs this kernel + * enforces, so userspace emits a construct only when it is present. + * Each follow-up that starts enforcing a construct appends its token + * (subtree, criu, load_rate, descendants, root, name, mediation). + */ + AA_SFS_FILE_STRING("mask", "self children percent local"), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_DIR("versions", aa_sfs_entry_versions), AA_SFS_FILE_BOOLEAN("set_load", 1), @@ -2609,6 +2688,8 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), AA_SFS_FILE_BOOLEAN("compressed_load", 1), AA_SFS_FILE_BOOLEAN("extended_policy_header", 1), + /* policy-namespace resource controls (quota) */ + AA_SFS_DIR("ns_quota", aa_sfs_entry_ns_quota), { } }; @@ -2921,6 +3002,20 @@ int __init aa_create_aafs(void) goto dent_error; ns_subrevision(root_ns) = dent; + /* + * Root ns accounting / cap files at the top level next to .load; + * child namespaces get theirs via __aafs_ns_mkdir_entries(). + */ + for (int i = 0; i < ARRAY_SIZE(aa_ns_acct_files); i++) { + dent = securityfs_create_file(aa_ns_acct_files[i].name, 0444, + aa_sfs_entry.dentry, + &root_ns->unconfined->label.count, + aa_ns_acct_files[i].fops); + if (IS_ERR(dent)) + goto dent_error; + root_ns->dents[aa_ns_acct_files[i].slot] = dent; + } + /* policy tree referenced by magic policy symlink */ mutex_lock_nested(&root_ns->lock, root_ns->level); error = __aafs_ns_mkdir(root_ns, aafs_mnt->mnt_root, ".policy", diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index c6c8fcde728f..faf37b48df72 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -79,6 +79,10 @@ enum aafs_ns_type { AAFS_NS_SIZE, AAFS_NS_MAX_SIZE, AAFS_NS_OWNER, + AAFS_NS_MAX_PROFILE, + AAFS_NS_NAMESPACES, + AAFS_NS_DEPTH, + AAFS_NS_CRIU, AAFS_NS_SIZEOF, }; -- 2.51.0
