Add basic interface files to access namespace and profile information. The interface files are created when a profile is loaded and removed when the profile or namespace is removed.
Signed-off-by: John Johansen <[email protected]> --- security/apparmor/apparmorfs.c | 281 ++++++++++++++++++++++++++++++-- security/apparmor/audit.c | 6 + security/apparmor/include/apparmorfs.h | 24 +++ security/apparmor/include/audit.h | 2 + security/apparmor/include/policy.h | 11 ++ security/apparmor/policy.c | 51 +++++- 6 files changed, 351 insertions(+), 24 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index b73e59b..7919735 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -12,6 +12,7 @@ * License. */ +#include <linux/ctype.h> #include <linux/security.h> #include <linux/vmalloc.h> #include <linux/module.h> @@ -28,6 +29,49 @@ #include "include/resource.h" /** + * aa_mangle_name - mangle a profile name to std profile layout form + * @name: profile name to mangle (NOT NULL) + * @target: buffer to store mangled name, same length as @name (MAYBE NULL) + * + * Returns: length of mangled name + */ +static int mangle_name(char *name, char *target) +{ + char *t = target; + + while (*name == '/' || *name == '.') + name++; + + if (target) { + for (; *name; name++) { + if (strchr("\"\'(){}[]", *name)) + continue; + else if (*name == '/') + *(t)++ = '.'; + else if (isspace(*name)) + *(t)++ = '_'; + else if (strchr("*?^$\\", *name)) + *(t)++ = 'X'; + else if (isgraph(*name)) + *(t)++ = *name; + } + + *t = 0; + } else { + int len = 0; + for (; *name; name++) { + if (strchr("\"\'(){}[]", *name)) + continue; + len++; + } + + return len; + } + + return t - target; +} + +/** * aa_simple_write_to_buffer - common routine for getting policy from user * @op: operation doing the user buffer copy * @userbuf: user buffer to copy data from (NOT NULL) @@ -182,8 +226,206 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; -/** Base file system setup **/ +static int aa_fs_seq_string_show(struct seq_file *seq, void *v) +{ + char *string = seq->private; + + if (string) + seq_printf(seq, "%s\n", string); + + return 0; +} + +static int aa_fs_seq_string_open(struct inode *inode, struct file *file) +{ + return single_open(file, aa_fs_seq_string_show, inode->i_private); +} + +const struct file_operations aa_fs_seq_string_fops = { + .owner = THIS_MODULE, + .open = aa_fs_seq_string_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int aa_fs_seq_mode_show(struct seq_file *seq, void *v) +{ + enum profile_mode *mode = seq->private; + + seq_printf(seq, "%s\n", aa_profile_mode_names[*mode]); + + return 0; +} + +static int aa_fs_seq_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, aa_fs_seq_mode_show, inode->i_private); +} + +const struct file_operations aa_fs_seq_mode_fops = { + .owner = THIS_MODULE, + .open = aa_fs_seq_mode_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** fns to setup dynamic per profile/namespace files **/ +void __aa_fs_profile_rmdir(struct aa_profile *profile) +{ + struct aa_profile *child; + int i; + + if (!profile) + return; + + list_for_each_entry(child, &profile->base.profiles, base.list) + __aa_fs_profile_rmdir(child); + + for (i = AAFS_PROF_SIZE - 1; i >= 0; --i) { + securityfs_remove(profile->dents[i]); + profile->dents[i] = NULL; + } +} + +/* requires lock be held */ +int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) +{ + struct aa_profile *child; + struct dentry *dent = NULL, *dir; + int error; + + if (!parent) { + dent = profile->parent->dents[AAFS_PROF_DIR]; + /* adding to parent that previously didn't have children */ + dent = securityfs_create_dir("profiles", dent); + if (!dent) + goto fail; + profile->parent->dents[AAFS_PROF_PROFS] = parent = dent; + } + + if (!profile->dirname) { + int len, id_len; + len = mangle_name(profile->base.name, NULL); + id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id); + + profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL); + if (!profile->dirname) + goto fail; + mangle_name(profile->base.name, profile->dirname); + sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++); + } + + dent = securityfs_create_dir(profile->dirname, parent); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_DIR] = dir = dent; + + dent = securityfs_create_file("name", S_IFREG | 0444, dir, + profile->base.name, + &aa_fs_seq_string_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_NAME] = dent; + + dent = securityfs_create_file("mode", S_IFREG | 0444, dir, + &profile->mode, &aa_fs_seq_mode_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_MODE] = dent; + + list_for_each_entry(child, &profile->base.profiles, base.list) { + error = __aa_fs_profile_mkdir(child, profile->dents[AAFS_PROF_PROFS]); + if (error) + goto fail2; + } + + return 0; + +fail: + error = PTR_ERR(dent); + +fail2: + __aa_fs_profile_rmdir(profile); + + return error; +} + +void __aa_fs_namespace_rmdir(struct aa_namespace *ns) +{ + struct aa_namespace *sub; + struct aa_profile *child; + int i; + + if (!ns) + return; + + list_for_each_entry(child, &ns->base.profiles, base.list) + __aa_fs_profile_rmdir(child); + + list_for_each_entry(sub, &ns->sub_ns, base.list) + __aa_fs_namespace_rmdir(sub); + + for (i = AAFS_NS_SIZE - 1; i >= 0 ; --i) { + securityfs_remove(ns->dents[i]); + ns->dents[i] = NULL; + } +} + +int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, + const char *name) +{ + struct aa_namespace *sub; + struct aa_profile *child; + struct dentry *dent, *dir; + int error; + + if (!name) + name = ns->base.name; + + dent = securityfs_create_dir(name, parent); + if (IS_ERR(dent)) + goto fail; + ns->dents[AAFS_NS_DIR] = dir = dent; + + dent = securityfs_create_dir("profiles", dir); + if (IS_ERR(dent)) + goto fail; + ns->dents[AAFS_NS_PROFS] = dent; + + dent = securityfs_create_dir("namespaces", dir); + if (IS_ERR(dent)) + goto fail; + ns->dents[AAFS_NS_NS] = dent; + + list_for_each_entry(child, &ns->base.profiles, base.list) { + error = __aa_fs_profile_mkdir(child, ns->dents[AAFS_NS_PROFS]); + if (error) + goto fail2; + } + + list_for_each_entry(sub, &ns->sub_ns, base.list) { + error = __aa_fs_namespace_mkdir(sub, ns->dents[AAFS_NS_NS], + NULL); + if (error) + goto fail2; + } + + return 0; + +fail: + error = PTR_ERR(dent); + +fail2: + __aa_fs_namespace_rmdir(ns); + + return error; +} + + +/** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \ "link lock"), @@ -221,8 +463,10 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { { } }; -static struct aa_fs_entry aa_fs_entry = - AA_FS_DIR("apparmor", aa_fs_entry_apparmor); +static struct aa_fs_entry aa_fs_entry[] = { + AA_FS_DIR("apparmor", aa_fs_entry_apparmor), + { } +}; /** * aafs_create_file - create a file entry in the apparmor securityfs @@ -247,6 +491,7 @@ static int __init aafs_create_file(struct aa_fs_entry *fs_file, return error; } +static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir); /** * aafs_create_dir - recursively create a directory entry in the securityfs * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL) @@ -257,17 +502,16 @@ static int __init aafs_create_file(struct aa_fs_entry *fs_file, static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, struct dentry *parent) { - int error; struct aa_fs_entry *fs_file; + struct dentry *dir; + int error; - fs_dir->dentry = securityfs_create_dir(fs_dir->name, parent); - if (IS_ERR(fs_dir->dentry)) { - error = PTR_ERR(fs_dir->dentry); - fs_dir->dentry = NULL; - goto failed; - } + dir = securityfs_create_dir(fs_dir->name, parent); + if (IS_ERR(dir)) + return PTR_ERR(dir); + fs_dir->dentry = dir; - for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) { + for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { if (fs_file->v_type == AA_FS_TYPE_DIR) error = aafs_create_dir(fs_file, fs_dir->dentry); else @@ -279,6 +523,8 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, return 0; failed: + aafs_remove_dir(fs_dir); + return error; } @@ -303,7 +549,7 @@ static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) { struct aa_fs_entry *fs_file; - for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) { + for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { if (fs_file->v_type == AA_FS_TYPE_DIR) aafs_remove_dir(fs_file); else @@ -320,7 +566,7 @@ static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) */ void __init aa_destroy_aafs(void) { - aafs_remove_dir(&aa_fs_entry); + aafs_remove_dir(aa_fs_entry); } /** @@ -337,13 +583,18 @@ static int __init aa_create_aafs(void) if (!apparmor_initialized) return 0; - if (aa_fs_entry.dentry) { + if (aa_fs_entry[0].dentry) { AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); return -EEXIST; } /* Populate fs tree. */ - error = aafs_create_dir(&aa_fs_entry, NULL); + error = aafs_create_dir(aa_fs_entry, NULL); + if (error) + goto error; + + error = __aa_fs_namespace_mkdir(root_ns, aa_fs_entry[0].dentry, + "policy"); if (error) goto error; diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 031d2d9..3f221c7 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -73,6 +73,12 @@ const char *const op_table[] = { "profile_remove" }; +const char *const aa_profile_mode_names[] = { + "enforce", + "complain", + "kill" +}; + const char *const audit_mode_names[] = { "normal", "quiet_denied", diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 7ea4769..1ab7efd 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -61,4 +61,28 @@ extern const struct file_operations aa_fs_seq_file_ops; extern void __init aa_destroy_aafs(void); +struct aa_profile; +struct aa_namespace; + +enum aafs_ns_type { + AAFS_NS_DIR, + AAFS_NS_PROFS, + AAFS_NS_NS, + AAFS_NS_SIZE, +}; + +enum aafs_prof_type { + AAFS_PROF_DIR, + AAFS_PROF_PROFS, + AAFS_PROF_NAME, + AAFS_PROF_MODE, + AAFS_PROF_SIZE, +}; + +void __aa_fs_profile_rmdir(struct aa_profile *profile); +int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); +void __aa_fs_namespace_rmdir(struct aa_namespace *ns); +int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, + const char *name); + #endif /* __AA_APPARMORFS_H */ diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 69d8cae..6539ab3 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -28,6 +28,8 @@ struct aa_profile; extern const char *const audit_mode_names[]; #define AUDIT_MAX_INDEX 5 +extern const char *const aa_profile_mode_names[]; + enum audit_mode { AUDIT_NORMAL, /* follow normal auditing of accesses */ AUDIT_QUIET_DENIED, /* quiet all denied access messages */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 0d16d91..07fc7b0 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -108,6 +108,8 @@ struct aa_ns_acct { * @acct: accounting for the namespace * @unconfined: special unconfined profile for the namespace * @sub_ns: list of namespaces under the current namespace. + * @uniq_id: a unique id count for the profiles in the namespace + * @dents: dentries for the namespaces file entries in apparmorfs * * An aa_namespace defines the set profiles that are searched to determine * which profile to attach to a task. Profiles can not be shared between @@ -132,6 +134,9 @@ struct aa_namespace { struct list_head sub_ns; atomic_t uniq_null; + long uniq_id; + + struct dentry *dents[AAFS_NS_SIZE]; }; /* struct aa_policydb - match engine for a policy @@ -163,6 +168,9 @@ struct aa_policydb { * @caps: capabilities for the profile * @rlimits: rlimits for the profile * + * @dents: dentries for the profiles file entries in apparmorfs + * @dirname: name of the profile dir in apparmorfs + * * The AppArmor profile contains the basic confinement data. Each profile * has a name, and exists in a namespace. The @name and @exec_match are * used to determine profile attachment against unconfined tasks. All other @@ -198,6 +206,9 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; struct aa_rlimit rlimits; + + char *dirname; + struct dentry *dents[AAFS_PROF_SIZE]; }; extern struct aa_namespace *root_ns; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4d3f8ba..4297f1d 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -388,7 +388,7 @@ struct aa_namespace *aa_find_namespace(struct aa_namespace *root, */ static struct aa_namespace *aa_prepare_namespace(const char *name) { - struct aa_namespace *ns, *root; + struct aa_namespace *ns, *new_ns = NULL, *root; root = aa_current_profile()->ns; @@ -405,11 +405,23 @@ static struct aa_namespace *aa_prepare_namespace(const char *name) /* released by caller */ ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); if (!ns) - ns = alloc_namespace(root->base.hname, name); + new_ns = ns = alloc_namespace(root->base.hname, name); if (ns) { /* add parent ref */ ns->parent = aa_get_namespace(root); + + if (new_ns && __aa_fs_namespace_mkdir(new_ns, + root->dents[AAFS_NS_PROFS], + NULL)) { + free_namespace(new_ns); + AA_ERROR("Added namespace %s but failed to " + "add interface files.\n", + new_ns->base.name); + ns = NULL; + goto out; + } + list_add_rcu(&ns->base.list, &root->sub_ns); /* add list ref */ aa_get_namespace(ns); @@ -670,6 +682,7 @@ static void free_profile(struct aa_profile *profile) aa_free_cap_rules(&profile->caps); aa_free_rlimit_rules(&profile->rlimits); + kzfree(profile->dirname); aa_put_dfa(profile->xmatch); aa_put_dfa(profile->policy.dfa); @@ -1013,11 +1026,11 @@ bool aa_may_manage_policy(int op) * @ns - namespace the lookup occurs in * @new - profile to lookup who it is replacing * @noreplace - true if not replacing an existing profile - * @old - Returns: pointer to profile to replace (NO REFCOUNT) - * @rename - Returns: pointer to profile to rename (NO REFCOUNT) + * @old - Returns: pointer to profile to replace (ref counted) + * @rename - Returns: pointer to profile to rename (ref counted) * @info - Returns: info string on why lookup failed * - * Returns: policy (no ref) profile is in on success else ptr error + * Returns: policy (no ref count) profile is in on success else ptr error */ static struct aa_policy *__lookup_replace(struct aa_namespace *ns, struct aa_profile *new, @@ -1108,10 +1121,9 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) policy = __lookup_replace(ns, new, noreplace, NULL, NULL, &info); if (IS_ERR(policy)) { - mutex_unlock(&ns->lock); error = PTR_ERR(policy); name = new->base.hname; - goto fail; + goto fail_lock; } /* released when @new is freed */ new->ns = aa_get_namespace(ns); @@ -1120,6 +1132,17 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) new->parent = aa_get_profile((struct aa_profile *) policy); } + list_for_each_entry(new, &lh, base.list) { + /* make new profiles available for introspection */ + error = __aa_fs_profile_mkdir(new, + new->parent ? new->parent->dents[AAFS_PROF_PROFS] : + new->ns->dents[AAFS_NS_PROFS]); + if (error) { + info = "failed to create "; + goto fail_lock; + } + } + /* do actual replacement */ list_for_each_entry_safe(new, tmp, &lh, base.list) { struct aa_profile *old, *rename; @@ -1132,10 +1155,14 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) audit_policy(op, GFP_ATOMIC, new->base.name, NULL, error); - if (rename) + if (rename) { __replace_profile(rename, new); - if (old) + __aa_fs_profile_rmdir(rename); + } + if (old) { __replace_profile(old, new); + __aa_fs_profile_rmdir(old); + } if (!(old || rename)) __list_add_profile(&policy->profiles, new); @@ -1143,6 +1170,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) aa_put_profile(old); aa_put_profile(new); } + mutex_unlock(&ns->lock); out: @@ -1152,6 +1180,9 @@ out: return error; return size; +fail_lock: + mutex_unlock(&ns->lock); + fail: error = audit_policy(op, GFP_KERNEL, name, info, error); @@ -1211,6 +1242,7 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) /* remove namespace - can only happen if fqname[0] == ':' */ mutex_lock(&ns->parent->lock); __remove_namespace(ns); + __aa_fs_namespace_rmdir(ns); mutex_unlock(&ns->parent->lock); } else { /* remove profile */ @@ -1223,6 +1255,7 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) } name = profile->base.hname; __remove_profile(profile); + __aa_fs_profile_rmdir(profile); mutex_unlock(&ns->lock); } -- 1.7.10.4 -- AppArmor mailing list [email protected] Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor
