diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-07-05 11:26:35 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-07-05 11:26:35 -0700 |
commit | e24dd9ee5399747b71c1d982a484fc7601795f31 (patch) | |
tree | 14fcec8728916092a9f6dbeb0f2b8d5c5a4e5c9a /security/apparmor | |
parent | 7391786a64dcfe9c609a1f8e2204c1abf42ded23 (diff) | |
parent | c4758fa59285fe4dbfeab4364a6957936d040fbf (diff) | |
download | linux-e24dd9ee5399747b71c1d982a484fc7601795f31.tar.bz2 |
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security
Pull security layer updates from James Morris:
- a major update for AppArmor. From JJ:
* several bug fixes and cleanups
* the patch to add symlink support to securityfs that was floated
on the list earlier and the apparmorfs changes that make use of
securityfs symlinks
* it introduces the domain labeling base code that Ubuntu has been
carrying for several years, with several cleanups applied. And it
converts the current mediation over to using the domain labeling
base, which brings domain stacking support with it. This finally
will bring the base upstream code in line with Ubuntu and provide
a base to upstream the new feature work that Ubuntu carries.
* This does _not_ contain any of the newer apparmor mediation
features/controls (mount, signals, network, keys, ...) that
Ubuntu is currently carrying, all of which will be RFC'd on top
of this.
- Notable also is the Infiniband work in SELinux, and the new file:map
permission. From Paul:
"While we're down to 21 patches for v4.13 (it was 31 for v4.12),
the diffstat jumps up tremendously with over 2k of line changes.
Almost all of these changes are the SELinux/IB work done by
Daniel Jurgens; some other noteworthy changes include a NFS v4.2
labeling fix, a new file:map permission, and reporting of policy
capabilities on policy load"
There's also now genfscon labeling support for tracefs, which was
lost in v4.1 with the separation from debugfs.
- Smack incorporates a safer socket check in file_receive, and adds a
cap_capable call in privilege check.
- TPM as usual has a bunch of fixes and enhancements.
- Multiple calls to security_add_hooks() can now be made for the same
LSM, to allow LSMs to have hook declarations across multiple files.
- IMA now supports different "ima_appraise=" modes (eg. log, fix) from
the boot command line.
* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: (126 commits)
apparmor: put back designators in struct initialisers
seccomp: Switch from atomic_t to recount_t
seccomp: Adjust selftests to avoid double-join
seccomp: Clean up core dump logic
IMA: update IMA policy documentation to include pcr= option
ima: Log the same audit cause whenever a file has no signature
ima: Simplify policy_func_show.
integrity: Small code improvements
ima: fix get_binary_runtime_size()
ima: use ima_parse_buf() to parse template data
ima: use ima_parse_buf() to parse measurements headers
ima: introduce ima_parse_buf()
ima: Add cgroups2 to the defaults list
ima: use memdup_user_nul
ima: fix up #endif comments
IMA: Correct Kconfig dependencies for hash selection
ima: define is_ima_appraise_enabled()
ima: define Kconfig IMA_APPRAISE_BOOTPARAM option
ima: define a set of appraisal rules requiring file signatures
ima: extend the "ima_policy" boot command line to support multiple policies
...
Diffstat (limited to 'security/apparmor')
34 files changed, 7136 insertions, 1786 deletions
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index ad369a7aac24..a16b195274de 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o secid.o file.o policy_ns.o + resource.o secid.o file.o policy_ns.o label.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o clean-files := capability_names.h rlim_names.h @@ -20,7 +20,7 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ ;\ - echo -n '\#define AA_FS_CAPS_MASK "' >> $@ ;\ + printf '%s' '\#define AA_SFS_CAPS_MASK "' >> $@ ;\ sed $< -r -n -e '/CAP_FS_MASK/d' \ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ @@ -46,7 +46,7 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ # #define RLIMIT_FSIZE 1 /* Maximum filesize */ # #define RLIMIT_STACK 3 /* max stack size */ # to -# #define AA_FS_RLIMIT_MASK "fsize stack" +# #define AA_SFS_RLIMIT_MASK "fsize stack" quiet_cmd_make-rlim = GEN $@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ > $@ ;\ @@ -56,7 +56,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ ; \ - echo -n '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\ + printf '%s' '\#define AA_SFS_RLIMIT_MASK "' >> $@ ;\ sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 4f6ac9dbc65d..853c2ec8e0c9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -22,19 +22,52 @@ #include <linux/namei.h> #include <linux/capability.h> #include <linux/rcupdate.h> -#include <uapi/linux/major.h> #include <linux/fs.h> +#include <linux/poll.h> +#include <uapi/linux/major.h> +#include <uapi/linux/magic.h> #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" #include "include/context.h" #include "include/crypto.h" +#include "include/policy_ns.h" +#include "include/label.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/resource.h" #include "include/policy_unpack.h" +/* + * The apparmor filesystem interface used for policy load and introspection + * The interface is split into two main components based on their function + * a securityfs component: + * used for static files that are always available, and which allows + * userspace to specificy the location of the security filesystem. + * + * fns and data are prefixed with + * aa_sfs_ + * + * an apparmorfs component: + * used loaded policy content and introspection. It is not part of a + * regular mounted filesystem and is available only through the magic + * policy symlink in the root of the securityfs apparmor/ directory. + * Tasks queries will be magically redirected to the correct portion + * of the policy tree based on their confinement. + * + * fns and data are prefixed with + * aafs_ + * + * The aa_fs_ prefix is used to indicate the fn is used by both the + * securityfs and apparmorfs filesystems. + */ + + +/* + * support fns + */ + /** * aa_mangle_name - mangle a profile name to std profile layout form * @name: profile name to mangle (NOT NULL) @@ -74,6 +107,265 @@ static int mangle_name(const char *name, char *target) return t - target; } + +/* + * aafs - core fns and data for the policy tree + */ + +#define AAFS_NAME "apparmorfs" +static struct vfsmount *aafs_mnt; +static int aafs_count; + + +static int aafs_show_path(struct seq_file *seq, struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + + seq_printf(seq, "%s:[%lu]", AAFS_NAME, inode->i_ino); + return 0; +} + +static void aafs_evict_inode(struct inode *inode) +{ + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); + if (S_ISLNK(inode->i_mode)) + kfree(inode->i_link); +} + +static const struct super_operations aafs_super_ops = { + .statfs = simple_statfs, + .evict_inode = aafs_evict_inode, + .show_path = aafs_show_path, +}; + +static int fill_super(struct super_block *sb, void *data, int silent) +{ + static struct tree_descr files[] = { {""} }; + int error; + + error = simple_fill_super(sb, AAFS_MAGIC, files); + if (error) + return error; + sb->s_op = &aafs_super_ops; + + return 0; +} + +static struct dentry *aafs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_single(fs_type, flags, data, fill_super); +} + +static struct file_system_type aafs_ops = { + .owner = THIS_MODULE, + .name = AAFS_NAME, + .mount = aafs_mount, + .kill_sb = kill_anon_super, +}; + +/** + * __aafs_setup_d_inode - basic inode setup for apparmorfs + * @dir: parent directory for the dentry + * @dentry: dentry we are seting the inode up for + * @mode: permissions the file should have + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used + * @iops: struct of inode_operations that should be used + */ +static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry, + umode_t mode, void *data, char *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct inode *inode = new_inode(dir->i_sb); + + AA_BUG(!dir); + AA_BUG(!dentry); + + if (!inode) + return -ENOMEM; + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); + inode->i_private = data; + if (S_ISDIR(mode)) { + inode->i_op = iops ? iops : &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inc_nlink(inode); + inc_nlink(dir); + } else if (S_ISLNK(mode)) { + inode->i_op = iops ? iops : &simple_symlink_inode_operations; + inode->i_link = link; + } else { + inode->i_fop = fops; + } + d_instantiate(dentry, inode); + dget(dentry); + + return 0; +} + +/** + * aafs_create - create a dentry in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used for + * @iops: struct of inode_operations that should be used + * + * This is the basic "create a xxx" function for apparmorfs. + * + * Returns a pointer to a dentry if it succeeds, that must be free with + * aafs_remove(). Will return ERR_PTR on failure. + */ +static struct dentry *aafs_create(const char *name, umode_t mode, + struct dentry *parent, void *data, void *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct dentry *dentry; + struct inode *dir; + int error; + + AA_BUG(!name); + AA_BUG(!parent); + + if (!(mode & S_IFMT)) + mode = (mode & S_IALLUGO) | S_IFREG; + + error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); + if (error) + return ERR_PTR(error); + + dir = d_inode(parent); + + inode_lock(dir); + dentry = lookup_one_len(name, parent, strlen(name)); + if (IS_ERR(dentry)) + goto fail_lock; + + if (d_really_is_positive(dentry)) { + error = -EEXIST; + goto fail_dentry; + } + + error = __aafs_setup_d_inode(dir, dentry, mode, data, link, fops, iops); + if (error) + goto fail_dentry; + inode_unlock(dir); + + return dentry; + +fail_dentry: + dput(dentry); + +fail_lock: + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); + + return ERR_PTR(error); +} + +/** + * aafs_create_file - create a file in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @fops: struct file_operations that should be used for + * + * see aafs_create + */ +static struct dentry *aafs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + return aafs_create(name, mode, parent, data, NULL, fops, NULL); +} + +/** + * aafs_create_dir - create a directory in the apparmorfs filesystem + * + * @name: name of dentry to create + * @parent: parent directory for this dentry + * + * see aafs_create + */ +static struct dentry *aafs_create_dir(const char *name, struct dentry *parent) +{ + return aafs_create(name, S_IFDIR | 0755, parent, NULL, NULL, NULL, + NULL); +} + +/** + * aafs_create_symlink - create a symlink in the apparmorfs filesystem + * @name: name of dentry to create + * @parent: parent directory for this dentry + * @target: if symlink, symlink target string + * @iops: struct of inode_operations that should be used + * + * If @target parameter is %NULL, then the @iops parameter needs to be + * setup to handle .readlink and .get_link inode_operations. + */ +static struct dentry *aafs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops) +{ + struct dentry *dent; + char *link = NULL; + + if (target) { + link = kstrdup(target, GFP_KERNEL); + if (!link) + return ERR_PTR(-ENOMEM); + } + dent = aafs_create(name, S_IFLNK | 0444, parent, NULL, link, NULL, + iops); + if (IS_ERR(dent)) + kfree(link); + + return dent; +} + +/** + * aafs_remove - removes a file or directory from the apparmorfs filesystem + * + * @dentry: dentry of the file/directory/symlink to removed. + */ +static void aafs_remove(struct dentry *dentry) +{ + struct inode *dir; + + if (!dentry || IS_ERR(dentry)) + return; + + dir = d_inode(dentry->d_parent); + inode_lock(dir); + if (simple_positive(dentry)) { + if (d_is_dir(dentry)) + simple_rmdir(dir, dentry); + else + simple_unlink(dir, dentry); + dput(dentry); + } + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); +} + + +/* + * aa_fs - policy load/replace/remove + */ + /** * aa_simple_write_to_buffer - common routine for getting policy from user * @userbuf: user buffer to copy data from (NOT NULL) @@ -98,14 +390,11 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return ERR_PTR(-ESPIPE); /* freed by caller to simple_write_to_buffer */ - data = kvmalloc(sizeof(*data) + alloc_size, GFP_KERNEL); - if (data == NULL) - return ERR_PTR(-ENOMEM); - kref_init(&data->count); - data->size = copy_size; - data->hash = NULL; - data->abi = 0; + data = aa_loaddata_alloc(alloc_size); + if (IS_ERR(data)) + return data; + data->size = copy_size; if (copy_from_user(data->data, userbuf, copy_size)) { kvfree(data); return ERR_PTR(-EFAULT); @@ -114,27 +403,29 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return data; } -static ssize_t policy_update(int binop, const char __user *buf, size_t size, +static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, loff_t *pos, struct aa_ns *ns) { - ssize_t error; struct aa_loaddata *data; - struct aa_profile *profile = aa_current_profile(); - const char *op = binop == PROF_ADD ? OP_PROF_LOAD : OP_PROF_REPL; + struct aa_label *label; + ssize_t error; + + label = begin_current_label_crit_section(); + /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(profile, ns, op); + error = aa_may_manage_policy(label, ns, mask); if (error) return error; data = aa_simple_write_to_buffer(buf, size, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - error = aa_replace_profiles(ns ? ns : profile->ns, profile, - binop, data); + error = aa_replace_profiles(ns, label, mask, data); aa_put_loaddata(data); } + end_current_label_crit_section(label); return error; } @@ -144,7 +435,7 @@ static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, loff_t *pos) { struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); - int error = policy_update(PROF_ADD, buf, size, pos, ns); + int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns); aa_put_ns(ns); @@ -161,8 +452,8 @@ static ssize_t profile_replace(struct file *f, const char __user *buf, size_t size, loff_t *pos) { struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); - int error = policy_update(PROF_REPLACE, buf, size, pos, ns); - + int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, + buf, size, pos, ns); aa_put_ns(ns); return error; @@ -178,15 +469,15 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, size_t size, loff_t *pos) { struct aa_loaddata *data; - struct aa_profile *profile; + struct aa_label *label; ssize_t error; struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); - profile = aa_current_profile(); + label = begin_current_label_crit_section(); /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(profile, ns, OP_PROF_RM); + error = aa_may_manage_policy(label, ns, AA_MAY_REMOVE_POLICY); if (error) goto out; @@ -199,11 +490,11 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, error = PTR_ERR(data); if (!IS_ERR(data)) { data->data[size] = 0; - error = aa_remove_profiles(ns ? ns : profile->ns, profile, - data->data, size); + error = aa_remove_profiles(ns, label, data->data, size); aa_put_loaddata(data); } out: + end_current_label_crit_section(label); aa_put_ns(ns); return error; } @@ -213,6 +504,136 @@ static const struct file_operations aa_fs_profile_remove = { .llseek = default_llseek, }; +struct aa_revision { + struct aa_ns *ns; + long last_read; +}; + +/* revision file hook fn for policy loads */ +static int ns_revision_release(struct inode *inode, struct file *file) +{ + struct aa_revision *rev = file->private_data; + + if (rev) { + aa_put_ns(rev->ns); + kfree(rev); + } + + return 0; +} + +static ssize_t ns_revision_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + struct aa_revision *rev = file->private_data; + char buffer[32]; + long last_read; + int avail; + + mutex_lock(&rev->ns->lock); + last_read = rev->last_read; + if (last_read == rev->ns->revision) { + mutex_unlock(&rev->ns->lock); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + if (wait_event_interruptible(rev->ns->wait, + last_read != + READ_ONCE(rev->ns->revision))) + return -ERESTARTSYS; + mutex_lock(&rev->ns->lock); + } + + avail = sprintf(buffer, "%ld\n", rev->ns->revision); + if (*ppos + size > avail) { + rev->last_read = rev->ns->revision; + *ppos = 0; + } + mutex_unlock(&rev->ns->lock); + + return simple_read_from_buffer(buf, size, ppos, buffer, avail); +} + +static int ns_revision_open(struct inode *inode, struct file *file) +{ + struct aa_revision *rev = kzalloc(sizeof(*rev), GFP_KERNEL); + + if (!rev) + return -ENOMEM; + + rev->ns = aa_get_ns(inode->i_private); + if (!rev->ns) + rev->ns = aa_get_current_ns(); + file->private_data = rev; + + return 0; +} + +static unsigned int ns_revision_poll(struct file *file, poll_table *pt) +{ + struct aa_revision *rev = file->private_data; + unsigned int mask = 0; + + if (rev) { + mutex_lock(&rev->ns->lock); + poll_wait(file, &rev->ns->wait, pt); + if (rev->last_read < rev->ns->revision) + mask |= POLLIN | POLLRDNORM; + mutex_unlock(&rev->ns->lock); + } + + return mask; +} + +void __aa_bump_ns_revision(struct aa_ns *ns) +{ + ns->revision++; + wake_up_interruptible(&ns->wait); +} + +static const struct file_operations aa_fs_ns_revision_fops = { + .owner = THIS_MODULE, + .open = ns_revision_open, + .poll = ns_revision_poll, + .read = ns_revision_read, + .llseek = generic_file_llseek, + .release = ns_revision_release, +}; + +static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, + const char *match_str, size_t match_len) +{ + struct aa_perms tmp; + struct aa_dfa *dfa; + unsigned int state = 0; + + if (profile_unconfined(profile)) + return; + if (profile->file.dfa && *match_str == AA_CLASS_FILE) { + dfa = profile->file.dfa; + state = aa_dfa_match_len(dfa, profile->file.start, + match_str + 1, match_len - 1); + tmp = nullperms; + if (state) { + struct path_cond cond = { }; + + tmp = aa_compute_fperms(dfa, state, &cond); + } + } else if (profile->policy.dfa) { + if (!PROFILE_MEDIATES_SAFE(profile, *match_str)) + return; /* no change to current perms */ + dfa = profile->policy.dfa; + state = aa_dfa_match_len(dfa, profile->policy.start[0], + match_str, match_len); + if (state) + aa_compute_perms(dfa, state, &tmp); + else + tmp = nullperms; + } + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum_raw(perms, &tmp); +} + + /** * query_data - queries a policy and writes its data to buf * @buf: the resulting data is stored here (NOT NULL) @@ -236,6 +657,8 @@ static ssize_t query_data(char *buf, size_t buf_len, { char *out; const char *key; + struct label_it i; + struct aa_label *label, *curr; struct aa_profile *profile; struct aa_data *data; u32 bytes, blocks; @@ -253,7 +676,11 @@ static ssize_t query_data(char *buf, size_t buf_len, if (buf_len < sizeof(bytes) + sizeof(blocks)) return -EINVAL; /* not enough space */ - profile = aa_current_profile(); + curr = begin_current_label_crit_section(); + label = aa_label_parse(curr, query, GFP_KERNEL, false, false); + end_current_label_crit_section(curr); + if (IS_ERR(label)) + return PTR_ERR(label); /* We are going to leave space for two numbers. The first is the total * number of bytes we are writing after the first number. This is so @@ -267,13 +694,19 @@ static ssize_t query_data(char *buf, size_t buf_len, out = buf + sizeof(bytes) + sizeof(blocks); blocks = 0; - if (profile->data) { + label_for_each_confined(i, label, profile) { + if (!profile->data) + continue; + data = rhashtable_lookup_fast(profile->data, &key, profile->data->p); if (data) { - if (out + sizeof(outle32) + data->size > buf + buf_len) + if (out + sizeof(outle32) + data->size > buf + + buf_len) { + aa_put_label(label); return -EINVAL; /* not enough space */ + } outle32 = __cpu_to_le32(data->size); memcpy(out, &outle32, sizeof(outle32)); out += sizeof(outle32); @@ -282,6 +715,7 @@ static ssize_t query_data(char *buf, size_t buf_len, blocks++; } } + aa_put_label(label); outle32 = __cpu_to_le32(out - buf - sizeof(bytes)); memcpy(buf, &outle32, sizeof(outle32)); @@ -291,6 +725,182 @@ static ssize_t query_data(char *buf, size_t buf_len, return out - buf; } +/** + * query_label - queries a label and writes permissions to buf + * @buf: the resulting permissions string is stored here (NOT NULL) + * @buf_len: size of buf + * @query: binary query string to match against the dfa + * @query_len: size of query + * @view_only: only compute for querier's view + * + * The buffers pointed to by buf and query may overlap. The query buffer is + * parsed before buf is written to. + * + * The query should look like "LABEL_NAME\0DFA_STRING" where LABEL_NAME is + * the name of the label, in the current namespace, that is to be queried and + * DFA_STRING is a binary string to match against the label(s)'s DFA. + * + * LABEL_NAME must be NUL terminated. DFA_STRING may contain NUL characters + * but must *not* be NUL terminated. + * + * Returns: number of characters written to buf or -errno on failure + */ +static ssize_t query_label(char *buf, size_t buf_len, + char *query, size_t query_len, bool view_only) +{ + struct aa_profile *profile; + struct aa_label *label, *curr; + char *label_name, *match_str; + size_t label_name_len, match_len; + struct aa_perms perms; + struct label_it i; + + if (!query_len) + return -EINVAL; + + label_name = query; + label_name_len = strnlen(query, query_len); + if (!label_name_len || label_name_len == query_len) + return -EINVAL; + + /** + * The extra byte is to account for the null byte between the + * profile name and dfa string. profile_name_len is greater + * than zero and less than query_len, so a byte can be safely + * added or subtracted. + */ + match_str = label_name + label_name_len + 1; + match_len = query_len - label_name_len - 1; + + curr = begin_current_label_crit_section(); + label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false); + end_current_label_crit_section(curr); + if (IS_ERR(label)) + return PTR_ERR(label); + + perms = allperms; + if (view_only) { + label_for_each_in_ns(i, labels_ns(label), label, profile) { + profile_query_cb(profile, &perms, match_str, match_len); + } + } else { + label_for_each(i, label, profile) { + profile_query_cb(profile, &perms, match_str, match_len); + } + } + aa_put_label(label); + + return scnprintf(buf, buf_len, + "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n", + perms.allow, perms.deny, perms.audit, perms.quiet); +} + +/* + * Transaction based IO. + * The file expects a write which triggers the transaction, and then + * possibly a read(s) which collects the result - which is stored in a + * file-local buffer. Once a new write is performed, a new set of results + * are stored in the file-local buffer. + */ +struct multi_transaction { + struct kref count; + ssize_t size; + char data[0]; +}; + +#define MULTI_TRANSACTION_LIMIT (PAGE_SIZE - sizeof(struct multi_transaction)) +/* TODO: replace with per file lock */ +static DEFINE_SPINLOCK(multi_transaction_lock); + +static void multi_transaction_kref(struct kref *kref) +{ + struct multi_transaction *t; + + t = container_of(kref, struct multi_transaction, count); + free_page((unsigned long) t); +} + +static struct multi_transaction * +get_multi_transaction(struct multi_transaction *t) +{ + if (t) + kref_get(&(t->count)); + + return t; +} + +static void put_multi_transaction(struct multi_transaction *t) +{ + if (t) + kref_put(&(t->count), multi_transaction_kref); +} + +/* does not increment @new's count */ +static void multi_transaction_set(struct file *file, + struct multi_transaction *new, size_t n) +{ + struct multi_transaction *old; + + AA_BUG(n > MULTI_TRANSACTION_LIMIT); + + new->size = n; + spin_lock(&multi_transaction_lock); + old = (struct multi_transaction *) file->private_data; + file->private_data = new; + spin_unlock(&multi_transaction_lock); + put_multi_transaction(old); +} + +static struct multi_transaction *multi_transaction_new(struct file *file, + const char __user *buf, + size_t size) +{ + struct multi_transaction *t; + + if (size > MULTI_TRANSACTION_LIMIT - 1) + return ERR_PTR(-EFBIG); + + t = (struct multi_transaction *)get_zeroed_page(GFP_KERNEL); + if (!t) + return ERR_PTR(-ENOMEM); + kref_init(&t->count); + if (copy_from_user(t->data, buf, size)) + return ERR_PTR(-EFAULT); + + return t; +} + +static ssize_t multi_transaction_read(struct file *file, char __user *buf, + size_t size, loff_t *pos) +{ + struct multi_transaction *t; + ssize_t ret; + + spin_lock(&multi_transaction_lock); + t = get_multi_transaction(file->private_data); + spin_unlock(&multi_transaction_lock); + if (!t) + return 0; + + ret = simple_read_from_buffer(buf, size, pos, t->data, t->size); + put_multi_transaction(t); + + return ret; +} + +static int multi_transaction_release(struct inode *inode, struct file *file) +{ + put_multi_transaction(file->private_data); + + return 0; +} + +#define QUERY_CMD_LABEL "label\0" +#define QUERY_CMD_LABEL_LEN 6 +#define QUERY_CMD_PROFILE "profile\0" +#define QUERY_CMD_PROFILE_LEN 8 +#define QUERY_CMD_LABELALL "labelall\0" +#define QUERY_CMD_LABELALL_LEN 9 #define QUERY_CMD_DATA "data\0" #define QUERY_CMD_DATA_LEN 5 @@ -318,54 +928,72 @@ static ssize_t query_data(char *buf, size_t buf_len, static ssize_t aa_write_access(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { - char *buf; + struct multi_transaction *t; ssize_t len; if (*ppos) return -ESPIPE; - buf = simple_transaction_get(file, ubuf, count); - if (IS_ERR(buf)) - return PTR_ERR(buf); - - if (count > QUERY_CMD_DATA_LEN && - !memcmp(buf, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { - len = query_data(buf, SIMPLE_TRANSACTION_LIMIT, - buf + QUERY_CMD_DATA_LEN, + t = multi_transaction_new(file, ubuf, count); + if (IS_ERR(t)) + return PTR_ERR(t); + + if (count > QUERY_CMD_PROFILE_LEN && + !memcmp(t->data, QUERY_CMD_PROFILE, QUERY_CMD_PROFILE_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_PROFILE_LEN, + count - QUERY_CMD_PROFILE_LEN, true); + } else if (count > QUERY_CMD_LABEL_LEN && + !memcmp(t->data, QUERY_CMD_LABEL, QUERY_CMD_LABEL_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_LABEL_LEN, + count - QUERY_CMD_LABEL_LEN, true); + } else if (count > QUERY_CMD_LABELALL_LEN && + !memcmp(t->data, QUERY_CMD_LABELALL, + QUERY_CMD_LABELALL_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_LABELALL_LEN, + count - QUERY_CMD_LABELALL_LEN, false); + } else if (count > QUERY_CMD_DATA_LEN && + !memcmp(t->data, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { + len = query_data(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_DATA_LEN, count - QUERY_CMD_DATA_LEN); } else len = -EINVAL; - if (len < 0) + if (len < 0) { + put_multi_transaction(t); return len; + } - simple_transaction_set(file, len); + multi_transaction_set(file, t, len); return count; } -static const struct file_operations aa_fs_access = { +static const struct file_operations aa_sfs_access = { .write = aa_write_access, - .read = simple_transaction_read, - .release = simple_transaction_release, + .read = multi_transaction_read, + .release = multi_transaction_release, .llseek = generic_file_llseek, }; -static int aa_fs_seq_show(struct seq_file *seq, void *v) +static int aa_sfs_seq_show(struct seq_file *seq, void *v) { - struct aa_fs_entry *fs_file = seq->private; + struct aa_sfs_entry *fs_file = seq->private; if (!fs_file) return 0; switch (fs_file->v_type) { - case AA_FS_TYPE_BOOLEAN: + case AA_SFS_TYPE_BOOLEAN: seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); break; - case AA_FS_TYPE_STRING: + case AA_SFS_TYPE_STRING: seq_printf(seq, "%s\n", fs_file->v.string); break; - case AA_FS_TYPE_U64: + case AA_SFS_TYPE_U64: seq_printf(seq, "%#08lx\n", fs_file->v.u64); break; default: @@ -376,21 +1004,40 @@ static int aa_fs_seq_show(struct seq_file *seq, void *v) return 0; } -static int aa_fs_seq_open(struct inode *inode, struct file *file) +static int aa_sfs_seq_open(struct inode *inode, struct file *file) { - return single_open(file, aa_fs_seq_show, inode->i_private); + return single_open(file, aa_sfs_seq_show, inode->i_private); } -const struct file_operations aa_fs_seq_file_ops = { +const struct file_operations aa_sfs_seq_file_ops = { .owner = THIS_MODULE, - .open = aa_fs_seq_open, + .open = aa_sfs_seq_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; -static int aa_fs_seq_profile_open(struct inode *inode, struct file *file, - int (*show)(struct seq_file *, void *)) +/* + * profile based file operations + * policy/profiles/XXXX/profiles/ * + */ + +#define SEQ_PROFILE_FOPS(NAME) \ +static int seq_profile_ ##NAME ##_open(struct inode *inode, struct file *file)\ +{ \ + return seq_profile_open(inode, file, seq_profile_ ##NAME ##_show); \ +} \ + \ +static const struct file_operations seq_profile_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_profile_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = seq_profile_release, \ +} \ + +static int seq_profile_open(struct inode *inode, struct file *file, + int (*show)(struct seq_file *, void *)) { struct aa_proxy *proxy = aa_get_proxy(inode->i_private); int error = single_open(file, show, proxy); @@ -403,7 +1050,7 @@ static int aa_fs_seq_profile_open(struct inode *inode, struct file *file, return error; } -static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) +static int seq_profile_release(struct inode *inode, struct file *file) { struct seq_file *seq = (struct seq_file *) file->private_data; if (seq) @@ -411,217 +1058,229 @@ static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) return single_release(inode, file); } -static int aa_fs_seq_profname_show(struct seq_file *seq, void *v) +static int seq_profile_name_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_label *label = aa_get_label_rcu(&proxy->label); + struct aa_profile *profile = labels_profile(label); seq_printf(seq, "%s\n", profile->base.name); - aa_put_profile(profile); + aa_put_label(label); return 0; } -static int aa_fs_seq_profname_open(struct inode *inode, struct file *file) -{ - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profname_show); -} - -static const struct file_operations aa_fs_profname_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_profname_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; - -static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v) +static int seq_profile_mode_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_label *label = aa_get_label_rcu(&proxy->label); + struct aa_profile *profile = labels_profile(label); seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]); - aa_put_profile(profile); + aa_put_label(label); return 0; } -static int aa_fs_seq_profmode_open(struct inode *inode, struct file *file) -{ - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profmode_show); -} - -static const struct file_operations aa_fs_profmode_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_profmode_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; - -static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v) +static int seq_profile_attach_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_label *label = aa_get_label_rcu(&proxy->label); + struct aa_profile *profile = labels_profile(label); if (profile->attach) seq_printf(seq, "%s\n", profile->attach); else if (profile->xmatch) seq_puts(seq, "<unknown>\n"); else seq_printf(seq, "%s\n", profile->base.name); - aa_put_profile(profile); + aa_put_label(label); return 0; } -static int aa_fs_seq_profattach_open(struct inode *inode, struct file *file) -{ - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profattach_show); -} - -static const struct file_operations aa_fs_profattach_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_profattach_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; - -static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) +static int seq_profile_hash_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_label *label = aa_get_label_rcu(&proxy->label); + struct aa_profile *profile = labels_profile(label); unsigned int i, size = aa_hash_size(); if (profile->hash) { for (i = 0; i < size; i++) seq_printf(seq, "%.2x", profile->hash[i]); - seq_puts(seq, "\n"); + seq_putc(seq, '\n'); } - aa_put_profile(profile); + aa_put_label(label); return 0; } -static int aa_fs_seq_hash_open(struct inode *inode, struct file *file) +SEQ_PROFILE_FOPS(name); +SEQ_PROFILE_FOPS(mode); +SEQ_PROFILE_FOPS(attach); +SEQ_PROFILE_FOPS(hash); + +/* + * namespace based files + * several root files and + * policy/ * + */ + +#define SEQ_NS_FOPS(NAME) \ +static int seq_ns_ ##NAME ##_open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, seq_ns_ ##NAME ##_show, inode->i_private); \ +} \ + \ +static const struct file_operations seq_ns_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_ns_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} \ + +static int seq_ns_stacked_show(struct seq_file *seq, void *v) { - return single_open(file, aa_fs_seq_hash_show, inode->i_private); -} + struct aa_label *label; -static const struct file_operations aa_fs_seq_hash_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_hash_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; + label = begin_current_label_crit_section(); + seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no"); + end_current_label_crit_section(label); + return 0; +} -static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v) +static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) { - struct aa_ns *ns = aa_current_profile()->ns; + struct aa_label *label; + struct aa_profile *profile; + struct label_it it; + int count = 1; - seq_printf(seq, "%d\n", ns->level); + label = begin_current_label_crit_section(); + + if (label->size > 1) { + label_for_each(it, label, profile) + if (profile->ns != labels_ns(label)) { + count++; + break; + } + } + + seq_printf(seq, "%s\n", count > 1 ? "yes" : "no"); + end_current_label_crit_section(label); return 0; } -static int aa_fs_seq_open_ns_level(struct inode *inode, struct file *file) +static int seq_ns_level_show(struct seq_file *seq, void *v) { - return single_open(file, aa_fs_seq_show_ns_level, inode->i_private); -} + struct aa_label *label; -static const struct file_operations aa_fs_ns_level = { - .owner = THIS_MODULE, - .open = aa_fs_seq_open_ns_level, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; + label = begin_current_label_crit_section(); + seq_printf(seq, "%d\n", labels_ns(label)->level); + end_current_label_crit_section(label); + + return 0; +} -static int aa_fs_seq_show_ns_name(struct seq_file *seq, void *v) +static int seq_ns_name_show(struct seq_file *seq, void *v) { - struct aa_ns *ns = aa_current_profile()->ns; + struct aa_label *label = begin_current_label_crit_section(); - seq_printf(seq, "%s\n", ns->base.name); + seq_printf(seq, "%s\n", aa_ns_name(labels_ns(label), + labels_ns(label), true)); + end_current_label_crit_section(label); return 0; } -static int aa_fs_seq_open_ns_name(struct inode *inode, struct file *file) +SEQ_NS_FOPS(stacked); +SEQ_NS_FOPS(nsstacked); +SEQ_NS_FOPS(level); +SEQ_NS_FOPS(name); + + +/* policy/raw_data/ * file ops */ + +#define SEQ_RAWDATA_FOPS(NAME) \ +static int seq_rawdata_ ##NAME ##_open(struct inode *inode, struct file *file)\ +{ \ + return seq_rawdata_open(inode, file, seq_rawdata_ ##NAME ##_show); \ +} \ + \ +static const struct file_operations seq_rawdata_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_rawdata_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = seq_rawdata_release, \ +} \ + +static int seq_rawdata_open(struct inode *inode, struct file *file, + int (*show)(struct seq_file *, void *)) { - return single_open(file, aa_fs_seq_show_ns_name, inode->i_private); -} + struct aa_loaddata *data = __aa_get_loaddata(inode->i_private); + int error; -static const struct file_operations aa_fs_ns_name = { - .owner = THIS_MODULE, - .open = aa_fs_seq_open_ns_name, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; + if (!data) + /* lost race this ent is being reaped */ + return -ENOENT; -static int rawdata_release(struct inode *inode, struct file *file) + error = single_open(file, show, data); + if (error) { + AA_BUG(file->private_data && + ((struct seq_file *)file->private_data)->private); + aa_put_loaddata(data); + } + + return error; +} + +static int seq_rawdata_release(struct inode *inode, struct file *file) { - /* TODO: switch to loaddata when profile switched to symlink */ - aa_put_loaddata(file->private_data); + struct seq_file *seq = (struct seq_file *) file->private_data; - return 0; + if (seq) + aa_put_loaddata(seq->private); + + return single_release(inode, file); } -static int aa_fs_seq_raw_abi_show(struct seq_file *seq, void *v) +static int seq_rawdata_abi_show(struct seq_file *seq, void *v) { - struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_loaddata *data = seq->private; - if (profile->rawdata->abi) { - seq_printf(seq, "v%d", profile->rawdata->abi); - seq_puts(seq, "\n"); - } - aa_put_profile(profile); + seq_printf(seq, "v%d\n", data->abi); return 0; } -static int aa_fs_seq_raw_abi_open(struct inode *inode, struct file *file) +static int seq_rawdata_revision_show(struct seq_file *seq, void *v) { - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_abi_show); -} + struct aa_loaddata *data = seq->private; -static const struct file_operations aa_fs_seq_raw_abi_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_raw_abi_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; + seq_printf(seq, "%ld\n", data->revision); + + return 0; +} -static int aa_fs_seq_raw_hash_show(struct seq_file *seq, void *v) +static int seq_rawdata_hash_show(struct seq_file *seq, void *v) { - struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_loaddata *data = seq->private; unsigned int i, size = aa_hash_size(); - if (profile->rawdata->hash) { + if (data->hash) { for (i = 0; i < size; i++) - seq_printf(seq, "%.2x", profile->rawdata->hash[i]); - seq_puts(seq, "\n"); + seq_printf(seq, "%.2x", data->hash[i]); + seq_putc(seq, '\n'); } - aa_put_profile(profile); return 0; } -static int aa_fs_seq_raw_hash_open(struct inode *inode, struct file *file) -{ - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_hash_show); -} - -static const struct file_operations aa_fs_seq_raw_hash_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_raw_hash_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; +SEQ_RAWDATA_FOPS(abi); +SEQ_RAWDATA_FOPS(revision); +SEQ_RAWDATA_FOPS(hash); static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) @@ -632,29 +1291,127 @@ static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, rawdata->size); } -static int rawdata_open(struct inode *inode, struct file *file) +static int rawdata_release(struct inode *inode, struct file *file) { - struct aa_proxy *proxy = inode->i_private; - struct aa_profile *profile; + aa_put_loaddata(file->private_data); + return 0; +} + +static int rawdata_open(struct inode *inode, struct file *file) +{ if (!policy_view_capable(NULL)) return -EACCES; - profile = aa_get_profile_rcu(&proxy->profile); - file->private_data = aa_get_loaddata(profile->rawdata); - aa_put_profile(profile); + file->private_data = __aa_get_loaddata(inode->i_private); + if (!file->private_data) + /* lost race: this entry is being reaped */ + return -ENOENT; return 0; } -static const struct file_operations aa_fs_rawdata_fops = { +static const struct file_operations rawdata_fops = { .open = rawdata_open, .read = rawdata_read, .llseek = generic_file_llseek, .release = rawdata_release, }; +static void remove_rawdata_dents(struct aa_loaddata *rawdata) +{ + int i; + + for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) { + if (!IS_ERR_OR_NULL(rawdata->dents[i])) { + /* no refcounts on i_private */ + aafs_remove(rawdata->dents[i]); + rawdata->dents[i] = NULL; + } + } +} + +void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata) +{ + AA_BUG(rawdata->ns && !mutex_is_locked(&rawdata->ns->lock)); + + if (rawdata->ns) { + remove_rawdata_dents(rawdata); + list_del_init(&rawdata->list); + aa_put_ns(rawdata->ns); + rawdata->ns = NULL; + } +} + +int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata) +{ + struct dentry *dent, *dir; + + AA_BUG(!ns); + AA_BUG(!rawdata); + AA_BUG(!mutex_is_locked(&ns->lock)); + AA_BUG(!ns_subdata_dir(ns)); + + /* + * just use ns revision dir was originally created at. This is + * under ns->lock and if load is successful revision will be + * bumped and is guaranteed to be unique + */ + rawdata->name = kasprintf(GFP_KERNEL, "%ld", ns->revision); + if (!rawdata->name) + return -ENOMEM; + + dir = aafs_create_dir(rawdata->name, ns_subdata_dir(ns)); + if (IS_ERR(dir)) + /* ->name freed when rawdata freed */ + return PTR_ERR(dir); + rawdata->dents[AAFS_LOADDATA_DIR] = dir; + + dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata, + &seq_rawdata_abi_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_ABI] = dent; + + dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata, + &seq_rawdata_revision_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_REVISION] = dent; + + if (aa_g_hash_policy) { + dent = aafs_create_file("sha1", S_IFREG | 0444, dir, + rawdata, &seq_rawdata_hash_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_HASH] = dent; + } + + dent = aafs_create_file("raw_data", S_IFREG | 0444, + dir, rawdata, &rawdata_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_DATA] = dent; + d_inode(dent)->i_size = rawdata->size; + + rawdata->ns = aa_get_ns(ns); + list_add(&rawdata->list, &ns->rawdata_list); + /* no refcount on inode rawdata */ + + return 0; + +fail: + remove_rawdata_dents(rawdata); + + return PTR_ERR(dent); +} + /** fns to setup dynamic per profile/namespace files **/ -void __aa_fs_profile_rmdir(struct aa_profile *profile) + +/** + * + * Requires: @profile->ns->lock held + */ +void __aafs_profile_rmdir(struct aa_profile *profile) { struct aa_profile *child; int i; @@ -663,7 +1420,7 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile) return; list_for_each_entry(child, &profile->base.profiles, base.list) - __aa_fs_profile_rmdir(child); + __aafs_profile_rmdir(child); for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) { struct aa_proxy *proxy; @@ -671,14 +1428,18 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile) continue; proxy = d_inode(profile->dents[i])->i_private; - securityfs_remove(profile->dents[i]); + aafs_remove(profile->dents[i]); aa_put_proxy(proxy); profile->dents[i] = NULL; } } -void __aa_fs_profile_migrate_dents(struct aa_profile *old, - struct aa_profile *new) +/** + * + * Requires: @old->ns->lock held + */ +void __aafs_profile_migrate_dents(struct aa_profile *old, + struct aa_profile *new) { int i; @@ -694,18 +1455,52 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name, struct aa_profile *profile, const struct file_operations *fops) { - struct aa_proxy *proxy = aa_get_proxy(profile->proxy); + struct aa_proxy *proxy = aa_get_proxy(profile->label.proxy); struct dentry *dent; - dent = securityfs_create_file(name, S_IFREG | 0444, dir, proxy, fops); + dent = aafs_create_file(name, S_IFREG | 0444, dir, proxy, fops); if (IS_ERR(dent)) aa_put_proxy(proxy); return dent; } -/* requires lock be held */ -int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) +static int profile_depth(struct aa_profile *profile) +{ + int depth = 0; + + rcu_read_lock(); + for (depth = 0; profile; profile = rcu_access_pointer(profile->parent)) + depth++; + rcu_read_unlock(); + + return depth; +} + +static int gen_symlink_name(char *buffer, size_t bsize, int depth, + const char *dirname, const char *fname) +{ + int error; + + for (; depth > 0; depth--) { + if (bsize < 7) + return -ENAMETOOLONG; + strcpy(buffer, "../../"); + buffer += 6; + bsize -= 6; + } + + error = snprintf(buffer, bsize, "raw_data/%s/%s", dirname, fname); + if (error >= bsize || error < 0) + return -ENAMETOOLONG; + + return 0; +} + +/* + * Requires: @profile->ns->lock held + */ +int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) { struct aa_profile *child; struct dentry *dent = NULL, *dir; @@ -716,7 +1511,7 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) p = aa_deref_parent(profile); dent = prof_dir(p); /* adding to parent that previously didn't have children */ - dent = securityfs_create_dir("profiles", dent); + dent = aafs_create_dir("profiles", dent); if (IS_ERR(dent)) goto fail; prof_child_dir(p) = parent = dent; @@ -728,67 +1523,80 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id); profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL); - if (!profile->dirname) - goto fail; + if (!profile->dirname) { + error = -ENOMEM; + goto fail2; + } mangle_name(profile->base.name, profile->dirname); sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++); } - dent = securityfs_create_dir(profile->dirname, parent); + dent = aafs_create_dir(profile->dirname, parent); if (IS_ERR(dent)) goto fail; prof_dir(profile) = dir = dent; - dent = create_profile_file(dir, "name", profile, &aa_fs_profname_fops); + dent = create_profile_file(dir, "name", profile, + &seq_profile_name_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_NAME] = dent; - dent = create_profile_file(dir, "mode", profile, &aa_fs_profmode_fops); + dent = create_profile_file(dir, "mode", profile, + &seq_profile_mode_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_MODE] = dent; dent = create_profile_file(dir, "attach", profile, - &aa_fs_profattach_fops); + &seq_profile_attach_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_ATTACH] = dent; if (profile->hash) { dent = create_profile_file(dir, "sha1", profile, - &aa_fs_seq_hash_fops); + &seq_profile_hash_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_HASH] = dent; } if (profile->rawdata) { - dent = create_profile_file(dir, "raw_sha1", profile, - &aa_fs_seq_raw_hash_fops); + char target[64]; + int depth = profile_depth(profile); + + error = gen_symlink_name(target, sizeof(target), depth, + profile->rawdata->name, "sha1"); + if (error < 0) + goto fail2; + dent = aafs_create_symlink("raw_sha1", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_HASH] = dent; - dent = create_profile_file(dir, "raw_abi", profile, - &aa_fs_seq_raw_abi_fops); + error = gen_symlink_name(target, sizeof(target), depth, + profile->rawdata->name, "abi"); + if (error < 0) + goto fail2; + dent = aafs_create_symlink("raw_abi", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_ABI] = dent; - dent = securityfs_create_file("raw_data", S_IFREG | 0444, dir, - profile->proxy, - &aa_fs_rawdata_fops); + error = gen_symlink_name(target, sizeof(target), depth, + profile->rawdata->name, "raw_data"); + if (error < 0) + goto fail2; + dent = aafs_create_symlink("raw_data", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_DATA] = dent; - d_inode(dent)->i_size = profile->rawdata->size; - aa_get_proxy(profile->proxy); } list_for_each_entry(child, &profile->base.profiles, base.list) { - error = __aa_fs_profile_mkdir(child, prof_child_dir(profile)); + error = __aafs_profile_mkdir(child, prof_child_dir(profile)); if (error) goto fail2; } @@ -799,12 +1607,123 @@ fail: error = PTR_ERR(dent); fail2: - __aa_fs_profile_rmdir(profile); + __aafs_profile_rmdir(profile); return error; } -void __aa_fs_ns_rmdir(struct aa_ns *ns) +static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + struct aa_ns *ns, *parent; + /* TODO: improve permission check */ + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY); + end_current_label_crit_section(label); + if (error) + return error; + + parent = aa_get_ns(dir->i_private); + AA_BUG(d_inode(ns_subns_dir(parent)) != dir); + + /* we have to unlock and then relock to get locking order right + * for pin_fs + */ + inode_unlock(dir); + error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); + mutex_lock(&parent->lock); + inode_lock_nested(dir, I_MUTEX_PARENT); + if (error) + goto out; + + error = __aafs_setup_d_inode(dir, dentry, mode | S_IFDIR, NULL, + NULL, NULL, NULL); + if (error) + goto out_pin; + + ns = __aa_find_or_create_ns(parent, READ_ONCE(dentry->d_name.name), + dentry); + if (IS_ERR(ns)) { + error = PTR_ERR(ns); + ns = NULL; + } + + aa_put_ns(ns); /* list ref remains */ +out_pin: + if (error) + simple_release_fs(&aafs_mnt, &aafs_count); +out: + mutex_unlock(&parent->lock); + aa_put_ns(parent); + + return error; +} + +static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) +{ + struct aa_ns *ns, *parent; + /* TODO: improve permission check */ + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY); + end_current_label_crit_section(label); + if (error) + return error; + + parent = aa_get_ns(dir->i_private); + /* rmdir calls the generic securityfs functions to remove files + * from the apparmor dir. It is up to the apparmor ns locking + * to avoid races. + */ + inode_unlock(dir); + inode_unlock(dentry->d_inode); + + mutex_lock(&parent->lock); + ns = aa_get_ns(__aa_findn_ns(&parent->sub_ns, dentry->d_name.name, + dentry->d_name.len)); + if (!ns) { + error = -ENOENT; + goto out; + } + AA_BUG(ns_dir(ns) != dentry); + + __aa_remove_ns(ns); + aa_put_ns(ns); + +out: + mutex_unlock(&parent->lock); + inode_lock_nested(dir, I_MUTEX_PARENT); + inode_lock(dentry->d_inode); + aa_put_ns(parent); + + return error; +} + +static const struct inode_operations ns_dir_inode_operations = { + .lookup = simple_lookup, + .mkdir = ns_mkdir_op, + .rmdir = ns_rmdir_op, +}; + +static void __aa_fs_list_remove_rawdata(struct aa_ns *ns) +{ + struct aa_loaddata *ent, *tmp; + + AA_BUG(!mutex_is_locked(&ns->lock)); + + list_for_each_entry_safe(ent, tmp, &ns->rawdata_list, list) + __aa_fs_remove_rawdata(ent); +} + +/** + * + * Requires: @ns->lock held + */ +void __aafs_ns_rmdir(struct aa_ns *ns) { struct aa_ns *sub; struct aa_profile *child; @@ -814,14 +1733,16 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns) return; list_for_each_entry(child, &ns->base.profiles, base.list) - __aa_fs_profile_rmdir(child); + __aafs_profile_rmdir(child); list_for_each_entry(sub, &ns->sub_ns, base.list) { mutex_lock(&sub->lock); - __aa_fs_ns_rmdir(sub); + __aafs_ns_rmdir(sub); mutex_unlock(&sub->lock); } + __aa_fs_list_remove_rawdata(ns); + if (ns_subns_dir(ns)) { sub = d_inode(ns_subns_dir(ns))->i_private; aa_put_ns(sub); @@ -838,53 +1759,66 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns) sub = d_inode(ns_subremove(ns))->i_private; aa_put_ns(sub); } + if (ns_subrevision(ns)) { + sub = d_inode(ns_subrevision(ns))->i_private; + aa_put_ns(sub); + } for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { - securityfs_remove(ns->dents[i]); + aafs_remove(ns->dents[i]); ns->dents[i] = NULL; } } /* assumes cleanup in caller */ -static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) +static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) { struct dentry *dent; AA_BUG(!ns); AA_BUG(!dir); - dent = securityfs_create_dir("profiles", dir); + dent = aafs_create_dir("profiles", dir); if (IS_ERR(dent)) return PTR_ERR(dent); ns_subprofs_dir(ns) = dent; - dent = securityfs_create_dir("raw_data", dir); + dent = aafs_create_dir("raw_data", dir); if (IS_ERR(dent)) return PTR_ERR(dent); ns_subdata_dir(ns) = dent; - dent = securityfs_create_file(".load", 0640, dir, ns, + dent = aafs_create_file("revision", 0444, dir, ns, + &aa_fs_ns_revision_fops); + if (IS_ERR(dent)) + return PTR_ERR(dent); + aa_get_ns(ns); + ns_subrevision(ns) = dent; + + dent = aafs_create_file(".load", 0640, dir, ns, &aa_fs_profile_load); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); ns_subload(ns) = dent; - dent = securityfs_create_file(".replace", 0640, dir, ns, + dent = aafs_create_file(".replace", 0640, dir, ns, &aa_fs_profile_replace); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); ns_subreplace(ns) = dent; - dent = securityfs_create_file(".remove", 0640, dir, ns, + dent = aafs_create_file(".remove", 0640, dir, ns, &aa_fs_profile_remove); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); ns_subremove(ns) = dent; - dent = securityfs_create_dir("namespaces", dir); + /* use create_dentry so we can supply private data */ + dent = aafs_create("namespaces", S_IFDIR | 0755, dir, ns, NULL, NULL, + &ns_dir_inode_operations); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); @@ -893,11 +1827,15 @@ static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) return 0; } -int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) +/* + * Requires: @ns->lock held + */ +int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, + struct dentry *dent) { struct aa_ns *sub; struct aa_profile *child; - struct dentry *dent, *dir; + struct dentry *dir; int error; AA_BUG(!ns); @@ -907,19 +1845,21 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) if (!name) name = ns->base.name; - /* create ns dir if it doesn't already exist */ - dent = securityfs_create_dir(name, parent); - if (IS_ERR(dent)) - goto fail; - + if (!dent) { + /* create ns dir if it doesn't already exist */ + dent = aafs_create_dir(name, parent); + if (IS_ERR(dent)) + goto fail; + } else + dget(dent); ns_dir(ns) = dir = dent; - error = __aa_fs_ns_mkdir_entries(ns, dir); + error = __aafs_ns_mkdir_entries(ns, dir); if (error) goto fail2; /* profiles */ list_for_each_entry(child, &ns->base.profiles, base.list) { - error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns)); + error = __aafs_profile_mkdir(child, ns_subprofs_dir(ns)); if (error) goto fail2; } @@ -927,7 +1867,7 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) /* subnamespaces */ list_for_each_entry(sub, &ns->sub_ns, base.list) { mutex_lock(&sub->lock); - error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL); + error = __aafs_ns_mkdir(sub, ns_subns_dir(ns), NULL, NULL); mutex_unlock(&sub->lock); if (error) goto fail2; @@ -939,7 +1879,7 @@ fail: error = PTR_ERR(dent); fail2: - __aa_fs_ns_rmdir(ns); + __aafs_ns_rmdir(ns); return error; } @@ -1074,10 +2014,9 @@ static struct aa_profile *next_profile(struct aa_ns *root, static void *p_start(struct seq_file *f, loff_t *pos) { struct aa_profile *profile = NULL; - struct aa_ns *root = aa_current_profile()->ns; + struct aa_ns *root = aa_get_current_ns(); loff_t l = *pos; - f->private = aa_get_ns(root); - + f->private = root; /* find the first profile */ mutex_lock(&root->lock); @@ -1141,15 +2080,14 @@ static int seq_show_profile(struct seq_file *f, void *p) struct aa_profile *profile = (struct aa_profile *)p; struct aa_ns *root = f->private; - if (profile->ns != root) - seq_printf(f, ":%s://", aa_ns_name(root, profile->ns, true)); - seq_printf(f, "%s (%s)\n", profile->base.hname, - aa_profile_mode_names[profile->mode]); + aa_label_seq_xprint(f, root, &profile->label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS, GFP_KERNEL); + seq_putc(f, '\n'); return 0; } -static const struct seq_operations aa_fs_profiles_op = { +static const struct seq_operations aa_sfs_profiles_op = { .start = p_start, .next = p_next, .stop = p_stop, @@ -1161,7 +2099,7 @@ static int profiles_open(struct inode *inode, struct file *file) if (!policy_view_capable(NULL)) return -EACCES; - return seq_open(file, &aa_fs_profiles_op); + return seq_open(file, &aa_sfs_profiles_op); } static int profiles_release(struct inode *inode, struct file *file) @@ -1169,7 +2107,7 @@ static int profiles_release(struct inode *inode, struct file *file) return seq_release(inode, file); } -static const struct file_operations aa_fs_profiles_fops = { +static const struct file_operations aa_sfs_profiles_fops = { .open = profiles_open, .read = seq_read, .llseek = seq_lseek, @@ -1178,64 +2116,94 @@ static const struct file_operations aa_fs_profiles_fops = { /** 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"), +static struct aa_sfs_entry aa_sfs_entry_file[] = { + AA_SFS_FILE_STRING("mask", + "create read write exec append mmap_exec link lock"), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry_ptrace[] = { + AA_SFS_FILE_STRING("mask", "read trace"), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry_domain[] = { + AA_SFS_FILE_BOOLEAN("change_hat", 1), + AA_SFS_FILE_BOOLEAN("change_hatv", 1), + AA_SFS_FILE_BOOLEAN("change_onexec", 1), + AA_SFS_FILE_BOOLEAN("change_profile", 1), + AA_SFS_FILE_BOOLEAN("stack", 1), + AA_SFS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1), + AA_SFS_FILE_STRING("version", "1.2"), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry_versions[] = { + AA_SFS_FILE_BOOLEAN("v5", 1), + AA_SFS_FILE_BOOLEAN("v6", 1), + AA_SFS_FILE_BOOLEAN("v7", 1), { } }; -static struct aa_fs_entry aa_fs_entry_domain[] = { - AA_FS_FILE_BOOLEAN("change_hat", 1), - AA_FS_FILE_BOOLEAN("change_hatv", 1), - AA_FS_FILE_BOOLEAN("change_onexec", 1), - AA_FS_FILE_BOOLEAN("change_profile", 1), - AA_FS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1), - AA_FS_FILE_STRING("version", "1.2"), +static struct aa_sfs_entry aa_sfs_entry_policy[] = { + AA_SFS_DIR("versions", aa_sfs_entry_versions), + AA_SFS_FILE_BOOLEAN("set_load", 1), { } }; -static struct aa_fs_entry aa_fs_entry_versions[] = { - AA_FS_FILE_BOOLEAN("v5", 1), +static struct aa_sfs_entry aa_sfs_entry_ns[] = { + AA_SFS_FILE_BOOLEAN("profile", 1), + AA_SFS_FILE_BOOLEAN("pivot_root", 1), { } }; -static struct aa_fs_entry aa_fs_entry_policy[] = { - AA_FS_DIR("versions", aa_fs_entry_versions), - AA_FS_FILE_BOOLEAN("set_load", 1), +static struct aa_sfs_entry aa_sfs_entry_query_label[] = { + AA_SFS_FILE_STRING("perms", "allow deny audit quiet"), + AA_SFS_FILE_BOOLEAN("data", 1), + AA_SFS_FILE_BOOLEAN("multi_transaction", 1), { } }; -static struct aa_fs_entry aa_fs_entry_features[] = { - AA_FS_DIR("policy", aa_fs_entry_policy), - AA_FS_DIR("domain", aa_fs_entry_domain), - AA_FS_DIR("file", aa_fs_entry_file), - AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), - AA_FS_DIR("rlimit", aa_fs_entry_rlimit), - AA_FS_DIR("caps", aa_fs_entry_caps), +static struct aa_sfs_entry aa_sfs_entry_query[] = { + AA_SFS_DIR("label", aa_sfs_entry_query_label), + { } +}; +static struct aa_sfs_entry aa_sfs_entry_features[] = { + AA_SFS_DIR("policy", aa_sfs_entry_policy), + AA_SFS_DIR("domain", aa_sfs_entry_domain), + AA_SFS_DIR("file", aa_sfs_entry_file), + AA_SFS_DIR("namespaces", aa_sfs_entry_ns), + AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), + AA_SFS_DIR("rlimit", aa_sfs_entry_rlimit), + AA_SFS_DIR("caps", aa_sfs_entry_caps), + AA_SFS_DIR("ptrace", aa_sfs_entry_ptrace), + AA_SFS_DIR("query", aa_sfs_entry_query), { } }; -static struct aa_fs_entry aa_fs_entry_apparmor[] = { - AA_FS_FILE_FOPS(".access", 0640, &aa_fs_access), - AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level), - AA_FS_FILE_FOPS(".ns_name", 0640, &aa_fs_ns_name), - AA_FS_FILE_FOPS("profiles", 0440, &aa_fs_profiles_fops), - AA_FS_DIR("features", aa_fs_entry_features), +static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { + AA_SFS_FILE_FOPS(".access", 0640, &aa_sfs_access), + AA_SFS_FILE_FOPS(".stacked", 0444, &seq_ns_stacked_fops), + AA_SFS_FILE_FOPS(".ns_stacked", 0444, &seq_ns_nsstacked_fops), + AA_SFS_FILE_FOPS(".ns_level", 0666, &seq_ns_level_fops), + AA_SFS_FILE_FOPS(".ns_name", 0640, &seq_ns_name_fops), + AA_SFS_FILE_FOPS("profiles", 0440, &aa_sfs_profiles_fops), + AA_SFS_DIR("features", aa_sfs_entry_features), { } }; -static struct aa_fs_entry aa_fs_entry = - AA_FS_DIR("apparmor", aa_fs_entry_apparmor); +static struct aa_sfs_entry aa_sfs_entry = + AA_SFS_DIR("apparmor", aa_sfs_entry_apparmor); /** - * aafs_create_file - create a file entry in the apparmor securityfs - * @fs_file: aa_fs_entry to build an entry for (NOT NULL) + * entry_create_file - create a file entry in the apparmor securityfs + * @fs_file: aa_sfs_entry to build an entry for (NOT NULL) * @parent: the parent dentry in the securityfs * - * Use aafs_remove_file to remove entries created with this fn. + * Use entry_remove_file to remove entries created with this fn. */ -static int __init aafs_create_file(struct aa_fs_entry *fs_file, - struct dentry *parent) +static int __init entry_create_file(struct aa_sfs_entry *fs_file, + struct dentry *parent) { int error = 0; @@ -1250,18 +2218,18 @@ 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); +static void __init entry_remove_dir(struct aa_sfs_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) + * entry_create_dir - recursively create a directory entry in the securityfs + * @fs_dir: aa_sfs_entry (and all child entries) to build (NOT NULL) * @parent: the parent dentry in the securityfs * - * Use aafs_remove_dir to remove entries created with this fn. + * Use entry_remove_dir to remove entries created with this fn. */ -static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, - struct dentry *parent) +static int __init entry_create_dir(struct aa_sfs_entry *fs_dir, + struct dentry *parent) { - struct aa_fs_entry *fs_file; + struct aa_sfs_entry *fs_file; struct dentry *dir; int error; @@ -1271,10 +2239,10 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, fs_dir->dentry = dir; 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); + if (fs_file->v_type == AA_SFS_TYPE_DIR) + error = entry_create_dir(fs_file, fs_dir->dentry); else - error = aafs_create_file(fs_file, fs_dir->dentry); + error = entry_create_file(fs_file, fs_dir->dentry); if (error) goto failed; } @@ -1282,16 +2250,16 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, return 0; failed: - aafs_remove_dir(fs_dir); + entry_remove_dir(fs_dir); return error; } /** - * aafs_remove_file - drop a single file entry in the apparmor securityfs - * @fs_file: aa_fs_entry to detach from the securityfs (NOT NULL) + * entry_remove_file - drop a single file entry in the apparmor securityfs + * @fs_file: aa_sfs_entry to detach from the securityfs (NOT NULL) */ -static void __init aafs_remove_file(struct aa_fs_entry *fs_file) +static void __init entry_remove_file(struct aa_sfs_entry *fs_file) { if (!fs_file->dentry) return; @@ -1301,21 +2269,21 @@ static void __init aafs_remove_file(struct aa_fs_entry *fs_file) } /** - * aafs_remove_dir - recursively drop a directory entry from the securityfs - * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL) + * entry_remove_dir - recursively drop a directory entry from the securityfs + * @fs_dir: aa_sfs_entry (and all child entries) to detach (NOT NULL) */ -static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) +static void __init entry_remove_dir(struct aa_sfs_entry *fs_dir) { - struct aa_fs_entry *fs_file; + struct aa_sfs_entry *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); + if (fs_file->v_type == AA_SFS_TYPE_DIR) + entry_remove_dir(fs_file); else - aafs_remove_file(fs_file); + entry_remove_file(fs_file); } - aafs_remove_file(fs_dir); + entry_remove_file(fs_dir); } /** @@ -1325,7 +2293,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); + entry_remove_dir(&aa_sfs_entry); } @@ -1374,6 +2342,59 @@ out: return error; } + + +static const char *policy_get_link(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + struct aa_ns *ns; + struct path path; + + if (!dentry) + return ERR_PTR(-ECHILD); + ns = aa_get_current_ns(); + path.mnt = mntget(aafs_mnt); + path.dentry = dget(ns_dir(ns)); + nd_jump_link(&path); + aa_put_ns(ns); + + return NULL; +} + +static int ns_get_name(char *buf, size_t size, struct aa_ns *ns, + struct inode *inode) +{ + int res = snprintf(buf, size, "%s:[%lu]", AAFS_NAME, inode->i_ino); + + if (res < 0 || res >= size) + res = -ENOENT; + + return res; +} + +static int policy_readlink(struct dentry *dentry, char __user *buffer, + int buflen) +{ + struct aa_ns *ns; + char name[32]; + int res; + + ns = aa_get_current_ns(); + res = ns_get_name(name, sizeof(name), ns, d_inode(dentry)); + if (res >= 0) + res = readlink_copy(buffer, buflen, name); + aa_put_ns(ns); + + return res; +} + +static const struct inode_operations policy_link_iops = { + .readlink = policy_readlink, + .get_link = policy_get_link, +}; + + /** * aa_create_aafs - create the apparmor security filesystem * @@ -1389,17 +2410,23 @@ static int __init aa_create_aafs(void) if (!apparmor_initialized) return 0; - if (aa_fs_entry.dentry) { + if (aa_sfs_entry.dentry) { AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); return -EEXIST; } + /* setup apparmorfs used to virtualize policy/ */ + aafs_mnt = kern_mount(&aafs_ops); + if (IS_ERR(aafs_mnt)) + panic("can't set apparmorfs up\n"); + aafs_mnt->mnt_sb->s_flags &= ~MS_NOUSER; + /* Populate fs tree. */ - error = aafs_create_dir(&aa_fs_entry, NULL); + error = entry_create_dir(&aa_sfs_entry, NULL); if (error) goto error; - dent = securityfs_create_file(".load", 0666, aa_fs_entry.dentry, + dent = securityfs_create_file(".load", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_load); if (IS_ERR(dent)) { error = PTR_ERR(dent); @@ -1407,7 +2434,7 @@ static int __init aa_create_aafs(void) } ns_subload(root_ns) = dent; - dent = securityfs_create_file(".replace", 0666, aa_fs_entry.dentry, + dent = securityfs_create_file(".replace", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_replace); if (IS_ERR(dent)) { error = PTR_ERR(dent); @@ -1415,7 +2442,7 @@ static int __init aa_create_aafs(void) } ns_subreplace(root_ns) = dent; - dent = securityfs_create_file(".remove", 0666, aa_fs_entry.dentry, + dent = securityfs_create_file(".remove", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_remove); if (IS_ERR(dent)) { error = PTR_ERR(dent); @@ -1423,14 +2450,31 @@ static int __init aa_create_aafs(void) } ns_subremove(root_ns) = dent; + dent = securityfs_create_file("revision", 0444, aa_sfs_entry.dentry, + NULL, &aa_fs_ns_revision_fops); + if (IS_ERR(dent)) { + error = PTR_ERR(dent); + goto error; + } + ns_subrevision(root_ns) = dent; + + /* policy tree referenced by magic policy symlink */ mutex_lock(&root_ns->lock); - error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy"); + error = __aafs_ns_mkdir(root_ns, aafs_mnt->mnt_root, ".policy", + aafs_mnt->mnt_root); mutex_unlock(&root_ns->lock); - if (error) goto error; - error = aa_mk_null_file(aa_fs_entry.dentry); + /* magic symlink similar to nsfs redirects based on task policy */ + dent = securityfs_create_symlink("policy", aa_sfs_entry.dentry, + NULL, &policy_link_iops); + if (IS_ERR(dent)) { + error = PTR_ERR(dent); + goto error; + } + + error = aa_mk_null_file(aa_sfs_entry.dentry); if (error) goto error; diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 87f40fa8c431..8f9ecac7f8de 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -77,14 +77,24 @@ static void audit_pre(struct audit_buffer *ab, void *ca) audit_log_format(ab, " error=%d", aad(sa)->error); } - if (aad(sa)->profile) { - struct aa_profile *profile = aad(sa)->profile; - if (profile->ns != root_ns) { - audit_log_format(ab, " namespace="); - audit_log_untrustedstring(ab, profile->ns->base.hname); + if (aad(sa)->label) { + struct aa_label *label = aad(sa)->label; + + if (label_isprofile(label)) { + struct aa_profile *profile = labels_profile(label); + + if (profile->ns != root_ns) { + audit_log_format(ab, " namespace="); + audit_log_untrustedstring(ab, + profile->ns->base.hname); + } + audit_log_format(ab, " profile="); + audit_log_untrustedstring(ab, profile->base.hname); + } else { + audit_log_format(ab, " label="); + aa_label_xaudit(ab, root_ns, label, FLAG_VIEW_SUBNS, + GFP_ATOMIC); } - audit_log_format(ab, " profile="); - audit_log_untrustedstring(ab, profile->base.hname); } if (aad(sa)->name) { @@ -139,8 +149,7 @@ int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa, if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) type = AUDIT_APPARMOR_KILL; - if (!unconfined(profile)) - aad(sa)->profile = profile; + aad(sa)->label = &profile->label; aa_audit_msg(type, sa, cb); diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index ed0a3e6b8022..67e347192a55 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -28,8 +28,8 @@ */ #include "capability_names.h" -struct aa_fs_entry aa_fs_entry_caps[] = { - AA_FS_FILE_STRING("mask", AA_FS_CAPS_MASK), +struct aa_sfs_entry aa_sfs_entry_caps[] = { + AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK), { } }; @@ -48,15 +48,16 @@ static DEFINE_PER_CPU(struct audit_cache, audit_cache); static void audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; + audit_log_format(ab, " capname="); audit_log_untrustedstring(ab, capability_names[sa->u.cap]); } /** * audit_caps - audit a capability + * @sa: audit data * @profile: profile being tested for confinement (NOT NULL) * @cap: capability tested - @audit: whether an audit record should be generated * @error: error code returned by test * * Do auditing of capability and handle, audit/complain/kill modes switching @@ -64,16 +65,13 @@ static void audit_cb(struct audit_buffer *ab, void *va) * * Returns: 0 or sa->error on success, error code on failure */ -static int audit_caps(struct aa_profile *profile, int cap, int audit, - int error) +static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile, + int cap, int error) { struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE); - sa.u.cap = cap; - aad(&sa)->error = error; - if (audit == SECURITY_CAP_NOAUDIT) - aad(&sa)->info = "optional: no audit"; + + aad(sa)->error = error; if (likely(!error)) { /* test if auditing is being forced */ @@ -105,24 +103,44 @@ static int audit_caps(struct aa_profile *profile, int cap, int audit, } put_cpu_var(audit_cache); - return aa_audit(type, profile, &sa, audit_cb); + return aa_audit(type, profile, sa, audit_cb); } /** * profile_capable - test if profile allows use of capability @cap * @profile: profile being enforced (NOT NULL, NOT unconfined) * @cap: capability to test if allowed + * @audit: whether an audit record should be generated + * @sa: audit data (MAY BE NULL indicating no auditing) * * Returns: 0 if allowed else -EPERM */ -static int profile_capable(struct aa_profile *profile, int cap) +static int profile_capable(struct aa_profile *profile, int cap, int audit, + struct common_audit_data *sa) { - return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM; + int error; + + if (cap_raised(profile->caps.allow, cap) && + !cap_raised(profile->caps.denied, cap)) + error = 0; + else + error = -EPERM; + + if (audit == SECURITY_CAP_NOAUDIT) { + if (!COMPLAIN_MODE(profile)) + return error; + /* audit the cap request in complain mode but note that it + * should be optional. + */ + aad(sa)->info = "optional: no audit"; + } + + return audit_caps(sa, profile, cap, error); } /** * aa_capable - test permission to use capability - * @profile: profile being tested against (NOT NULL) + * @label: label being tested for capability (NOT NULL) * @cap: capability to be tested * @audit: whether an audit record should be generated * @@ -130,14 +148,15 @@ static int profile_capable(struct aa_profile *profile, int cap) * * Returns: 0 on success, or else an error code. */ -int aa_capable(struct aa_profile *profile, int cap, int audit) +int aa_capable(struct aa_label *label, int cap, int audit) { - int error = profile_capable(profile, cap); + struct aa_profile *profile; + int error = 0; + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE); - if (audit == SECURITY_CAP_NOAUDIT) { - if (!COMPLAIN_MODE(profile)) - return error; - } + sa.u.cap = cap; + error = fn_for_each_confined(label, profile, + profile_capable(profile, cap, audit, &sa)); - return audit_caps(profile, cap, audit, error); + return error; } diff --git a/security/apparmor/context.c b/security/apparmor/context.c index 1fc16b88efbf..c95f1ac6190b 100644 --- a/security/apparmor/context.c +++ b/security/apparmor/context.c @@ -14,9 +14,9 @@ * * * AppArmor sets confinement on every task, via the the aa_task_ctx and - * the aa_task_ctx.profile, both of which are required and are not allowed + * the aa_task_ctx.label, both of which are required and are not allowed * to be NULL. The aa_task_ctx is not reference counted and is unique - * to each cred (which is reference count). The profile pointed to by + * to each cred (which is reference count). The label pointed to by * the task_ctx is reference counted. * * TODO @@ -47,9 +47,9 @@ struct aa_task_ctx *aa_alloc_task_context(gfp_t flags) void aa_free_task_context(struct aa_task_ctx *ctx) { if (ctx) { - aa_put_profile(ctx->profile); - aa_put_profile(ctx->previous); - aa_put_profile(ctx->onexec); + aa_put_label(ctx->label); + aa_put_label(ctx->previous); + aa_put_label(ctx->onexec); kzfree(ctx); } @@ -63,41 +63,41 @@ void aa_free_task_context(struct aa_task_ctx *ctx) void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old) { *new = *old; - aa_get_profile(new->profile); - aa_get_profile(new->previous); - aa_get_profile(new->onexec); + aa_get_label(new->label); + aa_get_label(new->previous); + aa_get_label(new->onexec); } /** - * aa_get_task_profile - Get another task's profile + * aa_get_task_label - Get another task's label * @task: task to query (NOT NULL) * - * Returns: counted reference to @task's profile + * Returns: counted reference to @task's label */ -struct aa_profile *aa_get_task_profile(struct task_struct *task) +struct aa_label *aa_get_task_label(struct task_struct *task) { - struct aa_profile *p; + struct aa_label *p; rcu_read_lock(); - p = aa_get_profile(__aa_task_profile(task)); + p = aa_get_newest_label(__aa_task_raw_label(task)); rcu_read_unlock(); return p; } /** - * aa_replace_current_profile - replace the current tasks profiles - * @profile: new profile (NOT NULL) + * aa_replace_current_label - replace the current tasks label + * @label: new label (NOT NULL) * * Returns: 0 or error on failure */ -int aa_replace_current_profile(struct aa_profile *profile) +int aa_replace_current_label(struct aa_label *label) { struct aa_task_ctx *ctx = current_ctx(); struct cred *new; - AA_BUG(!profile); + AA_BUG(!label); - if (ctx->profile == profile) + if (ctx->label == label) return 0; if (current_cred() != current_real_cred()) @@ -108,8 +108,8 @@ int aa_replace_current_profile(struct aa_profile *profile) return -ENOMEM; ctx = cred_ctx(new); - if (unconfined(profile) || (ctx->profile->ns != profile->ns)) - /* if switching to unconfined or a different profile namespace + if (unconfined(label) || (labels_ns(ctx->label) != labels_ns(label))) + /* if switching to unconfined or a different label namespace * clear out context state */ aa_clear_task_ctx_trans(ctx); @@ -120,9 +120,9 @@ int aa_replace_current_profile(struct aa_profile *profile) * keeping @profile valid, so make sure to get its reference before * dropping the reference on ctx->profile */ - aa_get_profile(profile); - aa_put_profile(ctx->profile); - ctx->profile = profile; + aa_get_label(label); + aa_put_label(ctx->label); + ctx->label = label; commit_creds(new); return 0; @@ -130,11 +130,11 @@ int aa_replace_current_profile(struct aa_profile *profile) /** * aa_set_current_onexec - set the tasks change_profile to happen onexec - * @profile: system profile to set at exec (MAYBE NULL to clear value) - * + * @label: system label to set at exec (MAYBE NULL to clear value) + * @stack: whether stacking should be done * Returns: 0 or error on failure */ -int aa_set_current_onexec(struct aa_profile *profile) +int aa_set_current_onexec(struct aa_label *label, bool stack) { struct aa_task_ctx *ctx; struct cred *new = prepare_creds(); @@ -142,9 +142,10 @@ int aa_set_current_onexec(struct aa_profile *profile) return -ENOMEM; ctx = cred_ctx(new); - aa_get_profile(profile); - aa_put_profile(ctx->onexec); - ctx->onexec = profile; + aa_get_label(label); + aa_clear_task_ctx_trans(ctx); + ctx->onexec = label; + ctx->token = stack; commit_creds(new); return 0; @@ -152,7 +153,7 @@ int aa_set_current_onexec(struct aa_profile *profile) /** * aa_set_current_hat - set the current tasks hat - * @profile: profile to set as the current hat (NOT NULL) + * @label: label to set as the current hat (NOT NULL) * @token: token value that must be specified to change from the hat * * Do switch of tasks hat. If the task is currently in a hat @@ -160,29 +161,29 @@ int aa_set_current_onexec(struct aa_profile *profile) * * Returns: 0 or error on failure */ -int aa_set_current_hat(struct aa_profile *profile, u64 token) +int aa_set_current_hat(struct aa_label *label, u64 token) { struct aa_task_ctx *ctx; struct cred *new = prepare_creds(); if (!new) return -ENOMEM; - AA_BUG(!profile); + AA_BUG(!label); ctx = cred_ctx(new); if (!ctx->previous) { /* transfer refcount */ - ctx->previous = ctx->profile; + ctx->previous = ctx->label; ctx->token = token; } else if (ctx->token == token) { - aa_put_profile(ctx->profile); + aa_put_label(ctx->label); } else { /* previous_profile && ctx->token != token */ abort_creds(new); return -EACCES; } - ctx->profile = aa_get_newest_profile(profile); + ctx->label = aa_get_newest_label(label); /* clear exec on switching context */ - aa_put_profile(ctx->onexec); + aa_put_label(ctx->onexec); ctx->onexec = NULL; commit_creds(new); @@ -190,15 +191,15 @@ int aa_set_current_hat(struct aa_profile *profile, u64 token) } /** - * aa_restore_previous_profile - exit from hat context restoring the profile + * aa_restore_previous_label - exit from hat context restoring previous label * @token: the token that must be matched to exit hat context * - * Attempt to return out of a hat to the previous profile. The token + * Attempt to return out of a hat to the previous label. The token * must match the stored token value. * * Returns: 0 or error of failure */ -int aa_restore_previous_profile(u64 token) +int aa_restore_previous_label(u64 token) { struct aa_task_ctx *ctx; struct cred *new = prepare_creds(); @@ -210,15 +211,15 @@ int aa_restore_previous_profile(u64 token) abort_creds(new); return -EACCES; } - /* ignore restores when there is no saved profile */ + /* ignore restores when there is no saved label */ if (!ctx->previous) { abort_creds(new); return 0; } - aa_put_profile(ctx->profile); - ctx->profile = aa_get_newest_profile(ctx->previous); - AA_BUG(!ctx->profile); + aa_put_label(ctx->label); + ctx->label = aa_get_newest_label(ctx->previous); + AA_BUG(!ctx->label); /* clear exec && prev information when restoring to previous context */ aa_clear_task_ctx_trans(ctx); diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 001e133a3c8c..d0594446ae3f 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -51,76 +51,254 @@ void aa_free_domain_entries(struct aa_domain *domain) /** * may_change_ptraced_domain - check if can change profile on ptraced task - * @to_profile: profile to change to (NOT NULL) + * @to_label: profile to change to (NOT NULL) + * @info: message if there is an error * * Check if current is ptraced and if so if the tracing task is allowed * to trace the new domain * * Returns: %0 or error if change not allowed */ -static int may_change_ptraced_domain(struct aa_profile *to_profile) +static int may_change_ptraced_domain(struct aa_label *to_label, + const char **info) { struct task_struct *tracer; - struct aa_profile *tracerp = NULL; + struct aa_label *tracerl = NULL; int error = 0; rcu_read_lock(); tracer = ptrace_parent(current); if (tracer) /* released below */ - tracerp = aa_get_task_profile(tracer); + tracerl = aa_get_task_label(tracer); /* not ptraced */ - if (!tracer || unconfined(tracerp)) + if (!tracer || unconfined(tracerl)) goto out; - error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH); + error = aa_may_ptrace(tracerl, to_label, PTRACE_MODE_ATTACH); out: rcu_read_unlock(); - aa_put_profile(tracerp); + aa_put_label(tracerl); + if (error) + *info = "ptrace prevents transition"; return error; } +/**** TODO: dedup to aa_label_match - needs perm and dfa, merging + * specifically this is an exact copy of aa_label_match except + * aa_compute_perms is replaced with aa_compute_fperms + * and policy.dfa with file.dfa + ****/ +/* match a profile and its associated ns component if needed + * Assumes visibility test has already been done. + * If a subns profile is not to be matched should be prescreened with + * visibility test. + */ +static inline unsigned int match_component(struct aa_profile *profile, + struct aa_profile *tp, + bool stack, unsigned int state) +{ + const char *ns_name; + + if (stack) + state = aa_dfa_match(profile->file.dfa, state, "&"); + if (profile->ns == tp->ns) + return aa_dfa_match(profile->file.dfa, state, tp->base.hname); + + /* try matching with namespace name and then profile */ + ns_name = aa_ns_name(profile->ns, tp->ns, true); + state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); + state = aa_dfa_match(profile->file.dfa, state, ns_name); + state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); + return aa_dfa_match(profile->file.dfa, state, tp->base.hname); +} + +/** + * label_compound_match - find perms for full compound label + * @profile: profile to find perms for + * @label: label to check access permissions for + * @stack: whether this is a stacking request + * @start: state to start match in + * @subns: whether to do permission checks on components in a subns + * @request: permissions to request + * @perms: perms struct to set + * + * Returns: 0 on success else ERROR + * + * For the label A//&B//&C this does the perm match for A//&B//&C + * @perms should be preinitialized with allperms OR a previous permission + * check to be stacked. + */ +static int label_compound_match(struct aa_profile *profile, + struct aa_label *label, bool stack, + unsigned int state, bool subns, u32 request, + struct aa_perms *perms) +{ + struct aa_profile *tp; + struct label_it i; + struct path_cond cond = { }; + + /* find first subcomponent that is visible */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, stack, state); + if (!state) + goto fail; + goto next; + } + + /* no component visible */ + *perms = allperms; + return 0; + +next: + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = aa_dfa_match(profile->file.dfa, state, "//&"); + state = match_component(profile, tp, false, state); + if (!state) + goto fail; + } + *perms = aa_compute_fperms(profile->file.dfa, state, &cond); + aa_apply_modes_to_perms(profile, perms); + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return -EACCES; +} + +/** + * label_components_match - find perms for all subcomponents of a label + * @profile: profile to find perms for + * @label: label to check access permissions for + * @stack: whether this is a stacking request + * @start: state to start match in + * @subns: whether to do permission checks on components in a subns + * @request: permissions to request + * @perms: an initialized perms struct to add accumulation to + * + * Returns: 0 on success else ERROR + * + * For the label A//&B//&C this does the perm match for each of A and B and C + * @perms should be preinitialized with allperms OR a previous permission + * check to be stacked. + */ +static int label_components_match(struct aa_profile *profile, + struct aa_label *label, bool stack, + unsigned int start, bool subns, u32 request, + struct aa_perms *perms) +{ + struct aa_profile *tp; + struct label_it i; + struct aa_perms tmp; + struct path_cond cond = { }; + unsigned int state = 0; + + /* find first subcomponent to test */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, stack, start); + if (!state) + goto fail; + goto next; + } + + /* no subcomponents visible - no change in perms */ + return 0; + +next: + tmp = aa_compute_fperms(profile->file.dfa, state, &cond); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, stack, start); + if (!state) + goto fail; + tmp = aa_compute_fperms(profile->file.dfa, state, &cond); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + } + + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return -EACCES; +} + +/** + * label_match - do a multi-component label match + * @profile: profile to match against (NOT NULL) + * @label: label to match (NOT NULL) + * @stack: whether this is a stacking request + * @state: state to start in + * @subns: whether to match subns components + * @request: permission request + * @perms: Returns computed perms (NOT NULL) + * + * Returns: the state the match finished in, may be the none matching state + */ +static int label_match(struct aa_profile *profile, struct aa_label *label, + bool stack, unsigned int state, bool subns, u32 request, + struct aa_perms *perms) +{ + int error; + + *perms = nullperms; + error = label_compound_match(profile, label, stack, state, subns, + request, perms); + if (!error) + return error; + + *perms = allperms; + return label_components_match(profile, label, stack, state, subns, + request, perms); +} + +/******* end TODO: dedup *****/ + /** * change_profile_perms - find permissions for change_profile * @profile: the current profile (NOT NULL) - * @ns: the namespace being switched to (NOT NULL) - * @name: the name of the profile to change to (NOT NULL) + * @target: label to transition to (NOT NULL) + * @stack: whether this is a stacking request * @request: requested perms * @start: state to start matching in * + * * Returns: permission set + * + * currently only matches full label A//&B//&C or individual components A, B, C + * not arbitrary combinations. Eg. A//&B, C */ -static struct file_perms change_profile_perms(struct aa_profile *profile, - struct aa_ns *ns, - const char *name, u32 request, - unsigned int start) +static int change_profile_perms(struct aa_profile *profile, + struct aa_label *target, bool stack, + u32 request, unsigned int start, + struct aa_perms *perms) { - struct file_perms perms; - struct path_cond cond = { }; - unsigned int state; - - if (unconfined(profile)) { - perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; - perms.audit = perms.quiet = perms.kill = 0; - return perms; - } else if (!profile->file.dfa) { - return nullperms; - } else if ((ns == profile->ns)) { - /* try matching against rules with out namespace prepended */ - aa_str_perms(profile->file.dfa, start, name, &cond, &perms); - if (COMBINED_PERM_MASK(perms) & request) - return perms; + if (profile_unconfined(profile)) { + perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; + perms->audit = perms->quiet = perms->kill = 0; + return 0; } - /* try matching with namespace name and then profile */ - state = aa_dfa_match(profile->file.dfa, start, ns->base.name); - state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); - aa_str_perms(profile->file.dfa, state, name, &cond, &perms); - - return perms; + /* TODO: add profile in ns screening */ + return label_match(profile, target, stack, start, true, request, perms); } /** @@ -144,7 +322,7 @@ static struct aa_profile *__attach_match(const char *name, struct aa_profile *profile, *candidate = NULL; list_for_each_entry_rcu(profile, head, base.list) { - if (profile->flags & PFLAG_NULL) + if (profile->label.flags & FLAG_NULL) continue; if (profile->xmatch && profile->xmatch_len > len) { unsigned int state = aa_dfa_match(profile->xmatch, @@ -169,10 +347,10 @@ static struct aa_profile *__attach_match(const char *name, * @list: list to search (NOT NULL) * @name: the executable name to match against (NOT NULL) * - * Returns: profile or NULL if no match found + * Returns: label or NULL if no match found */ -static struct aa_profile *find_attach(struct aa_ns *ns, - struct list_head *list, const char *name) +static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list, + const char *name) { struct aa_profile *profile; @@ -180,49 +358,7 @@ static struct aa_profile *find_attach(struct aa_ns *ns, profile = aa_get_profile(__attach_match(name, list)); rcu_read_unlock(); - return profile; -} - -/** - * separate_fqname - separate the namespace and profile names - * @fqname: the fqname name to split (NOT NULL) - * @ns_name: the namespace name if it exists (NOT NULL) - * - * This is the xtable equivalent routine of aa_split_fqname. It finds the - * split in an xtable fqname which contains an embedded \0 instead of a : - * if a namespace is specified. This is done so the xtable is constant and - * isn't re-split on every lookup. - * - * Either the profile or namespace name may be optional but if the namespace - * is specified the profile name termination must be present. This results - * in the following possible encodings: - * profile_name\0 - * :ns_name\0profile_name\0 - * :ns_name\0\0 - * - * NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table - * - * Returns: profile name if it is specified else NULL - */ -static const char *separate_fqname(const char *fqname, const char **ns_name) -{ - const char *name; - - if (fqname[0] == ':') { - /* In this case there is guaranteed to be two \0 terminators - * in the string. They are verified at load time by - * by unpack_trans_table - */ - *ns_name = fqname + 1; /* skip : */ - name = *ns_name + strlen(*ns_name) + 1; - if (!*name) - name = NULL; - } else { - *ns_name = NULL; - name = fqname; - } - - return name; + return profile ? &profile->label : NULL; } static const char *next_name(int xtype, const char *name) @@ -234,290 +370,477 @@ static const char *next_name(int xtype, const char *name) * x_table_lookup - lookup an x transition name via transition table * @profile: current profile (NOT NULL) * @xindex: index into x transition table + * @name: returns: name tested to find label (NOT NULL) * - * Returns: refcounted profile, or NULL on failure (MAYBE NULL) + * Returns: refcounted label, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +static struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, + const char **name) { - struct aa_profile *new_profile = NULL; - struct aa_ns *ns = profile->ns; + struct aa_label *label = NULL; u32 xtype = xindex & AA_X_TYPE_MASK; int index = xindex & AA_X_INDEX_MASK; - const char *name; - /* index is guaranteed to be in range, validated at load time */ - for (name = profile->file.trans.table[index]; !new_profile && name; - name = next_name(xtype, name)) { - struct aa_ns *new_ns; - const char *xname = NULL; + AA_BUG(!name); - new_ns = NULL; + /* index is guaranteed to be in range, validated at load time */ + /* TODO: move lookup parsing to unpack time so this is a straight + * index into the resultant label + */ + for (*name = profile->file.trans.table[index]; !label && *name; + *name = next_name(xtype, *name)) { if (xindex & AA_X_CHILD) { + struct aa_profile *new_profile; /* release by caller */ - new_profile = aa_find_child(profile, name); + new_profile = aa_find_child(profile, *name); + if (new_profile) + label = &new_profile->label; continue; - } else if (*name == ':') { - /* switching namespace */ - const char *ns_name; - xname = name = separate_fqname(name, &ns_name); - if (!xname) - /* no name so use profile name */ - xname = profile->base.hname; - if (*ns_name == '@') { - /* TODO: variable support */ - ; - } - /* released below */ - new_ns = aa_find_ns(ns, ns_name); - if (!new_ns) - continue; - } else if (*name == '@') { - /* TODO: variable support */ - continue; - } else { - /* basic namespace lookup */ - xname = name; } - - /* released by caller */ - new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname); - aa_put_ns(new_ns); + label = aa_label_parse(&profile->label, *name, GFP_ATOMIC, + true, false); + if (IS_ERR(label)) + label = NULL; } /* released by caller */ - return new_profile; + + return label; } /** - * x_to_profile - get target profile for a given xindex + * x_to_label - get target label for a given xindex * @profile: current profile (NOT NULL) * @name: name to lookup (NOT NULL) * @xindex: index into x transition table + * @lookupname: returns: name used in lookup if one was specified (NOT NULL) * - * find profile for a transition index + * find label for a transition index * - * Returns: refcounted profile or NULL if not found available + * Returns: refcounted label or NULL if not found available */ -static struct aa_profile *x_to_profile(struct aa_profile *profile, - const char *name, u32 xindex) +static struct aa_label *x_to_label(struct aa_profile *profile, + const char *name, u32 xindex, + const char **lookupname, + const char **info) { - struct aa_profile *new_profile = NULL; + struct aa_label *new = NULL; struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; + const char *stack = NULL; switch (xtype) { case AA_X_NONE: /* fail exec unless ix || ux fallback - handled by caller */ - return NULL; + *lookupname = NULL; + break; + case AA_X_TABLE: + /* TODO: fix when perm mapping done at unload */ + stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK]; + if (*stack != '&') { + /* released by caller */ + new = x_table_lookup(profile, xindex, lookupname); + stack = NULL; + break; + } + /* fall through to X_NAME */ case AA_X_NAME: if (xindex & AA_X_CHILD) /* released by caller */ - new_profile = find_attach(ns, &profile->base.profiles, - name); + new = find_attach(ns, &profile->base.profiles, + name); else /* released by caller */ - new_profile = find_attach(ns, &ns->base.profiles, - name); - break; - case AA_X_TABLE: - /* released by caller */ - new_profile = x_table_lookup(profile, xindex); + new = find_attach(ns, &ns->base.profiles, + name); + *lookupname = name; break; } + if (!new) { + if (xindex & AA_X_INHERIT) { + /* (p|c|n)ix - don't change profile but do + * use the newest version + */ + *info = "ix fallback"; + /* no profile && no error */ + new = aa_get_newest_label(&profile->label); + } else if (xindex & AA_X_UNCONFINED) { + new = aa_get_newest_label(ns_unconfined(profile->ns)); + *info = "ux fallback"; + } + } + + if (new && stack) { + /* base the stack on post domain transition */ + struct aa_label *base = new; + + new = aa_label_parse(base, stack, GFP_ATOMIC, true, false); + if (IS_ERR(new)) + new = NULL; + aa_put_label(base); + } + /* released by caller */ - return new_profile; + return new; } -/** - * apparmor_bprm_set_creds - set the new creds on the bprm struct - * @bprm: binprm for the exec (NOT NULL) - * - * Returns: %0 or error on failure - */ -int apparmor_bprm_set_creds(struct linux_binprm *bprm) +static struct aa_label *profile_transition(struct aa_profile *profile, + const struct linux_binprm *bprm, + char *buffer, struct path_cond *cond, + bool *secure_exec) { - struct aa_task_ctx *ctx; - struct aa_profile *profile, *new_profile = NULL; - struct aa_ns *ns; - char *buffer = NULL; - unsigned int state; - struct file_perms perms = {}; - struct path_cond cond = { - file_inode(bprm->file)->i_uid, - file_inode(bprm->file)->i_mode - }; - const char *name = NULL, *info = NULL; + struct aa_label *new = NULL; + const char *info = NULL, *name = NULL, *target = NULL; + unsigned int state = profile->file.start; + struct aa_perms perms = {}; + bool nonewprivs = false; int error = 0; - if (bprm->cred_prepared) - return 0; - - ctx = cred_ctx(bprm->cred); - AA_BUG(!ctx); - - profile = aa_get_newest_profile(ctx->profile); - /* - * get the namespace from the replacement profile as replacement - * can change the namespace - */ - ns = profile->ns; - state = profile->file.start; + AA_BUG(!profile); + AA_BUG(!bprm); + AA_BUG(!buffer); - /* buffer freed below, name is pointer into buffer */ - error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, - &name, &info); + error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, + &name, &info, profile->disconnected); if (error) { - if (unconfined(profile) || - (profile->flags & PFLAG_IX_ON_NAME_ERROR)) + if (profile_unconfined(profile) || + (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { + AA_DEBUG("name lookup ix on error"); error = 0; + new = aa_get_newest_label(&profile->label); + } name = bprm->filename; goto audit; } - /* Test for onexec first as onexec directives override other - * x transitions. - */ - if (unconfined(profile)) { - /* unconfined task */ - if (ctx->onexec) - /* change_profile on exec already been granted */ - new_profile = aa_get_profile(ctx->onexec); - else - new_profile = find_attach(ns, &ns->base.profiles, name); - if (!new_profile) - goto cleanup; - /* - * NOTE: Domain transitions from unconfined are allowed - * even when no_new_privs is set because this aways results - * in a further reduction of permissions. - */ - goto apply; + if (profile_unconfined(profile)) { + new = find_attach(profile->ns, &profile->ns->base.profiles, + name); + if (new) { + AA_DEBUG("unconfined attached to new label"); + return new; + } + AA_DEBUG("unconfined exec no attachment"); + return aa_get_newest_label(&profile->label); } /* find exec permissions for name */ - state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms); - if (ctx->onexec) { - struct file_perms cp; - info = "change_profile onexec"; - new_profile = aa_get_newest_profile(ctx->onexec); - if (!(perms.allow & AA_MAY_ONEXEC)) - goto audit; - - /* test if this exec can be paired with change_profile onexec. - * onexec permission is linked to exec with a standard pairing - * exec\0change_profile - */ - state = aa_dfa_null_transition(profile->file.dfa, state); - cp = change_profile_perms(profile, ctx->onexec->ns, - ctx->onexec->base.name, - AA_MAY_ONEXEC, state); - - if (!(cp.allow & AA_MAY_ONEXEC)) - goto audit; - goto apply; - } - + state = aa_str_perms(profile->file.dfa, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ - new_profile = x_to_profile(profile, name, perms.xindex); - if (!new_profile) { - if (perms.xindex & AA_X_INHERIT) { - /* (p|c|n)ix - don't change profile but do - * use the newest version, which was picked - * up above when getting profile - */ - info = "ix fallback"; - new_profile = aa_get_profile(profile); - goto x_clear; - } else if (perms.xindex & AA_X_UNCONFINED) { - new_profile = aa_get_newest_profile(ns->unconfined); - info = "ux fallback"; - } else { - error = -EACCES; - info = "profile not found"; - /* remove MAY_EXEC to audit as failure */ - perms.allow &= ~MAY_EXEC; - } + new = x_to_label(profile, name, perms.xindex, &target, &info); + if (new && new->proxy == profile->label.proxy && info) { + /* hack ix fallback - improve how this is detected */ + goto audit; + } else if (!new) { + error = -EACCES; + info = "profile transition not found"; + /* remove MAY_EXEC to audit as failure */ + perms.allow &= ~MAY_EXEC; } } else if (COMPLAIN_MODE(profile)) { - /* no exec permission - are we in learning mode */ - new_profile = aa_new_null_profile(profile, false, name, - GFP_ATOMIC); + /* no exec permission - learning mode */ + struct aa_profile *new_profile = aa_new_null_profile(profile, + false, name, + GFP_ATOMIC); if (!new_profile) { error = -ENOMEM; info = "could not create null profile"; - } else + } else { error = -EACCES; + new = &new_profile->label; + } perms.xindex |= AA_X_UNSAFE; } else /* fail exec */ error = -EACCES; - /* - * Policy has specified a domain transition, if no_new_privs then - * fail the exec. + if (!new) + goto audit; + + /* Policy has specified a domain transitions. if no_new_privs and + * confined and not transitioning to the current domain fail. + * + * NOTE: Domain transitions from unconfined and to stritly stacked + * subsets are allowed even when no_new_privs is set because this + * aways results in a further reduction of permissions. */ - if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) { + if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && + !profile_unconfined(profile) && + !aa_label_is_subset(new, &profile->label)) { error = -EPERM; - goto cleanup; + info = "no new privs"; + nonewprivs = true; + perms.allow &= ~MAY_EXEC; + goto audit; + } + + if (!(perms.xindex & AA_X_UNSAFE)) { + if (DEBUG_ON) { + dbg_printk("apparmor: scrubbing environment variables" + " for %s profile=", name); + aa_label_printk(new, GFP_ATOMIC); + dbg_printk("\n"); + } + *secure_exec = true; + } + +audit: + aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target, new, + cond->uid, info, error); + if (!new || nonewprivs) { + aa_put_label(new); + return ERR_PTR(error); } - if (!new_profile) + return new; +} + +static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, + bool stack, const struct linux_binprm *bprm, + char *buffer, struct path_cond *cond, + bool *secure_exec) +{ + unsigned int state = profile->file.start; + struct aa_perms perms = {}; + const char *xname = NULL, *info = "change_profile onexec"; + int error = -EACCES; + + AA_BUG(!profile); + AA_BUG(!onexec); + AA_BUG(!bprm); + AA_BUG(!buffer); + + if (profile_unconfined(profile)) { + /* change_profile on exec already granted */ + /* + * NOTE: Domain transitions from unconfined are allowed + * even when no_new_privs is set because this aways results + * in a further reduction of permissions. + */ + return 0; + } + + error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, + &xname, &info, profile->disconnected); + if (error) { + if (profile_unconfined(profile) || + (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { + AA_DEBUG("name lookup ix on error"); + error = 0; + } + xname = bprm->filename; + goto audit; + } + + /* find exec permissions for name */ + state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms); + if (!(perms.allow & AA_MAY_ONEXEC)) { + info = "no change_onexec valid for executable"; + goto audit; + } + /* test if this exec can be paired with change_profile onexec. + * onexec permission is linked to exec with a standard pairing + * exec\0change_profile + */ + state = aa_dfa_null_transition(profile->file.dfa, state); + error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC, + state, &perms); + if (error) { + perms.allow &= ~AA_MAY_ONEXEC; goto audit; + } + /* Policy has specified a domain transitions. if no_new_privs and + * confined and not transitioning to the current domain fail. + * + * NOTE: Domain transitions from unconfined and to stritly stacked + * subsets are allowed even when no_new_privs is set because this + * aways results in a further reduction of permissions. + */ + if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && + !profile_unconfined(profile) && + !aa_label_is_subset(onexec, &profile->label)) { + error = -EPERM; + info = "no new privs"; + perms.allow &= ~AA_MAY_ONEXEC; + goto audit; + } + + if (!(perms.xindex & AA_X_UNSAFE)) { + if (DEBUG_ON) { + dbg_printk("apparmor: scrubbing environment " + "variables for %s label=", xname); + aa_label_printk(onexec, GFP_ATOMIC); + dbg_printk("\n"); + } + *secure_exec = true; + } + +audit: + return aa_audit_file(profile, &perms, OP_EXEC, AA_MAY_ONEXEC, xname, + NULL, onexec, cond->uid, info, error); +} + +/* ensure none ns domain transitions are correctly applied with onexec */ + +static struct aa_label *handle_onexec(struct aa_label *label, + struct aa_label *onexec, bool stack, + const struct linux_binprm *bprm, + char *buffer, struct path_cond *cond, + bool *unsafe) +{ + struct aa_profile *profile; + struct aa_label *new; + int error; + + AA_BUG(!label); + AA_BUG(!onexec); + AA_BUG(!bprm); + AA_BUG(!buffer); + + if (!stack) { + error = fn_for_each_in_ns(label, profile, + profile_onexec(profile, onexec, stack, + bprm, buffer, cond, unsafe)); + if (error) + return ERR_PTR(error); + new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, + aa_get_newest_label(onexec), + profile_transition(profile, bprm, buffer, + cond, unsafe)); + + } else { + /* TODO: determine how much we want to losen this */ + error = fn_for_each_in_ns(label, profile, + profile_onexec(profile, onexec, stack, bprm, + buffer, cond, unsafe)); + if (error) + return ERR_PTR(error); + new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, + aa_label_merge(&profile->label, onexec, + GFP_ATOMIC), + profile_transition(profile, bprm, buffer, + cond, unsafe)); + } + + if (new) + return new; + + /* TODO: get rid of GLOBAL_ROOT_UID */ + error = fn_for_each_in_ns(label, profile, + aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC, + AA_MAY_ONEXEC, bprm->filename, NULL, + onexec, GLOBAL_ROOT_UID, + "failed to build target label", -ENOMEM)); + return ERR_PTR(error); +} + +/** + * apparmor_bprm_set_creds - set the new creds on the bprm struct + * @bprm: binprm for the exec (NOT NULL) + * + * Returns: %0 or error on failure + * + * TODO: once the other paths are done see if we can't refactor into a fn + */ +int apparmor_bprm_set_creds(struct linux_binprm *bprm) +{ + struct aa_task_ctx *ctx; + struct aa_label *label, *new = NULL; + struct aa_profile *profile; + char *buffer = NULL; + const char *info = NULL; + int error = 0; + bool unsafe = false; + struct path_cond cond = { + file_inode(bprm->file)->i_uid, + file_inode(bprm->file)->i_mode + }; + + if (bprm->cred_prepared) + return 0; + + ctx = cred_ctx(bprm->cred); + AA_BUG(!ctx); + + label = aa_get_newest_label(ctx->label); + + /* buffer freed below, name is pointer into buffer */ + get_buffers(buffer); + /* Test for onexec first as onexec override other x transitions. */ + if (ctx->onexec) + new = handle_onexec(label, ctx->onexec, ctx->token, + bprm, buffer, &cond, &unsafe); + else + new = fn_label_build(label, profile, GFP_ATOMIC, + profile_transition(profile, bprm, buffer, + &cond, &unsafe)); + + AA_BUG(!new); + if (IS_ERR(new)) { + error = PTR_ERR(new); + goto done; + } else if (!new) { + error = -ENOMEM; + goto done; + } + + /* TODO: Add ns level no_new_privs subset test */ if (bprm->unsafe & LSM_UNSAFE_SHARE) { /* FIXME: currently don't mediate shared state */ ; } - if (bprm->unsafe & LSM_UNSAFE_PTRACE) { - error = may_change_ptraced_domain(new_profile); + if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) { + /* TODO: test needs to be profile of label to new */ + error = may_change_ptraced_domain(new, &info); if (error) goto audit; } - /* Determine if secure exec is needed. - * Can be at this point for the following reasons: - * 1. unconfined switching to confined - * 2. confined switching to different confinement - * 3. confined switching to unconfined - * - * Cases 2 and 3 are marked as requiring secure exec - * (unless policy specified "unsafe exec") - * - * bprm->unsafe is used to cache the AA_X_UNSAFE permission - * to avoid having to recompute in secureexec - */ - if (!(perms.xindex & AA_X_UNSAFE)) { - AA_DEBUG("scrubbing environment variables for %s profile=%s\n", - name, new_profile->base.hname); + if (unsafe) { + if (DEBUG_ON) { + dbg_printk("scrubbing environment variables for %s " + "label=", bprm->filename); + aa_label_printk(new, GFP_ATOMIC); + dbg_printk("\n"); + } bprm->unsafe |= AA_SECURE_X_NEEDED; } -apply: - /* when transitioning profiles clear unsafe personality bits */ - bprm->per_clear |= PER_CLEAR_ON_SETID; -x_clear: - aa_put_profile(ctx->profile); - /* transfer new profile reference will be released when ctx is freed */ - ctx->profile = new_profile; - new_profile = NULL; + if (label->proxy != new->proxy) { + /* when transitioning clear unsafe personality bits */ + if (DEBUG_ON) { + dbg_printk("apparmor: clearing unsafe personality " + "bits. %s label=", bprm->filename); + aa_label_printk(new, GFP_ATOMIC); + dbg_printk("\n"); + } + bprm->per_clear |= PER_CLEAR_ON_SETID; + } + aa_put_label(ctx->label); + /* transfer reference, released when ctx is freed */ + ctx->label = new; - /* clear out all temporary/transitional state from the context */ +done: + /* clear out temporary/transitional state from the context */ aa_clear_task_ctx_trans(ctx); -audit: - error = aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, - new_profile ? new_profile->base.hname : NULL, - cond.uid, info, error); - -cleanup: - aa_put_profile(new_profile); - aa_put_profile(profile); - kfree(buffer); + aa_put_label(label); + put_buffers(buffer); return error; + +audit: + error = fn_for_each(label, profile, + aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC, + bprm->filename, NULL, new, + file_inode(bprm->file)->i_uid, info, + error)); + aa_put_label(new); + goto done; } /** @@ -537,53 +860,157 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm) return 0; } -/** - * apparmor_bprm_committing_creds - do task cleanup on committing new creds - * @bprm: binprm for the exec (NOT NULL) +/* + * Functions for self directed profile change */ -void apparmor_bprm_committing_creds(struct linux_binprm *bprm) -{ - struct aa_profile *profile = __aa_current_profile(); - struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); - /* bail out if unconfined or not changing profile */ - if ((new_ctx->profile == profile) || - (unconfined(new_ctx->profile))) - return; - current->pdeath_signal = 0; - - /* reset soft limits and set hard limits for the new profile */ - __aa_transition_rlimits(profile, new_ctx->profile); -} - -/** - * apparmor_bprm_commited_cred - do cleanup after new creds committed - * @bprm: binprm for the exec (NOT NULL) +/* helper fn for change_hat + * + * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL */ -void apparmor_bprm_committed_creds(struct linux_binprm *bprm) +static struct aa_label *build_change_hat(struct aa_profile *profile, + const char *name, bool sibling) { - /* TODO: cleanup signals - ipc mediation */ - return; -} + struct aa_profile *root, *hat = NULL; + const char *info = NULL; + int error = 0; -/* - * Functions for self directed profile change - */ + if (sibling && PROFILE_IS_HAT(profile)) { + root = aa_get_profile_rcu(&profile->parent); + } else if (!sibling && !PROFILE_IS_HAT(profile)) { + root = aa_get_profile(profile); + } else { + info = "conflicting target types"; + error = -EPERM; + goto audit; + } -/** - * new_compound_name - create an hname with @n2 appended to @n1 - * @n1: base of hname (NOT NULL) - * @n2: name to append (NOT NULL) + hat = aa_find_child(root, name); + if (!hat) { + error = -ENOENT; + if (COMPLAIN_MODE(profile)) { + hat = aa_new_null_profile(profile, true, name, + GFP_KERNEL); + if (!hat) { + info = "failed null profile create"; + error = -ENOMEM; + } + } + } + aa_put_profile(root); + +audit: + aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, + name, hat ? hat->base.hname : NULL, + hat ? &hat->label : NULL, GLOBAL_ROOT_UID, NULL, + error); + if (!hat || (error && error != -ENOENT)) + return ERR_PTR(error); + /* if hat && error - complain mode, already audited and we adjust for + * complain mode allow by returning hat->label + */ + return &hat->label; +} + +/* helper fn for changing into a hat * - * Returns: new name or NULL on error + * Returns: label for hat transition or ERR_PTR. Does not return NULL */ -static char *new_compound_name(const char *n1, const char *n2) +static struct aa_label *change_hat(struct aa_label *label, const char *hats[], + int count, int flags) { - char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); - if (name) - sprintf(name, "%s//%s", n1, n2); - return name; + struct aa_profile *profile, *root, *hat = NULL; + struct aa_label *new; + struct label_it it; + bool sibling = false; + const char *name, *info = NULL; + int i, error; + + AA_BUG(!label); + AA_BUG(!hats); + AA_BUG(count < 1); + + if (PROFILE_IS_HAT(labels_profile(label))) + sibling = true; + + /*find first matching hat */ + for (i = 0; i < count && !hat; i++) { + name = hats[i]; + label_for_each_in_ns(it, labels_ns(label), label, profile) { + if (sibling && PROFILE_IS_HAT(profile)) { + root = aa_get_profile_rcu(&profile->parent); + } else if (!sibling && !PROFILE_IS_HAT(profile)) { + root = aa_get_profile(profile); + } else { /* conflicting change type */ + info = "conflicting targets types"; + error = -EPERM; + goto fail; + } + hat = aa_find_child(root, name); + aa_put_profile(root); + if (!hat) { + if (!COMPLAIN_MODE(profile)) + goto outer_continue; + /* complain mode succeed as if hat */ + } else if (!PROFILE_IS_HAT(hat)) { + info = "target not hat"; + error = -EPERM; + aa_put_profile(hat); + goto fail; + } + aa_put_profile(hat); + } + /* found a hat for all profiles in ns */ + goto build; +outer_continue: + ; + } + /* no hats that match, find appropriate error + * + * In complain mode audit of the failure is based off of the first + * hat supplied. This is done due how userspace interacts with + * change_hat. + */ + name = NULL; + label_for_each_in_ns(it, labels_ns(label), label, profile) { + if (!list_empty(&profile->base.profiles)) { + info = "hat not found"; + error = -ENOENT; + goto fail; + } + } + info = "no hats defined"; + error = -ECHILD; + +fail: + label_for_each_in_ns(it, labels_ns(label), label, profile) { + /* + * no target as it has failed to be found or built + * + * change_hat uses probing and should not log failures + * related to missing hats + */ + /* TODO: get rid of GLOBAL_ROOT_UID */ + if (count > 1 || COMPLAIN_MODE(profile)) { + aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, + AA_MAY_CHANGEHAT, name, NULL, NULL, + GLOBAL_ROOT_UID, info, error); + } + } + return ERR_PTR(error); + +build: + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + build_change_hat(profile, name, sibling), + aa_get_label(&profile->label)); + if (!new) { + info = "label build failed"; + error = -ENOMEM; + goto fail; + } /* else if (IS_ERR) build_change_hat has logged error so return new */ + + return new; } /** @@ -591,24 +1018,26 @@ static char *new_compound_name(const char *n1, const char *n2) * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0) * @count: number of hat names in @hats * @token: magic value to validate the hat change - * @permtest: true if this is just a permission test + * @flags: flags affecting behavior of the change + * + * Returns %0 on success, error otherwise. * * Change to the first profile specified in @hats that exists, and store * the @hat_magic in the current task context. If the count == 0 and the * @token matches that stored in the current task context, return to the * top level profile. * - * Returns %0 on success, error otherwise. + * change_hat only applies to profiles in the current ns, and each profile + * in the ns must make the same transition otherwise change_hat will fail. */ -int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) +int aa_change_hat(const char *hats[], int count, u64 token, int flags) { const struct cred *cred; struct aa_task_ctx *ctx; - struct aa_profile *profile, *previous_profile, *hat = NULL; - char *name = NULL; - int i; - struct file_perms perms = {}; - const char *target = NULL, *info = NULL; + struct aa_label *label, *previous, *new = NULL, *target = NULL; + struct aa_profile *profile; + struct aa_perms perms = {}; + const char *info = NULL; int error = 0; /* @@ -616,122 +1045,120 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) * There is no exception for unconfined as change_hat is not * available. */ - if (task_no_new_privs(current)) + if (task_no_new_privs(current)) { + /* not an apparmor denial per se, so don't log it */ + AA_DEBUG("no_new_privs - change_hat denied"); return -EPERM; + } /* released below */ cred = get_current_cred(); ctx = cred_ctx(cred); - profile = aa_get_newest_profile(aa_cred_profile(cred)); - previous_profile = aa_get_newest_profile(ctx->previous); + label = aa_get_newest_cred_label(cred); + previous = aa_get_newest_label(ctx->previous); - if (unconfined(profile)) { - info = "unconfined"; + if (unconfined(label)) { + info = "unconfined can not change_hat"; error = -EPERM; - goto audit; + goto fail; } if (count) { - /* attempting to change into a new hat or switch to a sibling */ - struct aa_profile *root; - if (PROFILE_IS_HAT(profile)) - root = aa_get_profile_rcu(&profile->parent); - else - root = aa_get_profile(profile); - - /* find first matching hat */ - for (i = 0; i < count && !hat; i++) - /* released below */ - hat = aa_find_child(root, hats[i]); - if (!hat) { - if (!COMPLAIN_MODE(root) || permtest) { - if (list_empty(&root->base.profiles)) - error = -ECHILD; - else - error = -ENOENT; - aa_put_profile(root); - goto out; - } - - /* - * In complain mode and failed to match any hats. - * Audit the failure is based off of the first hat - * supplied. This is done due how userspace - * interacts with change_hat. - * - * TODO: Add logging of all failed hats - */ - - /* freed below */ - name = new_compound_name(root->base.hname, hats[0]); - aa_put_profile(root); - target = name; - /* released below */ - hat = aa_new_null_profile(profile, true, hats[0], - GFP_KERNEL); - if (!hat) { - info = "failed null profile create"; - error = -ENOMEM; - goto audit; - } - } else { - aa_put_profile(root); - target = hat->base.hname; - if (!PROFILE_IS_HAT(hat)) { - info = "target not hat"; - error = -EPERM; - goto audit; - } + new = change_hat(label, hats, count, flags); + AA_BUG(!new); + if (IS_ERR(new)) { + error = PTR_ERR(new); + new = NULL; + /* already audited */ + goto out; } - error = may_change_ptraced_domain(hat); + error = may_change_ptraced_domain(new, &info); + if (error) + goto fail; + + if (flags & AA_CHANGE_TEST) + goto out; + + target = new; + error = aa_set_current_hat(new, token); + if (error == -EACCES) + /* kill task in case of brute force attacks */ + goto kill; + } else if (previous && !(flags & AA_CHANGE_TEST)) { + /* Return to saved label. Kill task if restore fails + * to avoid brute force attacks + */ + target = previous; + error = aa_restore_previous_label(token); if (error) { - info = "ptraced"; - error = -EPERM; - goto audit; - } - - if (!permtest) { - error = aa_set_current_hat(hat, token); if (error == -EACCES) - /* kill task in case of brute force attacks */ - perms.kill = AA_MAY_CHANGEHAT; - else if (name && !error) - /* reset error for learning of new hats */ - error = -ENOENT; + goto kill; + goto fail; } - } else if (previous_profile) { - /* Return to saved profile. Kill task if restore fails - * to avoid brute force attacks - */ - target = previous_profile->base.hname; - error = aa_restore_previous_profile(token); - perms.kill = AA_MAY_CHANGEHAT; - } else - /* ignore restores when there is no saved profile */ - goto out; - -audit: - if (!permtest) - error = aa_audit_file(profile, &perms, OP_CHANGE_HAT, - AA_MAY_CHANGEHAT, NULL, target, - GLOBAL_ROOT_UID, info, error); + } /* else ignore @flags && restores when there is no saved profile */ out: - aa_put_profile(hat); - kfree(name); - aa_put_profile(profile); - aa_put_profile(previous_profile); + aa_put_label(new); + aa_put_label(previous); + aa_put_label(label); put_cred(cred); return error; + +kill: + info = "failed token match"; + perms.kill = AA_MAY_CHANGEHAT; + +fail: + fn_for_each_in_ns(label, profile, + aa_audit_file(profile, &perms, OP_CHANGE_HAT, + AA_MAY_CHANGEHAT, NULL, NULL, target, + GLOBAL_ROOT_UID, info, error)); + + goto out; +} + + +static int change_profile_perms_wrapper(const char *op, const char *name, + struct aa_profile *profile, + struct aa_label *target, bool stack, + u32 request, struct aa_perms *perms) +{ + const char *info = NULL; + int error = 0; + + /* + * Fail explicitly requested domain transitions when no_new_privs + * and not unconfined OR the transition results in a stack on + * the current label. + * Stacking domain transitions and transitions from unconfined are + * allowed even when no_new_privs is set because this aways results + * in a reduction of permissions. + */ + if (task_no_new_privs(current) && !stack && + !profile_unconfined(profile) && + !aa_label_is_subset(target, &profile->label)) { + info = "no new privs"; + error = -EPERM; + } + + if (!error) + error = change_profile_perms(profile, target, stack, request, + profile->file.start, perms); + if (error) + error = aa_audit_file(profile, perms, op, request, name, + NULL, target, GLOBAL_ROOT_UID, info, + error); + + return error; } /** * aa_change_profile - perform a one-way profile transition * @fqname: name of profile may include namespace (NOT NULL) * @onexec: whether this transition is to take place immediately or at exec - * @permtest: true if this is just a permission test + * @flags: flags affecting change behavior * * Change to new profile @name. Unlike with hats, there is no way * to change back. If @name isn't specified the current profile name is @@ -741,14 +1168,16 @@ out: * * Returns %0 on success, error otherwise. */ -int aa_change_profile(const char *fqname, bool onexec, - bool permtest, bool stack) +int aa_change_profile(const char *fqname, int flags) { - const struct cred *cred; - struct aa_profile *profile, *target = NULL; - struct file_perms perms = {}; - const char *info = NULL, *op; + struct aa_label *label, *new = NULL, *target = NULL; + struct aa_profile *profile; + struct aa_perms perms = {}; + const char *info = NULL; + const char *auditname = fqname; /* retain leading & if stack */ + bool stack = flags & AA_CHANGE_STACK; int error = 0; + char *op; u32 request; if (!fqname || !*fqname) { @@ -756,74 +1185,118 @@ int aa_change_profile(const char *fqname, bool onexec, return -EINVAL; } - if (onexec) { + if (flags & AA_CHANGE_ONEXEC) { request = AA_MAY_ONEXEC; - op = OP_CHANGE_ONEXEC; + if (stack) + op = OP_STACK_ONEXEC; + else + op = OP_CHANGE_ONEXEC; } else { request = AA_MAY_CHANGE_PROFILE; - op = OP_CHANGE_PROFILE; + if (stack) + op = OP_STACK; + else + op = OP_CHANGE_PROFILE; } - cred = get_current_cred(); - profile = aa_cred_profile(cred); + label = aa_get_current_label(); - /* - * Fail explicitly requested domain transitions if no_new_privs - * and not unconfined. - * Domain transitions from unconfined are allowed even when - * no_new_privs is set because this aways results in a reduction - * of permissions. - */ - if (task_no_new_privs(current) && !unconfined(profile)) { - put_cred(cred); - return -EPERM; + if (*fqname == '&') { + stack = true; + /* don't have label_parse() do stacking */ + fqname++; } + target = aa_label_parse(label, fqname, GFP_KERNEL, true, false); + if (IS_ERR(target)) { + struct aa_profile *tprofile; - target = aa_fqlookupn_profile(profile, fqname, strlen(fqname)); - if (!target) { - info = "profile not found"; - error = -ENOENT; - if (permtest || !COMPLAIN_MODE(profile)) + info = "label not found"; + error = PTR_ERR(target); + target = NULL; + /* + * TODO: fixme using labels_profile is not right - do profile + * per complain profile + */ + if ((flags & AA_CHANGE_TEST) || + !COMPLAIN_MODE(labels_profile(label))) goto audit; /* released below */ - target = aa_new_null_profile(profile, false, fqname, - GFP_KERNEL); - if (!target) { + tprofile = aa_new_null_profile(labels_profile(label), false, + fqname, GFP_KERNEL); + if (!tprofile) { info = "failed null profile create"; error = -ENOMEM; goto audit; } + target = &tprofile->label; + goto check; } - perms = change_profile_perms(profile, target->ns, target->base.hname, - request, profile->file.start); - if (!(perms.allow & request)) { - error = -EACCES; - goto audit; - } + /* + * self directed transitions only apply to current policy ns + * TODO: currently requiring perms for stacking and straight change + * stacking doesn't strictly need this. Determine how much + * we want to loosen this restriction for stacking + * + * if (!stack) { + */ + error = fn_for_each_in_ns(label, profile, + change_profile_perms_wrapper(op, auditname, + profile, target, stack, + request, &perms)); + if (error) + /* auditing done in change_profile_perms_wrapper */ + goto out; + + /* } */ +check: /* check if tracing task is allowed to trace target domain */ - error = may_change_ptraced_domain(target); - if (error) { - info = "ptrace prevents transition"; + error = may_change_ptraced_domain(target, &info); + if (error && !fn_for_each_in_ns(label, profile, + COMPLAIN_MODE(profile))) goto audit; - } - if (permtest) - goto audit; + /* TODO: add permission check to allow this + * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) { + * info = "not a single threaded task"; + * error = -EACCES; + * goto audit; + * } + */ + if (flags & AA_CHANGE_TEST) + goto out; - if (onexec) - error = aa_set_current_onexec(target); - else - error = aa_replace_current_profile(target); + if (!(flags & AA_CHANGE_ONEXEC)) { + /* only transition profiles in the current ns */ + if (stack) + new = aa_label_merge(label, target, GFP_KERNEL); + else + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + aa_get_label(target), + aa_get_label(&profile->label)); + if (IS_ERR_OR_NULL(new)) { + info = "failed to build target label"; + error = PTR_ERR(new); + new = NULL; + perms.allow = 0; + goto audit; + } + error = aa_replace_current_label(new); + } else + /* full transition will be built in exec path */ + error = aa_set_current_onexec(target, stack); audit: - if (!permtest) - error = aa_audit_file(profile, &perms, op, request, NULL, - fqname, GLOBAL_ROOT_UID, info, error); + error = fn_for_each_in_ns(label, profile, + aa_audit_file(profile, &perms, op, request, auditname, + NULL, new ? new : target, + GLOBAL_ROOT_UID, info, error)); - aa_put_profile(target); - put_cred(cred); +out: + aa_put_label(new); + aa_put_label(target); + aa_put_label(label); return error; } diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 750564c3ab71..3382518b87fa 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -12,15 +12,30 @@ * License. */ +#include <linux/tty.h> +#include <linux/fdtable.h> +#include <linux/file.h> + #include "include/apparmor.h" #include "include/audit.h" +#include "include/context.h" #include "include/file.h" #include "include/match.h" #include "include/path.h" #include "include/policy.h" +#include "include/label.h" -struct file_perms nullperms; +static u32 map_mask_to_chr_mask(u32 mask) +{ + u32 m = mask & PERMS_CHRS_MASK; + if (mask & AA_MAY_GETATTR) + m |= MAY_READ; + if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN)) + m |= MAY_WRITE; + + return m; +} /** * audit_file_mask - convert mask to permission string @@ -31,29 +46,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask) { char str[10]; - char *m = str; - - if (mask & AA_EXEC_MMAP) - *m++ = 'm'; - if (mask & (MAY_READ | AA_MAY_META_READ)) - *m++ = 'r'; - if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD | - AA_MAY_CHOWN)) - *m++ = 'w'; - else if (mask & MAY_APPEND) - *m++ = 'a'; - if (mask & AA_MAY_CREATE) - *m++ = 'c'; - if (mask & AA_MAY_DELETE) - *m++ = 'd'; - if (mask & AA_MAY_LINK) - *m++ = 'l'; - if (mask & AA_MAY_LOCK) - *m++ = 'k'; - if (mask & MAY_EXEC) - *m++ = 'x'; - *m = '\0'; - + aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask)); audit_log_string(ab, str); } @@ -67,22 +60,26 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) struct common_audit_data *sa = va; kuid_t fsuid = current_fsuid(); - if (aad(sa)->fs.request & AA_AUDIT_FILE_MASK) { + if (aad(sa)->request & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " requested_mask="); - audit_file_mask(ab, aad(sa)->fs.request); + audit_file_mask(ab, aad(sa)->request); } - if (aad(sa)->fs.denied & AA_AUDIT_FILE_MASK) { + if (aad(sa)->denied & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " denied_mask="); - audit_file_mask(ab, aad(sa)->fs.denied); + audit_file_mask(ab, aad(sa)->denied); } - if (aad(sa)->fs.request & AA_AUDIT_FILE_MASK) { + if (aad(sa)->request & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " fsuid=%d", from_kuid(&init_user_ns, fsuid)); audit_log_format(ab, " ouid=%d", from_kuid(&init_user_ns, aad(sa)->fs.ouid)); } - if (aad(sa)->fs.target) { + if (aad(sa)->peer) { + audit_log_format(ab, " target="); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + FLAG_VIEW_SUBNS, GFP_ATOMIC); + } else if (aad(sa)->fs.target) { audit_log_format(ab, " target="); audit_log_untrustedstring(ab, aad(sa)->fs.target); } @@ -92,28 +89,30 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) * aa_audit_file - handle the auditing of file operations * @profile: the profile being enforced (NOT NULL) * @perms: the permissions computed for the request (NOT NULL) - * @gfp: allocation flags * @op: operation being mediated * @request: permissions requested * @name: name of object being mediated (MAYBE NULL) * @target: name of target (MAYBE NULL) + * @tlabel: target label (MAY BE NULL) * @ouid: object uid * @info: extra information message (MAYBE NULL) * @error: 0 if operation allowed else failure error code * * Returns: %0 or error on failure */ -int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, +int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, - const char *target, kuid_t ouid, const char *info, int error) + const char *target, struct aa_label *tlabel, + kuid_t ouid, const char *info, int error) { int type = AUDIT_APPARMOR_AUTO; DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op); sa.u.tsk = NULL; - aad(&sa)->fs.request = request; + aad(&sa)->request = request; aad(&sa)->name = name; aad(&sa)->fs.target = target; + aad(&sa)->peer = tlabel; aad(&sa)->fs.ouid = ouid; aad(&sa)->info = info; aad(&sa)->error = error; @@ -126,34 +125,67 @@ int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, mask = 0xffff; /* mask off perms that are not being force audited */ - aad(&sa)->fs.request &= mask; + aad(&sa)->request &= mask; - if (likely(!aad(&sa)->fs.request)) + if (likely(!aad(&sa)->request)) return 0; type = AUDIT_APPARMOR_AUDIT; } else { /* only report permissions that were denied */ - aad(&sa)->fs.request = aad(&sa)->fs.request & ~perms->allow; - AA_BUG(!aad(&sa)->fs.request); + aad(&sa)->request = aad(&sa)->request & ~perms->allow; + AA_BUG(!aad(&sa)->request); - if (aad(&sa)->fs.request & perms->kill) + if (aad(&sa)->request & perms->kill) type = AUDIT_APPARMOR_KILL; /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((aad(&sa)->fs.request & perms->quiet) && + if ((aad(&sa)->request & perms->quiet) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) - aad(&sa)->fs.request &= ~perms->quiet; + aad(&sa)->request &= ~perms->quiet; - if (!aad(&sa)->fs.request) - return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error; + if (!aad(&sa)->request) + return aad(&sa)->error; } - aad(&sa)->fs.denied = aad(&sa)->fs.request & ~perms->allow; + aad(&sa)->denied = aad(&sa)->request & ~perms->allow; return aa_audit(type, profile, &sa, file_audit_cb); } /** + * is_deleted - test if a file has been completely unlinked + * @dentry: dentry of file to test for deletion (NOT NULL) + * + * Returns: %1 if deleted else %0 + */ +static inline bool is_deleted(struct dentry *dentry) +{ + if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) + return 1; + return 0; +} + +static int path_name(const char *op, struct aa_label *label, + const struct path *path, int flags, char *buffer, + const char **name, struct path_cond *cond, u32 request) +{ + struct aa_profile *profile; + const char *info = NULL; + int error; + + error = aa_path_name(path, flags, buffer, name, &info, + labels_profile(label)->disconnected); + if (error) { + fn_for_each_confined(label, profile, + aa_audit_file(profile, &nullperms, op, request, *name, + NULL, NULL, cond->uid, info, error)); + return error; + } + + return 0; +} + +/** * map_old_perms - map old file perms layout to the new layout * @old: permission set in old mapping * @@ -163,10 +195,10 @@ static u32 map_old_perms(u32 old) { u32 new = old & 0xf; if (old & MAY_READ) - new |= AA_MAY_META_READ; + new |= AA_MAY_GETATTR | AA_MAY_OPEN; if (old & MAY_WRITE) - new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE | - AA_MAY_CHMOD | AA_MAY_CHOWN; + new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE | + AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN; if (old & 0x10) new |= AA_MAY_LINK; /* the old mapping lock and link_subset flags where overlaid @@ -181,7 +213,7 @@ static u32 map_old_perms(u32 old) } /** - * compute_perms - convert dfa compressed perms to internal perms + * aa_compute_fperms - convert dfa compressed perms to internal perms * @dfa: dfa to compute perms for (NOT NULL) * @state: state in dfa * @cond: conditions to consider (NOT NULL) @@ -191,17 +223,21 @@ static u32 map_old_perms(u32 old) * * Returns: computed permission set */ -static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, - struct path_cond *cond) +struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, + struct path_cond *cond) { - struct file_perms perms; + struct aa_perms perms; /* FIXME: change over to new dfa format * currently file perms are encoded in the dfa, new format * splits the permissions from the dfa. This mapping can be * done at profile load */ - perms.kill = 0; + perms.deny = 0; + perms.kill = perms.stop = 0; + perms.complain = perms.cond = 0; + perms.hide = 0; + perms.prompt = 0; if (uid_eq(current_fsuid(), cond->uid)) { perms.allow = map_old_perms(dfa_user_allow(dfa, state)); @@ -214,7 +250,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); perms.xindex = dfa_other_xindex(dfa, state); } - perms.allow |= AA_MAY_META_READ; + perms.allow |= AA_MAY_GETATTR; /* change_profile wasn't determined by ownership in old mapping */ if (ACCEPT_TABLE(dfa)[state] & 0x80000000) @@ -237,37 +273,55 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, */ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, const char *name, struct path_cond *cond, - struct file_perms *perms) + struct aa_perms *perms) { unsigned int state; - if (!dfa) { - *perms = nullperms; - return DFA_NOMATCH; - } - state = aa_dfa_match(dfa, start, name); - *perms = compute_perms(dfa, state, cond); + *perms = aa_compute_fperms(dfa, state, cond); return state; } -/** - * is_deleted - test if a file has been completely unlinked - * @dentry: dentry of file to test for deletion (NOT NULL) - * - * Returns: %1 if deleted else %0 - */ -static inline bool is_deleted(struct dentry *dentry) +int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, + u32 request, struct path_cond *cond, int flags, + struct aa_perms *perms) { - if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) - return 1; - return 0; + int e = 0; + + if (profile_unconfined(profile)) + return 0; + aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms); + if (request & ~perms->allow) + e = -EACCES; + return aa_audit_file(profile, perms, op, request, name, NULL, NULL, + cond->uid, NULL, e); +} + + +static int profile_path_perm(const char *op, struct aa_profile *profile, + const struct path *path, char *buffer, u32 request, + struct path_cond *cond, int flags, + struct aa_perms *perms) +{ + const char *name; + int error; + + if (profile_unconfined(profile)) + return 0; + + error = path_name(op, &profile->label, path, + flags | profile->path_flags, buffer, &name, cond, + request); + if (error) + return error; + return __aa_path_perm(op, profile, name, request, cond, flags, + perms); } /** * aa_path_perm - do permissions check & audit for @path * @op: operation being checked - * @profile: profile being enforced (NOT NULL) + * @label: profile being enforced (NOT NULL) * @path: path to check permissions of (NOT NULL) * @flags: any additional path flags beyond what the profile specifies * @request: requested permissions @@ -275,35 +329,23 @@ static inline bool is_deleted(struct dentry *dentry) * * Returns: %0 else error if access denied or other error */ -int aa_path_perm(const char *op, struct aa_profile *profile, +int aa_path_perm(const char *op, struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond) { + struct aa_perms perms = {}; + struct aa_profile *profile; char *buffer = NULL; - struct file_perms perms = {}; - const char *name, *info = NULL; int error; - flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); - error = aa_path_name(path, flags, &buffer, &name, &info); - if (error) { - if (error == -ENOENT && is_deleted(path->dentry)) { - /* Access to open files that are deleted are - * give a pass (implicit delegation) - */ - error = 0; - info = NULL; - perms.allow = request; - } - } else { - aa_str_perms(profile->file.dfa, profile->file.start, name, cond, - &perms); - if (request & ~perms.allow) - error = -EACCES; - } - error = aa_audit_file(profile, &perms, op, request, name, NULL, - cond->uid, info, error); - kfree(buffer); + flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : + 0); + get_buffers(buffer); + error = fn_for_each_confined(label, profile, + profile_path_perm(op, profile, path, buffer, request, + cond, flags, &perms)); + + put_buffers(buffer); return error; } @@ -328,65 +370,40 @@ static inline bool xindex_is_subset(u32 link, u32 target) return 1; } -/** - * aa_path_link - Handle hard link permission check - * @profile: the profile being enforced (NOT NULL) - * @old_dentry: the target dentry (NOT NULL) - * @new_dir: directory the new link will be created in (NOT NULL) - * @new_dentry: the link being created (NOT NULL) - * - * Handle the permission test for a link & target pair. Permission - * is encoded as a pair where the link permission is determined - * first, and if allowed, the target is tested. The target test - * is done from the point of the link match (not start of DFA) - * making the target permission dependent on the link permission match. - * - * The subset test if required forces that permissions granted - * on link are a subset of the permission granted to target. - * - * Returns: %0 if allowed else error - */ -int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, - const struct path *new_dir, struct dentry *new_dentry) +static int profile_path_link(struct aa_profile *profile, + const struct path *link, char *buffer, + const struct path *target, char *buffer2, + struct path_cond *cond) { - struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry }; - struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry }; - struct path_cond cond = { - d_backing_inode(old_dentry)->i_uid, - d_backing_inode(old_dentry)->i_mode - }; - char *buffer = NULL, *buffer2 = NULL; - const char *lname, *tname = NULL, *info = NULL; - struct file_perms lperms, perms; + const char *lname, *tname = NULL; + struct aa_perms lperms = {}, perms; + const char *info = NULL; u32 request = AA_MAY_LINK; unsigned int state; int error; - lperms = nullperms; - - /* buffer freed below, lname is pointer in buffer */ - error = aa_path_name(&link, profile->path_flags, &buffer, &lname, - &info); + error = path_name(OP_LINK, &profile->label, link, profile->path_flags, + buffer, &lname, cond, AA_MAY_LINK); if (error) goto audit; /* buffer2 freed below, tname is pointer in buffer2 */ - error = aa_path_name(&target, profile->path_flags, &buffer2, &tname, - &info); + error = path_name(OP_LINK, &profile->label, target, profile->path_flags, + buffer2, &tname, cond, AA_MAY_LINK); if (error) goto audit; error = -EACCES; /* aa_str_perms - handles the case of the dfa being NULL */ state = aa_str_perms(profile->file.dfa, profile->file.start, lname, - &cond, &lperms); + cond, &lperms); if (!(lperms.allow & AA_MAY_LINK)) goto audit; /* test to see if target can be paired with link */ state = aa_dfa_null_transition(profile->file.dfa, state); - aa_str_perms(profile->file.dfa, state, tname, &cond, &perms); + aa_str_perms(profile->file.dfa, state, tname, cond, &perms); /* force audit/quiet masks for link are stored in the second entry * in the link pair. @@ -397,6 +414,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, if (!(perms.allow & AA_MAY_LINK)) { info = "target restricted"; + lperms = perms; goto audit; } @@ -404,10 +422,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, if (!(perms.allow & AA_LINK_SUBSET)) goto done_tests; - /* Do link perm subset test requiring allowed permission on link are a - * subset of the allowed permissions on target. + /* Do link perm subset test requiring allowed permission on link are + * a subset of the allowed permissions on target. */ - aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond, + aa_str_perms(profile->file.dfa, profile->file.start, tname, cond, &perms); /* AA_MAY_LINK is not considered in the subset test */ @@ -429,10 +447,121 @@ done_tests: error = 0; audit: - error = aa_audit_file(profile, &lperms, OP_LINK, request, - lname, tname, cond.uid, info, error); - kfree(buffer); - kfree(buffer2); + return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname, + NULL, cond->uid, info, error); +} + +/** + * aa_path_link - Handle hard link permission check + * @label: the label being enforced (NOT NULL) + * @old_dentry: the target dentry (NOT NULL) + * @new_dir: directory the new link will be created in (NOT NULL) + * @new_dentry: the link being created (NOT NULL) + * + * Handle the permission test for a link & target pair. Permission + * is encoded as a pair where the link permission is determined + * first, and if allowed, the target is tested. The target test + * is done from the point of the link match (not start of DFA) + * making the target permission dependent on the link permission match. + * + * The subset test if required forces that permissions granted + * on link are a subset of the permission granted to target. + * + * Returns: %0 if allowed else error + */ +int aa_path_link(struct aa_label *label, struct dentry *old_dentry, + const struct path *new_dir, struct dentry *new_dentry) +{ + struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry }; + struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry }; + struct path_cond cond = { + d_backing_inode(old_dentry)->i_uid, + d_backing_inode(old_dentry)->i_mode + }; + char *buffer = NULL, *buffer2 = NULL; + struct aa_profile *profile; + int error; + + /* buffer freed below, lname is pointer in buffer */ + get_buffers(buffer, buffer2); + error = fn_for_each_confined(label, profile, + profile_path_link(profile, &link, buffer, &target, + buffer2, &cond)); + put_buffers(buffer, buffer2); + + return error; +} + +static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label, + u32 request) +{ + struct aa_label *l, *old; + + /* update caching of label on file_ctx */ + spin_lock(&fctx->lock); + old = rcu_dereference_protected(fctx->label, + spin_is_locked(&fctx->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(fctx->label, l); + aa_put_label(old); + } else + aa_put_label(l); + fctx->allow |= request; + } + spin_unlock(&fctx->lock); +} + +static int __file_path_perm(const char *op, struct aa_label *label, + struct aa_label *flabel, struct file *file, + u32 request, u32 denied) +{ + struct aa_profile *profile; + struct aa_perms perms = {}; + struct path_cond cond = { + .uid = file_inode(file)->i_uid, + .mode = file_inode(file)->i_mode + }; + char *buffer; + int flags, error; + + /* revalidation due to label out of date. No revocation at this time */ + if (!denied && aa_label_is_subset(flabel, label)) + /* TODO: check for revocation on stale profiles */ + return 0; + + flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); + get_buffers(buffer); + + /* check every profile in task label not in current cache */ + error = fn_for_each_not_in_set(flabel, label, profile, + profile_path_perm(op, profile, &file->f_path, buffer, + request, &cond, flags, &perms)); + if (denied && !error) { + /* + * check every profile in file label that was not tested + * in the initial check above. + * + * TODO: cache full perms so this only happens because of + * conditionals + * TODO: don't audit here + */ + if (label == flabel) + error = fn_for_each(label, profile, + profile_path_perm(op, profile, &file->f_path, + buffer, request, &cond, flags, + &perms)); + else + error = fn_for_each_not_in_set(label, flabel, profile, + profile_path_perm(op, profile, &file->f_path, + buffer, request, &cond, flags, + &perms)); + } + if (!error) + update_file_ctx(file_ctx(file), label, request); + + put_buffers(buffer); return error; } @@ -440,20 +569,114 @@ audit: /** * aa_file_perm - do permission revalidation check & audit for @file * @op: operation being checked - * @profile: profile being enforced (NOT NULL) + * @label: label being enforced (NOT NULL) * @file: file to revalidate access permissions on (NOT NULL) * @request: requested permissions * * Returns: %0 if access allowed else error */ -int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file, +int aa_file_perm(const char *op, struct aa_label *label, struct file *file, u32 request) { - struct path_cond cond = { - .uid = file_inode(file)->i_uid, - .mode = file_inode(file)->i_mode - }; + struct aa_file_ctx *fctx; + struct aa_label *flabel; + u32 denied; + int error = 0; + + AA_BUG(!label); + AA_BUG(!file); + + fctx = file_ctx(file); + + rcu_read_lock(); + flabel = rcu_dereference(fctx->label); + AA_BUG(!flabel); + + /* revalidate access, if task is unconfined, or the cached cred + * doesn't match or if the request is for more permissions than + * was granted. + * + * Note: the test for !unconfined(flabel) is to handle file + * delegation from unconfined tasks + */ + denied = request & ~fctx->allow; + if (unconfined(label) || unconfined(flabel) || + (!denied && aa_label_is_subset(flabel, label))) + goto done; + + /* TODO: label cross check */ + + if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) + error = __file_path_perm(op, label, flabel, file, request, + denied); + +done: + rcu_read_unlock(); + + return error; +} + +static void revalidate_tty(struct aa_label *label) +{ + struct tty_struct *tty; + int drop_tty = 0; + + tty = get_current_tty(); + if (!tty) + return; + + spin_lock(&tty->files_lock); + if (!list_empty(&tty->tty_files)) { + struct tty_file_private *file_priv; + struct file *file; + /* TODO: Revalidate access to controlling tty. */ + file_priv = list_first_entry(&tty->tty_files, + struct tty_file_private, list); + file = file_priv->file; + + if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE)) + drop_tty = 1; + } + spin_unlock(&tty->files_lock); + tty_kref_put(tty); - return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, - request, &cond); + if (drop_tty) + no_tty(); +} + +static int match_file(const void *p, struct file *file, unsigned int fd) +{ + struct aa_label *label = (struct aa_label *)p; + + if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file))) + return fd + 1; + return 0; +} + + +/* based on selinux's flush_unauthorized_files */ +void aa_inherit_files(const struct cred *cred, struct files_struct *files) +{ + struct aa_label *label = aa_get_newest_cred_label(cred); + struct file *devnull = NULL; + unsigned int n; + + revalidate_tty(label); + + /* Revalidate access to inherited open files. */ + n = iterate_fd(files, 0, match_file, label); + if (!n) /* none found? */ + goto out; + + devnull = dentry_open(&aa_null, O_RDWR, cred); + if (IS_ERR(devnull)) + devnull = NULL; + /* replace all the matching ones with this */ + do { + replace_fd(n - 1, devnull, 0); + } while ((n = iterate_fd(files, n, match_file, label)) != 0); + if (devnull) + fput(devnull); +out: + aa_put_label(label); } diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 1750cc0721c1..aaf893f4e4f5 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -4,7 +4,7 @@ * This file contains AppArmor basic global * * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * Copyright 2009-2017 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -27,8 +27,10 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_PTRACE 9 +#define AA_CLASS_LABEL 16 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_LABEL /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 120a798b5bb0..bd689114bf93 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,49 +17,49 @@ extern struct path aa_null; -enum aa_fs_type { - AA_FS_TYPE_BOOLEAN, - AA_FS_TYPE_STRING, - AA_FS_TYPE_U64, - AA_FS_TYPE_FOPS, - AA_FS_TYPE_DIR, +enum aa_sfs_type { + AA_SFS_TYPE_BOOLEAN, + AA_SFS_TYPE_STRING, + AA_SFS_TYPE_U64, + AA_SFS_TYPE_FOPS, + AA_SFS_TYPE_DIR, }; -struct aa_fs_entry; +struct aa_sfs_entry; -struct aa_fs_entry { +struct aa_sfs_entry { const char *name; struct dentry *dentry; umode_t mode; - enum aa_fs_type v_type; + enum aa_sfs_type v_type; union { bool boolean; char *string; unsigned long u64; - struct aa_fs_entry *files; + struct aa_sfs_entry *files; } v; const struct file_operations *file_ops; }; -extern const struct file_operations aa_fs_seq_file_ops; +extern const struct file_operations aa_sfs_seq_file_ops; -#define AA_FS_FILE_BOOLEAN(_name, _value) \ +#define AA_SFS_FILE_BOOLEAN(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_BOOLEAN, .v.boolean = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_STRING(_name, _value) \ + .v_type = AA_SFS_TYPE_BOOLEAN, .v.boolean = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_STRING(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_STRING, .v.string = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_U64(_name, _value) \ + .v_type = AA_SFS_TYPE_STRING, .v.string = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_U64(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_U64, .v.u64 = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_FOPS(_name, _mode, _fops) \ - { .name = (_name), .v_type = AA_FS_TYPE_FOPS, \ + .v_type = AA_SFS_TYPE_U64, .v.u64 = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_FOPS(_name, _mode, _fops) \ + { .name = (_name), .v_type = AA_SFS_TYPE_FOPS, \ .mode = (_mode), .file_ops = (_fops) } -#define AA_FS_DIR(_name, _value) \ - { .name = (_name), .v_type = AA_FS_TYPE_DIR, .v.files = (_value) } +#define AA_SFS_DIR(_name, _value) \ + { .name = (_name), .v_type = AA_SFS_TYPE_DIR, .v.files = (_value) } extern void __init aa_destroy_aafs(void); @@ -74,6 +74,7 @@ enum aafs_ns_type { AAFS_NS_LOAD, AAFS_NS_REPLACE, AAFS_NS_REMOVE, + AAFS_NS_REVISION, AAFS_NS_COUNT, AAFS_NS_MAX_COUNT, AAFS_NS_SIZE, @@ -102,16 +103,22 @@ enum aafs_prof_type { #define ns_subload(X) ((X)->dents[AAFS_NS_LOAD]) #define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE]) #define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE]) +#define ns_subrevision(X) ((X)->dents[AAFS_NS_REVISION]) #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) -void __aa_fs_profile_rmdir(struct aa_profile *profile); -void __aa_fs_profile_migrate_dents(struct aa_profile *old, +void __aa_bump_ns_revision(struct aa_ns *ns); +void __aafs_profile_rmdir(struct aa_profile *profile); +void __aafs_profile_migrate_dents(struct aa_profile *old, struct aa_profile *new); -int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); -void __aa_fs_ns_rmdir(struct aa_ns *ns); -int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, - const char *name); +int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); +void __aafs_ns_rmdir(struct aa_ns *ns); +int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, + struct dentry *dent); + +struct aa_loaddata; +void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata); +int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata); #endif /* __AA_APPARMORFS_H */ diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index fdc4774318ba..c68839a44351 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -22,8 +22,7 @@ #include <linux/slab.h> #include "file.h" - -struct aa_profile; +#include "label.h" extern const char *const audit_mode_names[]; #define AUDIT_MAX_INDEX 5 @@ -65,10 +64,12 @@ enum audit_type { #define OP_GETATTR "getattr" #define OP_OPEN "open" +#define OP_FRECEIVE "file_receive" #define OP_FPERM "file_perm" #define OP_FLOCK "file_lock" #define OP_FMMAP "file_mmap" #define OP_FMPROT "file_mprotect" +#define OP_INHERIT "file_inherit" #define OP_CREATE "create" #define OP_POST_CREATE "post_create" @@ -91,6 +92,8 @@ enum audit_type { #define OP_CHANGE_HAT "change_hat" #define OP_CHANGE_PROFILE "change_profile" #define OP_CHANGE_ONEXEC "change_onexec" +#define OP_STACK "stack" +#define OP_STACK_ONEXEC "stack_onexec" #define OP_SETPROCATTR "setprocattr" #define OP_SETRLIMIT "setrlimit" @@ -102,19 +105,19 @@ enum audit_type { struct apparmor_audit_data { int error; - const char *op; int type; - void *profile; + const char *op; + struct aa_label *label; const char *name; const char *info; + u32 request; + u32 denied; union { /* these entries require a custom callback fn */ struct { - struct aa_profile *peer; + struct aa_label *peer; struct { const char *target; - u32 request; - u32 denied; kuid_t ouid; } fs; }; diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index fc3fa381d850..e0304e2aeb7f 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -19,11 +19,12 @@ #include "apparmorfs.h" -struct aa_profile; +struct aa_label; /* aa_caps - confinement data for capabilities * @allowed: capabilities mask * @audit: caps that are to be audited + * @denied: caps that are explicitly denied * @quiet: caps that should not be audited * @kill: caps that when requested will result in the task being killed * @extended: caps that are subject finer grained mediation @@ -31,14 +32,15 @@ struct aa_profile; struct aa_caps { kernel_cap_t allow; kernel_cap_t audit; + kernel_cap_t denied; kernel_cap_t quiet; kernel_cap_t kill; kernel_cap_t extended; }; -extern struct aa_fs_entry aa_fs_entry_caps[]; +extern struct aa_sfs_entry aa_sfs_entry_caps[]; -int aa_capable(struct aa_profile *profile, int cap, int audit); +int aa_capable(struct aa_label *label, int cap, int audit); static inline void aa_free_cap_rules(struct aa_caps *caps) { diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h index 5b18fedab4c8..6ae07e9aaa17 100644 --- a/security/apparmor/include/context.h +++ b/security/apparmor/include/context.h @@ -19,60 +19,28 @@ #include <linux/slab.h> #include <linux/sched.h> -#include "policy.h" +#include "label.h" #include "policy_ns.h" #define cred_ctx(X) ((X)->security) #define current_ctx() cred_ctx(current_cred()) -/* struct aa_file_ctx - the AppArmor context the file was opened in - * @perms: the permission the file was opened with - * - * The file_ctx could currently be directly stored in file->f_security - * as the profile reference is now stored in the f_cred. However the - * ctx struct will expand in the future so we keep the struct. - */ -struct aa_file_ctx { - u16 allow; -}; - -/** - * aa_alloc_file_context - allocate file_ctx - * @gfp: gfp flags for allocation - * - * Returns: file_ctx or NULL on failure - */ -static inline struct aa_file_ctx *aa_alloc_file_context(gfp_t gfp) -{ - return kzalloc(sizeof(struct aa_file_ctx), gfp); -} - -/** - * aa_free_file_context - free a file_ctx - * @ctx: file_ctx to free (MAYBE_NULL) - */ -static inline void aa_free_file_context(struct aa_file_ctx *ctx) -{ - if (ctx) - kzfree(ctx); -} - /** * struct aa_task_ctx - primary label for confined tasks - * @profile: the current profile (NOT NULL) - * @exec: profile to transition to on next exec (MAYBE NULL) - * @previous: profile the task may return to (MAYBE NULL) - * @token: magic value the task must know for returning to @previous_profile + * @label: the current label (NOT NULL) + * @exec: label to transition to on next exec (MAYBE NULL) + * @previous: label the task may return to (MAYBE NULL) + * @token: magic value the task must know for returning to @previous * - * Contains the task's current profile (which could change due to + * Contains the task's current label (which could change due to * change_hat). Plus the hat_magic needed during change_hat. * * TODO: make so a task can be confined by a stack of contexts */ struct aa_task_ctx { - struct aa_profile *profile; - struct aa_profile *onexec; - struct aa_profile *previous; + struct aa_label *label; + struct aa_label *onexec; + struct aa_label *previous; u64 token; }; @@ -80,40 +48,51 @@ struct aa_task_ctx *aa_alloc_task_context(gfp_t flags); void aa_free_task_context(struct aa_task_ctx *ctx); void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old); -int aa_replace_current_profile(struct aa_profile *profile); -int aa_set_current_onexec(struct aa_profile *profile); -int aa_set_current_hat(struct aa_profile *profile, u64 token); -int aa_restore_previous_profile(u64 cookie); -struct aa_profile *aa_get_task_profile(struct task_struct *task); +int aa_replace_current_label(struct aa_label *label); +int aa_set_current_onexec(struct aa_label *label, bool stack); +int aa_set_current_hat(struct aa_label *label, u64 token); +int aa_restore_previous_label(u64 cookie); +struct aa_label *aa_get_task_label(struct task_struct *task); /** - * aa_cred_profile - obtain cred's profiles - * @cred: cred to obtain profiles from (NOT NULL) + * aa_cred_raw_label - obtain cred's label + * @cred: cred to obtain label from (NOT NULL) * - * Returns: confining profile + * Returns: confining label * * does NOT increment reference count */ -static inline struct aa_profile *aa_cred_profile(const struct cred *cred) +static inline struct aa_label *aa_cred_raw_label(const struct cred *cred) { struct aa_task_ctx *ctx = cred_ctx(cred); - AA_BUG(!ctx || !ctx->profile); - return ctx->profile; + AA_BUG(!ctx || !ctx->label); + return ctx->label; +} + +/** + * aa_get_newest_cred_label - obtain the newest label on a cred + * @cred: cred to obtain label from (NOT NULL) + * + * Returns: newest version of confining label + */ +static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred) +{ + return aa_get_newest_label(aa_cred_raw_label(cred)); } /** - * __aa_task_profile - retrieve another task's profile + * __aa_task_raw_label - retrieve another task's label * @task: task to query (NOT NULL) * - * Returns: @task's profile without incrementing its ref count + * Returns: @task's label without incrementing its ref count * * If @task != current needs to be called in RCU safe critical section */ -static inline struct aa_profile *__aa_task_profile(struct task_struct *task) +static inline struct aa_label *__aa_task_raw_label(struct task_struct *task) { - return aa_cred_profile(__task_cred(task)); + return aa_cred_raw_label(__task_cred(task)); } /** @@ -124,50 +103,114 @@ static inline struct aa_profile *__aa_task_profile(struct task_struct *task) */ static inline bool __aa_task_is_confined(struct task_struct *task) { - return !unconfined(__aa_task_profile(task)); + return !unconfined(__aa_task_raw_label(task)); } /** - * __aa_current_profile - find the current tasks confining profile + * aa_current_raw_label - find the current tasks confining label * - * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * Returns: up to date confining label or the ns unconfined label (NOT NULL) * * This fn will not update the tasks cred to the most up to date version - * of the profile so it is safe to call when inside of locks. + * of the label so it is safe to call when inside of locks. */ -static inline struct aa_profile *__aa_current_profile(void) +static inline struct aa_label *aa_current_raw_label(void) { - return aa_cred_profile(current_cred()); + return aa_cred_raw_label(current_cred()); } /** - * aa_current_profile - find the current tasks confining profile and do updates + * aa_get_current_label - get the newest version of the current tasks label + * + * Returns: newest version of confining label (NOT NULL) * - * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * This fn will not update the tasks cred, so it is safe inside of locks * - * This fn will update the tasks cred structure if the profile has been - * replaced. Not safe to call inside locks + * The returned reference must be put with aa_put_label() */ -static inline struct aa_profile *aa_current_profile(void) +static inline struct aa_label *aa_get_current_label(void) { - const struct aa_task_ctx *ctx = current_ctx(); - struct aa_profile *profile; + struct aa_label *l = aa_current_raw_label(); - AA_BUG(!ctx || !ctx->profile); + if (label_is_stale(l)) + return aa_get_newest_label(l); + return aa_get_label(l); +} + +#define __end_current_label_crit_section(X) end_current_label_crit_section(X) - if (profile_is_stale(ctx->profile)) { - profile = aa_get_newest_profile(ctx->profile); - aa_replace_current_profile(profile); - aa_put_profile(profile); - ctx = current_ctx(); +/** + * end_label_crit_section - put a reference found with begin_current_label.. + * @label: label reference to put + * + * Should only be used with a reference obtained with + * begin_current_label_crit_section and never used in situations where the + * task cred may be updated + */ +static inline void end_current_label_crit_section(struct aa_label *label) +{ + if (label != aa_current_raw_label()) + aa_put_label(label); +} + +/** + * __begin_current_label_crit_section - current's confining label + * + * Returns: up to date confining label or the ns unconfined label (NOT NULL) + * + * safe to call inside locks + * + * The returned reference must be put with __end_current_label_crit_section() + * This must NOT be used if the task cred could be updated within the + * critical section between __begin_current_label_crit_section() .. + * __end_current_label_crit_section() + */ +static inline struct aa_label *__begin_current_label_crit_section(void) +{ + struct aa_label *label = aa_current_raw_label(); + + if (label_is_stale(label)) + label = aa_get_newest_label(label); + + return label; +} + +/** + * begin_current_label_crit_section - current's confining label and update it + * + * Returns: up to date confining label or the ns unconfined label (NOT NULL) + * + * Not safe to call inside locks + * + * The returned reference must be put with end_current_label_crit_section() + * This must NOT be used if the task cred could be updated within the + * critical section between begin_current_label_crit_section() .. + * end_current_label_crit_section() + */ +static inline struct aa_label *begin_current_label_crit_section(void) +{ + struct aa_label *label = aa_current_raw_label(); + + if (label_is_stale(label)) { + label = aa_get_newest_label(label); + if (aa_replace_current_label(label) == 0) + /* task cred will keep the reference */ + aa_put_label(label); } - return ctx->profile; + return label; } static inline struct aa_ns *aa_get_current_ns(void) { - return aa_get_ns(__aa_current_profile()->ns); + struct aa_label *label; + struct aa_ns *ns; + + label = __begin_current_label_crit_section(); + ns = aa_get_ns(labels_ns(label)); + __end_current_label_crit_section(label); + + return ns; } /** @@ -176,8 +219,8 @@ static inline struct aa_ns *aa_get_current_ns(void) */ static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx) { - aa_put_profile(ctx->previous); - aa_put_profile(ctx->onexec); + aa_put_label(ctx->previous); + aa_put_label(ctx->onexec); ctx->previous = NULL; ctx->onexec = NULL; ctx->token = 0; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index 30544729878a..bab5810b6e9a 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,14 +23,17 @@ struct aa_domain { char **table; }; +#define AA_CHANGE_NOFLAGS 0 +#define AA_CHANGE_TEST 1 +#define AA_CHANGE_CHILD 2 +#define AA_CHANGE_ONEXEC 4 +#define AA_CHANGE_STACK 8 + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); -void apparmor_bprm_committing_creds(struct linux_binprm *bprm); -void apparmor_bprm_committed_creds(struct linux_binprm *bprm); void aa_free_domain_entries(struct aa_domain *domain); -int aa_change_hat(const char *hats[], int count, u64 token, bool permtest); -int aa_change_profile(const char *fqname, bool onexec, bool permtest, - bool stack); +int aa_change_hat(const char *hats[], int count, u64 token, int flags); +int aa_change_profile(const char *fqname, int flags); #endif /* __AA_DOMAIN_H */ diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 38f821bf49b6..001e40073ff9 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -15,38 +15,73 @@ #ifndef __AA_FILE_H #define __AA_FILE_H +#include <linux/spinlock.h> + #include "domain.h" #include "match.h" +#include "perms.h" struct aa_profile; struct path; -/* - * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags - * for profile permissions - */ -#define AA_MAY_CREATE 0x0010 -#define AA_MAY_DELETE 0x0020 -#define AA_MAY_META_WRITE 0x0040 -#define AA_MAY_META_READ 0x0080 - -#define AA_MAY_CHMOD 0x0100 -#define AA_MAY_CHOWN 0x0200 -#define AA_MAY_LOCK 0x0400 -#define AA_EXEC_MMAP 0x0800 - -#define AA_MAY_LINK 0x1000 -#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ -#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */ -#define AA_MAY_CHANGE_PROFILE 0x80000000 -#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */ +#define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND)) #define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\ AA_MAY_CREATE | AA_MAY_DELETE | \ - AA_MAY_META_READ | AA_MAY_META_WRITE | \ + AA_MAY_GETATTR | AA_MAY_SETATTR | \ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ AA_EXEC_MMAP | AA_MAY_LINK) +#define file_ctx(X) ((struct aa_file_ctx *)(X)->f_security) + +/* struct aa_file_ctx - the AppArmor context the file was opened in + * @lock: lock to update the ctx + * @label: label currently cached on the ctx + * @perms: the permission the file was opened with + */ +struct aa_file_ctx { + spinlock_t lock; + struct aa_label __rcu *label; + u32 allow; +}; + +/** + * aa_alloc_file_ctx - allocate file_ctx + * @label: initial label of task creating the file + * @gfp: gfp flags for allocation + * + * Returns: file_ctx or NULL on failure + */ +static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label, + gfp_t gfp) +{ + struct aa_file_ctx *ctx; + + ctx = kzalloc(sizeof(struct aa_file_ctx), gfp); + if (ctx) { + spin_lock_init(&ctx->lock); + rcu_assign_pointer(ctx->label, aa_get_label(label)); + } + return ctx; +} + +/** + * aa_free_file_ctx - free a file_ctx + * @ctx: file_ctx to free (MAYBE_NULL) + */ +static inline void aa_free_file_ctx(struct aa_file_ctx *ctx) +{ + if (ctx) { + aa_put_label(rcu_access_pointer(ctx->label)); + kzfree(ctx); + } +} + +static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx) +{ + return aa_get_label_rcu(&ctx->label); +} + /* * The xindex is broken into 3 parts * - index - an index into either the exec name table or the variable table @@ -75,25 +110,6 @@ struct path_cond { umode_t mode; }; -/* struct file_perms - file permission - * @allow: mask of permissions that are allowed - * @audit: mask of permissions to force an audit message for - * @quiet: mask of permissions to quiet audit messages for - * @kill: mask of permissions that when matched will kill the task - * @xindex: exec transition index if @allow contains MAY_EXEC - * - * The @audit and @queit mask should be mutually exclusive. - */ -struct file_perms { - u32 allow; - u32 audit; - u32 quiet; - u32 kill; - u16 xindex; -}; - -extern struct file_perms nullperms; - #define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) /* FIXME: split perms from dfa and match this to description @@ -144,9 +160,10 @@ static inline u16 dfa_map_xindex(u16 mask) #define dfa_other_xindex(dfa, state) \ dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) -int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, +int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, - const char *target, kuid_t ouid, const char *info, int error); + const char *target, struct aa_label *tlabel, kuid_t ouid, + const char *info, int error); /** * struct aa_file_rules - components used for file rule permissions @@ -167,20 +184,27 @@ struct aa_file_rules { /* TODO: add delegate table */ }; +struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, + struct path_cond *cond); unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, const char *name, struct path_cond *cond, - struct file_perms *perms); + struct aa_perms *perms); -int aa_path_perm(const char *op, struct aa_profile *profile, +int __aa_path_perm(const char *op, struct aa_profile *profile, + const char *name, u32 request, struct path_cond *cond, + int flags, struct aa_perms *perms); +int aa_path_perm(const char *op, struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond); -int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, +int aa_path_link(struct aa_label *label, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry); -int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file, +int aa_file_perm(const char *op, struct aa_label *label, struct file *file, u32 request); +void aa_inherit_files(const struct cred *cred, struct files_struct *files); + static inline void aa_free_file_rules(struct aa_file_rules *rules) { aa_put_dfa(rules->dfa); diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h index 288ca76e2fb1..656fdb81c8a0 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -4,7 +4,7 @@ * This file contains AppArmor ipc mediation function definitions. * * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * Copyright 2009-2017 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -19,10 +19,16 @@ struct aa_profile; -int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, - unsigned int mode); +#define AA_PTRACE_TRACE MAY_WRITE +#define AA_PTRACE_READ MAY_READ +#define AA_MAY_BE_TRACED AA_MAY_APPEND +#define AA_MAY_BE_READ AA_MAY_CREATE +#define PTRACE_PERM_SHIFT 2 -int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, - unsigned int mode); +#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \ + AA_MAY_BE_READ | AA_MAY_BE_TRACED) + +int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, + u32 request); #endif /* __AA_IPC_H */ diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h new file mode 100644 index 000000000000..9a283b722755 --- /dev/null +++ b/security/apparmor/include/label.h @@ -0,0 +1,441 @@ +/* + * AppArmor security module + * + * This file contains AppArmor label definitions + * + * Copyright 2017 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_LABEL_H +#define __AA_LABEL_H + +#include <linux/atomic.h> +#include <linux/audit.h> +#include <linux/rbtree.h> +#include <linux/rcupdate.h> + +#include "apparmor.h" +#include "lib.h" + +struct aa_ns; + +#define LOCAL_VEC_ENTRIES 8 +#define DEFINE_VEC(T, V) \ + struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \ + struct aa_ ## T **(V) + +#define vec_setup(T, V, N, GFP) \ +({ \ + if ((N) <= LOCAL_VEC_ENTRIES) { \ + typeof(N) i; \ + (V) = (_ ## V ## _localtmp); \ + for (i = 0; i < (N); i++) \ + (V)[i] = NULL; \ + } else \ + (V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \ + (V) ? 0 : -ENOMEM; \ +}) + +#define vec_cleanup(T, V, N) \ +do { \ + int i; \ + for (i = 0; i < (N); i++) { \ + if (!IS_ERR_OR_NULL((V)[i])) \ + aa_put_ ## T((V)[i]); \ + } \ + if ((V) != _ ## V ## _localtmp) \ + kfree(V); \ +} while (0) + +#define vec_last(VEC, SIZE) ((VEC)[(SIZE) - 1]) +#define vec_ns(VEC, SIZE) (vec_last((VEC), (SIZE))->ns) +#define vec_labelset(VEC, SIZE) (&vec_ns((VEC), (SIZE))->labels) +#define cleanup_domain_vec(V, L) cleanup_label_vec((V), (L)->size) + +struct aa_profile; +#define VEC_FLAG_TERMINATE 1 +int aa_vec_unique(struct aa_profile **vec, int n, int flags); +struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, + gfp_t gfp); +#define aa_sort_and_merge_vec(N, V) \ + aa_sort_and_merge_profiles((N), (struct aa_profile **)(V)) + + +/* struct aa_labelset - set of labels for a namespace + * + * Labels are reference counted; aa_labelset does not contribute to label + * reference counts. Once a label's last refcount is put it is removed from + * the set. + */ +struct aa_labelset { + rwlock_t lock; + + struct rb_root root; +}; + +#define __labelset_for_each(LS, N) \ + for ((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N)) + +void aa_labelset_destroy(struct aa_labelset *ls); +void aa_labelset_init(struct aa_labelset *ls); + + +enum label_flags { + FLAG_HAT = 1, /* profile is a hat */ + FLAG_UNCONFINED = 2, /* label unconfined only if all */ + FLAG_NULL = 4, /* profile is null learning profile */ + FLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ + FLAG_IMMUTIBLE = 0x10, /* don't allow changes/replacement */ + FLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ + FLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ + FLAG_NS_COUNT = 0x80, /* carries NS ref count */ + FLAG_IN_TREE = 0x100, /* label is in tree */ + FLAG_PROFILE = 0x200, /* label is a profile */ + FLAG_EXPLICIT = 0x400, /* explicit static label */ + FLAG_STALE = 0x800, /* replaced/removed */ + FLAG_RENAMED = 0x1000, /* label has renaming in it */ + FLAG_REVOKED = 0x2000, /* label has revocation in it */ + + /* These flags must correspond with PATH_flags */ + /* TODO: add new path flags */ +}; + +struct aa_label; +struct aa_proxy { + struct kref count; + struct aa_label __rcu *label; +}; + +struct label_it { + int i, j; +}; + +/* struct aa_label - lazy labeling struct + * @count: ref count of active users + * @node: rbtree position + * @rcu: rcu callback struct + * @proxy: is set to the label that replaced this label + * @hname: text representation of the label (MAYBE_NULL) + * @flags: stale and other flags - values may change under label set lock + * @secid: secid that references this label + * @size: number of entries in @ent[] + * @ent: set of profiles for label, actual size determined by @size + */ +struct aa_label { + struct kref count; + struct rb_node node; + struct rcu_head rcu; + struct aa_proxy *proxy; + __counted char *hname; + long flags; + u32 secid; + int size; + struct aa_profile *vec[]; +}; + +#define last_error(E, FN) \ +do { \ + int __subE = (FN); \ + if (__subE) \ + (E) = __subE; \ +} while (0) + +#define label_isprofile(X) ((X)->flags & FLAG_PROFILE) +#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED) +#define unconfined(X) label_unconfined(X) +#define label_is_stale(X) ((X)->flags & FLAG_STALE) +#define __label_make_stale(X) ((X)->flags |= FLAG_STALE) +#define labels_ns(X) (vec_ns(&((X)->vec[0]), (X)->size)) +#define labels_set(X) (&labels_ns(X)->labels) +#define labels_profile(X) ((X)->vec[(X)->size - 1]) + + +int aa_label_next_confined(struct aa_label *l, int i); + +/* for each profile in a label */ +#define label_for_each(I, L, P) \ + for ((I).i = 0; ((P) = (L)->vec[(I).i]); ++((I).i)) + +/* assumes break/goto ended label_for_each */ +#define label_for_each_cont(I, L, P) \ + for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i)) + +#define next_comb(I, L1, L2) \ +do { \ + (I).j++; \ + if ((I).j >= (L2)->size) { \ + (I).i++; \ + (I).j = 0; \ + } \ +} while (0) + + +/* for each combination of P1 in L1, and P2 in L2 */ +#define label_for_each_comb(I, L1, L2, P1, P2) \ +for ((I).i = (I).j = 0; \ + ((P1) = (L1)->vec[(I).i]) && ((P2) = (L2)->vec[(I).j]); \ + (I) = next_comb(I, L1, L2)) + +#define fn_for_each_comb(L1, L2, P1, P2, FN) \ +({ \ + struct label_it i; \ + int __E = 0; \ + label_for_each_comb(i, (L1), (L2), (P1), (P2)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + +/* for each profile that is enforcing confinement in a label */ +#define label_for_each_confined(I, L, P) \ + for ((I).i = aa_label_next_confined((L), 0); \ + ((P) = (L)->vec[(I).i]); \ + (I).i = aa_label_next_confined((L), (I).i + 1)) + +#define label_for_each_in_merge(I, A, B, P) \ + for ((I).i = (I).j = 0; \ + ((P) = aa_label_next_in_merge(&(I), (A), (B))); \ + ) + +#define label_for_each_not_in_set(I, SET, SUB, P) \ + for ((I).i = (I).j = 0; \ + ((P) = __aa_label_next_not_in_set(&(I), (SET), (SUB))); \ + ) + +#define next_in_ns(i, NS, L) \ +({ \ + typeof(i) ___i = (i); \ + while ((L)->vec[___i] && (L)->vec[___i]->ns != (NS)) \ + (___i)++; \ + (___i); \ +}) + +#define label_for_each_in_ns(I, NS, L, P) \ + for ((I).i = next_in_ns(0, (NS), (L)); \ + ((P) = (L)->vec[(I).i]); \ + (I).i = next_in_ns((I).i + 1, (NS), (L))) + +#define fn_for_each_in_ns(L, P, FN) \ +({ \ + struct label_it __i; \ + struct aa_ns *__ns = labels_ns(L); \ + int __E = 0; \ + label_for_each_in_ns(__i, __ns, (L), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + + +#define fn_for_each_XXX(L, P, FN, ...) \ +({ \ + struct label_it i; \ + int __E = 0; \ + label_for_each ## __VA_ARGS__(i, (L), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + +#define fn_for_each(L, P, FN) fn_for_each_XXX(L, P, FN) +#define fn_for_each_confined(L, P, FN) fn_for_each_XXX(L, P, FN, _confined) + +#define fn_for_each2_XXX(L1, L2, P, FN, ...) \ +({ \ + struct label_it i; \ + int __E = 0; \ + label_for_each ## __VA_ARGS__(i, (L1), (L2), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + +#define fn_for_each_in_merge(L1, L2, P, FN) \ + fn_for_each2_XXX((L1), (L2), P, FN, _in_merge) +#define fn_for_each_not_in_set(L1, L2, P, FN) \ + fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set) + +#define LABEL_MEDIATES(L, C) \ +({ \ + struct aa_profile *profile; \ + struct label_it i; \ + int ret = 0; \ + label_for_each(i, (L), profile) { \ + if (PROFILE_MEDIATES(profile, (C))) { \ + ret = 1; \ + break; \ + } \ + } \ + ret; \ +}) + + +void aa_labelset_destroy(struct aa_labelset *ls); +void aa_labelset_init(struct aa_labelset *ls); +void __aa_labelset_update_subtree(struct aa_ns *ns); + +void aa_label_free(struct aa_label *label); +void aa_label_kref(struct kref *kref); +bool aa_label_init(struct aa_label *label, int size); +struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp); + +bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub); +struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, + struct aa_label *set, + struct aa_label *sub); +bool aa_label_remove(struct aa_label *label); +struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l); +bool aa_label_replace(struct aa_label *old, struct aa_label *new); +bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old, + struct aa_label *new); + +struct aa_label *aa_label_find(struct aa_label *l); + +struct aa_profile *aa_label_next_in_merge(struct label_it *I, + struct aa_label *a, + struct aa_label *b); +struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b); +struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, + gfp_t gfp); + + +bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp); + +#define FLAGS_NONE 0 +#define FLAG_SHOW_MODE 1 +#define FLAG_VIEW_SUBNS 2 +#define FLAG_HIDDEN_UNCONFINED 4 +int aa_label_snxprint(char *str, size_t size, struct aa_ns *view, + struct aa_label *label, int flags); +int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, + int flags, gfp_t gfp); +int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp); +void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp); +void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp); +void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, + gfp_t gfp); +void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp); +void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp); +void aa_label_printk(struct aa_label *label, gfp_t gfp); + +struct aa_label *aa_label_parse(struct aa_label *base, const char *str, + gfp_t gfp, bool create, bool force_stack); + + +struct aa_perms; +int aa_label_match(struct aa_profile *profile, struct aa_label *label, + unsigned int state, bool subns, u32 request, + struct aa_perms *perms); + + +/** + * __aa_get_label - get a reference count to uncounted label reference + * @l: reference to get a count on + * + * Returns: pointer to reference OR NULL if race is lost and reference is + * being repeated. + * Requires: lock held, and the return code MUST be checked + */ +static inline struct aa_label *__aa_get_label(struct aa_label *l) +{ + if (l && kref_get_unless_zero(&l->count)) + return l; + + return NULL; +} + +static inline struct aa_label *aa_get_label(struct aa_label *l) +{ + if (l) + kref_get(&(l->count)); + + return l; +} + + +/** + * aa_get_label_rcu - increment refcount on a label that can be replaced + * @l: pointer to label that can be replaced (NOT NULL) + * + * Returns: pointer to a refcounted label. + * else NULL if no label + */ +static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l) +{ + struct aa_label *c; + + rcu_read_lock(); + do { + c = rcu_dereference(*l); + } while (c && !kref_get_unless_zero(&c->count)); + rcu_read_unlock(); + + return c; +} + +/** + * aa_get_newest_label - find the newest version of @l + * @l: the label to check for newer versions of + * + * Returns: refcounted newest version of @l taking into account + * replacement, renames and removals + * return @l. + */ +static inline struct aa_label *aa_get_newest_label(struct aa_label *l) +{ + if (!l) + return NULL; + + if (label_is_stale(l)) { + struct aa_label *tmp; + + AA_BUG(!l->proxy); + AA_BUG(!l->proxy->label); + /* BUG: only way this can happen is @l ref count and its + * replacement count have gone to 0 and are on their way + * to destruction. ie. we have a refcounting error + */ + tmp = aa_get_label_rcu(&l->proxy->label); + AA_BUG(!tmp); + + return tmp; + } + + return aa_get_label(l); +} + +static inline void aa_put_label(struct aa_label *l) +{ + if (l) + kref_put(&l->count, aa_label_kref); +} + + +struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp); +void aa_proxy_kref(struct kref *kref); + +static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy) +{ + if (proxy) + kref_get(&(proxy->count)); + + return proxy; +} + +static inline void aa_put_proxy(struct aa_proxy *proxy) +{ + if (proxy) + kref_put(&proxy->count, aa_proxy_kref); +} + +void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new); + +#endif /* __AA_LABEL_H */ diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 550a700563b4..436b3a722357 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -60,6 +60,7 @@ extern int apparmor_initialized; /* fn's in lib */ +const char *skipn_spaces(const char *str, size_t n); char *aa_split_fqname(char *args, char **ns_name); const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, size_t *ns_len); @@ -99,6 +100,36 @@ static inline bool path_mediated_fs(struct dentry *dentry) return !(dentry->d_sb->s_flags & MS_NOUSER); } + +struct counted_str { + struct kref count; + char name[]; +}; + +#define str_to_counted(str) \ + ((struct counted_str *)(str - offsetof(struct counted_str, name))) + +#define __counted /* atm just a notation */ + +void aa_str_kref(struct kref *kref); +char *aa_str_alloc(int size, gfp_t gfp); + + +static inline __counted char *aa_get_str(__counted char *str) +{ + if (str) + kref_get(&(str_to_counted(str)->count)); + + return str; +} + +static inline void aa_put_str(__counted char *str) +{ + if (str) + kref_put(&str_to_counted(str)->count, aa_str_kref); +} + + /* struct aa_policy - common part of both namespaces and profiles * @name: name of the object * @hname - The hierarchical name @@ -107,7 +138,7 @@ static inline bool path_mediated_fs(struct dentry *dentry) */ struct aa_policy { const char *name; - const char *hname; + __counted char *hname; struct list_head list; struct list_head profiles; }; @@ -180,4 +211,89 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix, const char *name, gfp_t gfp); void aa_policy_destroy(struct aa_policy *policy); -#endif /* AA_LIB_H */ + +/* + * fn_label_build - abstract out the build of a label transition + * @L: label the transition is being computed for + * @P: profile parameter derived from L by this macro, can be passed to FN + * @GFP: memory allocation type to use + * @FN: fn to call for each profile transition. @P is set to the profile + * + * Returns: new label on success + * ERR_PTR if build @FN fails + * NULL if label_build fails due to low memory conditions + * + * @FN must return a label or ERR_PTR on failure. NULL is not allowed + */ +#define fn_label_build(L, P, GFP, FN) \ +({ \ + __label__ __cleanup, __done; \ + struct aa_label *__new_; \ + \ + if ((L)->size > 1) { \ + /* TODO: add cache of transitions already done */ \ + struct label_it __i; \ + int __j, __k, __count; \ + DEFINE_VEC(label, __lvec); \ + DEFINE_VEC(profile, __pvec); \ + if (vec_setup(label, __lvec, (L)->size, (GFP))) { \ + __new_ = NULL; \ + goto __done; \ + } \ + __j = 0; \ + label_for_each(__i, (L), (P)) { \ + __new_ = (FN); \ + AA_BUG(!__new_); \ + if (IS_ERR(__new_)) \ + goto __cleanup; \ + __lvec[__j++] = __new_; \ + } \ + for (__j = __count = 0; __j < (L)->size; __j++) \ + __count += __lvec[__j]->size; \ + if (!vec_setup(profile, __pvec, __count, (GFP))) { \ + for (__j = __k = 0; __j < (L)->size; __j++) { \ + label_for_each(__i, __lvec[__j], (P)) \ + __pvec[__k++] = aa_get_profile(P); \ + } \ + __count -= aa_vec_unique(__pvec, __count, 0); \ + if (__count > 1) { \ + __new_ = aa_vec_find_or_create_label(__pvec,\ + __count, (GFP)); \ + /* only fails if out of Mem */ \ + if (!__new_) \ + __new_ = NULL; \ + } else \ + __new_ = aa_get_label(&__pvec[0]->label); \ + vec_cleanup(profile, __pvec, __count); \ + } else \ + __new_ = NULL; \ +__cleanup: \ + vec_cleanup(label, __lvec, (L)->size); \ + } else { \ + (P) = labels_profile(L); \ + __new_ = (FN); \ + } \ +__done: \ + if (!__new_) \ + AA_DEBUG("label build failed\n"); \ + (__new_); \ +}) + + +#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \ +({ \ + struct aa_label *__new; \ + if ((P)->ns != (NS)) \ + __new = (OTHER_FN); \ + else \ + __new = (NS_FN); \ + (__new); \ +}) + +#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \ +({ \ + fn_label_build((L), (P), (GFP), \ + __fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \ +}) + +#endif /* __AA_LIB_H */ diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 0444fdde3918..05fb3305671e 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -23,11 +23,12 @@ enum path_flags { PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */ PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */ - PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ + PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ }; -int aa_path_name(const struct path *path, int flags, char **buffer, - const char **name, const char **info); +int aa_path_name(const struct path *path, int flags, char *buffer, + const char **name, const char **info, + const char *disconnected); #define MAX_PATH_BUFFERS 2 diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h new file mode 100644 index 000000000000..2b27bb79aec4 --- /dev/null +++ b/security/apparmor/include/perms.h @@ -0,0 +1,155 @@ +/* + * AppArmor security module + * + * This file contains AppArmor basic permission sets definitions. + * + * Copyright 2017 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_PERM_H +#define __AA_PERM_H + +#include <linux/fs.h> +#include "label.h" + +#define AA_MAY_EXEC MAY_EXEC +#define AA_MAY_WRITE MAY_WRITE +#define AA_MAY_READ MAY_READ +#define AA_MAY_APPEND MAY_APPEND + +#define AA_MAY_CREATE 0x0010 +#define AA_MAY_DELETE 0x0020 +#define AA_MAY_OPEN 0x0040 +#define AA_MAY_RENAME 0x0080 /* pair */ + +#define AA_MAY_SETATTR 0x0100 /* meta write */ +#define AA_MAY_GETATTR 0x0200 /* meta read */ +#define AA_MAY_SETCRED 0x0400 /* security cred/attr */ +#define AA_MAY_GETCRED 0x0800 + +#define AA_MAY_CHMOD 0x1000 /* pair */ +#define AA_MAY_CHOWN 0x2000 /* pair */ +#define AA_MAY_CHGRP 0x4000 /* pair */ +#define AA_MAY_LOCK 0x8000 /* LINK_SUBSET overlaid */ + +#define AA_EXEC_MMAP 0x00010000 +#define AA_MAY_MPROT 0x00020000 /* extend conditions */ +#define AA_MAY_LINK 0x00040000 /* pair */ +#define AA_MAY_SNAPSHOT 0x00080000 /* pair */ + +#define AA_MAY_DELEGATE +#define AA_CONT_MATCH 0x08000000 + +#define AA_MAY_STACK 0x10000000 +#define AA_MAY_ONEXEC 0x20000000 /* either stack or change_profile */ +#define AA_MAY_CHANGE_PROFILE 0x40000000 +#define AA_MAY_CHANGEHAT 0x80000000 + +#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ + + +#define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \ + AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \ + AA_MAY_EXEC | AA_EXEC_MMAP | AA_MAY_APPEND) + +#define PERMS_NAMES_MASK (PERMS_CHRS_MASK | AA_MAY_OPEN | AA_MAY_RENAME | \ + AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_SETCRED | \ + AA_MAY_GETCRED | AA_MAY_CHMOD | AA_MAY_CHOWN | \ + AA_MAY_CHGRP | AA_MAY_MPROT | AA_MAY_SNAPSHOT | \ + AA_MAY_STACK | AA_MAY_ONEXEC | \ + AA_MAY_CHANGE_PROFILE | AA_MAY_CHANGEHAT) + +extern const char aa_file_perm_chrs[]; +extern const char *aa_file_perm_names[]; + +struct aa_perms { + u32 allow; + u32 audit; /* set only when allow is set */ + + u32 deny; /* explicit deny, or conflict if allow also set */ + u32 quiet; /* set only when ~allow | deny */ + u32 kill; /* set only when ~allow | deny */ + u32 stop; /* set only when ~allow | deny */ + + u32 complain; /* accumulates only used when ~allow & ~deny */ + u32 cond; /* set only when ~allow and ~deny */ + + u32 hide; /* set only when ~allow | deny */ + u32 prompt; /* accumulates only used when ~allow & ~deny */ + + /* Reserved: + * u32 subtree; / * set only when allow is set * / + */ + u16 xindex; +}; + +#define ALL_PERMS_MASK 0xffffffff +extern struct aa_perms nullperms; +extern struct aa_perms allperms; + + +#define xcheck(FN1, FN2) \ +({ \ + int e, error = FN1; \ + e = FN2; \ + if (e) \ + error = e; \ + error; \ +}) + + +/* + * TODO: update for labels pointing to labels instead of profiles + * TODO: optimize the walk, currently does subwalk of L2 for each P in L1 + * gah this doesn't allow for label compound check!!!! + */ +#define xcheck_ns_profile_profile(P1, P2, FN, args...) \ +({ \ + int ____e = 0; \ + if (P1->ns == P2->ns) \ + ____e = FN((P1), (P2), args); \ + (____e); \ +}) + +#define xcheck_ns_profile_label(P, L, FN, args...) \ +({ \ + struct aa_profile *__p2; \ + fn_for_each((L), __p2, \ + xcheck_ns_profile_profile((P), __p2, (FN), args)); \ +}) + +#define xcheck_ns_labels(L1, L2, FN, args...) \ +({ \ + struct aa_profile *__p1; \ + fn_for_each((L1), __p1, FN(__p1, (L2), args)); \ +}) + +/* Do the cross check but applying FN at the profiles level */ +#define xcheck_labels_profiles(L1, L2, FN, args...) \ + xcheck_ns_labels((L1), (L2), xcheck_ns_profile_label, (FN), args) + + +void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask); +void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask); +void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, + u32 chrsmask, const char **names, u32 namesmask); +void aa_apply_modes_to_perms(struct aa_profile *profile, + struct aa_perms *perms); +void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, + struct aa_perms *perms); +void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend); +void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend); +void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, + int type, u32 request, struct aa_perms *perms); +int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, + u32 request, int type, u32 *deny, + struct common_audit_data *sa); +int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, + u32 request, struct common_audit_data *sa, + void (*cb)(struct audit_buffer *, void *)); +#endif /* __AA_PERM_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 67bc96afe541..17fe41a9cac3 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -29,6 +29,8 @@ #include "domain.h" #include "file.h" #include "lib.h" +#include "label.h" +#include "perms.h" #include "resource.h" @@ -47,9 +49,9 @@ extern const char *const aa_profile_mode_names[]; #define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL) -#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT) +#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT) -#define profile_is_stale(_profile) ((_profile)->flags & PFLAG_STALE) +#define profile_is_stale(_profile) (label_is_stale(&(_profile)->label)) #define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) @@ -66,22 +68,6 @@ enum profile_mode { APPARMOR_UNCONFINED, /* profile set to unconfined */ }; -enum profile_flags { - PFLAG_HAT = 1, /* profile is a hat */ - PFLAG_NULL = 4, /* profile is null learning profile */ - PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ - PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */ - PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ - PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ - PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */ - PFLAG_STALE = 0x200, /* profile replaced/removed */ - PFLAG_NS_COUNT = 0x400, /* carries NS ref count */ - - /* These flags must correspond with PATH_flags */ - PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */ -}; - -struct aa_profile; /* struct aa_policydb - match engine for a policy * dfa: dfa pattern match @@ -94,11 +80,6 @@ struct aa_policydb { }; -struct aa_proxy { - struct kref count; - struct aa_profile __rcu *profile; -}; - /* struct aa_data - generic data structure * key: name for retrieving this data * size: size of data in bytes @@ -115,19 +96,17 @@ struct aa_data { /* struct aa_profile - basic confinement data * @base - base components of the profile (name, refcount, lists, lock ...) - * @count: reference count of the obj - * @rcu: rcu head used when removing from @list + * @label - label this profile is an extension of * @parent: parent of profile * @ns: namespace the profile is in - * @proxy: is set to the profile that replaced this profile * @rename: optional profile name that this profile renamed * @attach: human readable attachment string * @xmatch: optional extended matching for unconfined executables names * @xmatch_len: xmatch prefix len, used to determine xmatch priority * @audit: the auditing mode of the profile * @mode: the enforcement mode of the profile - * @flags: flags controlling profile behavior * @path_flags: flags controlling path generation behavior + * @disconnected: what to prepend if attach_disconnected is specified * @size: the memory consumed by this profiles rules * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions @@ -143,8 +122,6 @@ struct aa_data { * used to determine profile attachment against unconfined tasks. All other * attachments are determined by profile X transition rules. * - * The @proxy struct is write protected by the profile lock. - * * Profiles have a hierarchy where hats and children profiles keep * a reference to their parent. * @@ -154,12 +131,9 @@ struct aa_data { */ struct aa_profile { struct aa_policy base; - struct kref count; - struct rcu_head rcu; struct aa_profile __rcu *parent; struct aa_ns *ns; - struct aa_proxy *proxy; const char *rename; const char *attach; @@ -167,8 +141,8 @@ struct aa_profile { int xmatch_len; enum audit_mode audit; long mode; - long flags; u32 path_flags; + const char *disconnected; int size; struct aa_policydb policy; @@ -181,17 +155,24 @@ struct aa_profile { char *dirname; struct dentry *dents[AAFS_PROF_SIZEOF]; struct rhashtable *data; + struct aa_label label; }; extern enum profile_mode aa_g_profile_mode; -void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new); +#define AA_MAY_LOAD_POLICY AA_MAY_APPEND +#define AA_MAY_REPLACE_POLICY AA_MAY_WRITE +#define AA_MAY_REMOVE_POLICY AA_MAY_DELETE + +#define profiles_ns(P) ((P)->ns) +#define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname) void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); void aa_free_proxy_kref(struct kref *kref); -struct aa_profile *aa_alloc_profile(const char *name, gfp_t gfp); +struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy, + gfp_t gfp); struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat, const char *base, gfp_t gfp); void aa_free_profile(struct aa_profile *profile); @@ -200,21 +181,44 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname, size_t n); struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name); -struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, +struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n); struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); -ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, - bool noreplace, struct aa_loaddata *udata); -ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *profile, - char *name, size_t size); +ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, + u32 mask, struct aa_loaddata *udata); +ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label, + char *name, size_t size); void __aa_profile_list_release(struct list_head *head); #define PROF_ADD 1 #define PROF_REPLACE 0 -#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) +#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) + +/** + * aa_get_newest_profile - simple wrapper fn to wrap the label version + * @p: profile (NOT NULL) + * + * Returns refcount to newest version of the profile (maybe @p) + * + * Requires: @p must be held with a valid refcount + */ +static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) +{ + return labels_profile(aa_get_newest_label(&p->label)); +} +#define PROFILE_MEDIATES(P, T) ((P)->policy.start[(T)]) +/* safe version of POLICY_MEDIATES for full range input */ +static inline unsigned int PROFILE_MEDIATES_SAFE(struct aa_profile *profile, + unsigned char class) +{ + if (profile->policy.dfa) + return aa_dfa_match_len(profile->policy.dfa, + profile->policy.start[0], &class, 1); + return 0; +} /** * aa_get_profile - increment refcount on profile @p @@ -226,7 +230,7 @@ void __aa_profile_list_release(struct list_head *head); static inline struct aa_profile *aa_get_profile(struct aa_profile *p) { if (p) - kref_get(&(p->count)); + kref_get(&(p->label.count)); return p; } @@ -240,7 +244,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p) */ static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p) { - if (p && kref_get_unless_zero(&p->count)) + if (p && kref_get_unless_zero(&p->label.count)) return p; return NULL; @@ -260,53 +264,20 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) rcu_read_lock(); do { c = rcu_dereference(*p); - } while (c && !kref_get_unless_zero(&c->count)); + } while (c && !kref_get_unless_zero(&c->label.count)); rcu_read_unlock(); return c; } /** - * aa_get_newest_profile - find the newest version of @profile - * @profile: the profile to check for newer versions of - * - * Returns: refcounted newest version of @profile taking into account - * replacement, renames and removals - * return @profile. - */ -static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) -{ - if (!p) - return NULL; - - if (profile_is_stale(p)) - return aa_get_profile_rcu(&p->proxy->profile); - - return aa_get_profile(p); -} - -/** * aa_put_profile - decrement refcount on profile @p * @p: profile (MAYBE NULL) */ static inline void aa_put_profile(struct aa_profile *p) { if (p) - kref_put(&p->count, aa_free_profile_kref); -} - -static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *p) -{ - if (p) - kref_get(&(p->count)); - - return p; -} - -static inline void aa_put_proxy(struct aa_proxy *p) -{ - if (p) - kref_put(&p->count, aa_free_proxy_kref); + kref_put(&p->label.count, aa_label_kref); } static inline int AUDIT_MODE(struct aa_profile *profile) @@ -319,7 +290,7 @@ static inline int AUDIT_MODE(struct aa_profile *profile) bool policy_view_capable(struct aa_ns *ns); bool policy_admin_capable(struct aa_ns *ns); -int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns, - const char *op); +int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, + u32 mask); #endif /* __AA_POLICY_H */ diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index 89cffddd7e75..9605f18624e2 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -19,6 +19,7 @@ #include "apparmor.h" #include "apparmorfs.h" +#include "label.h" #include "policy.h" @@ -68,6 +69,11 @@ struct aa_ns { atomic_t uniq_null; long uniq_id; int level; + long revision; + wait_queue_head_t wait; + + struct aa_labelset labels; + struct list_head rawdata_list; struct dentry *dents[AAFS_NS_SIZEOF]; }; @@ -76,6 +82,8 @@ extern struct aa_ns *root_ns; extern const char *aa_hidden_ns_name; +#define ns_unconfined(NS) (&(NS)->unconfined->label) + bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns); const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns); void aa_free_ns(struct aa_ns *ns); @@ -85,6 +93,8 @@ void aa_free_ns_kref(struct kref *kref); struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name); struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n); +struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n); +struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n); struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name, struct dentry *dir); struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name); @@ -144,4 +154,15 @@ static inline struct aa_ns *__aa_find_ns(struct list_head *head, return __aa_findn_ns(head, name, strlen(name)); } +static inline struct aa_ns *__aa_lookup_ns(struct aa_ns *base, + const char *hname) +{ + return __aa_lookupn_ns(base, hname, strlen(hname)); +} + +static inline struct aa_ns *aa_lookup_ns(struct aa_ns *view, const char *name) +{ + return aa_lookupn_ns(view, name, strlen(name)); +} + #endif /* AA_NAMESPACE_H */ diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index 4c1319eebc42..be6cd69ac319 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -17,6 +17,8 @@ #include <linux/list.h> #include <linux/kref.h> +#include <linux/dcache.h> +#include <linux/workqueue.h> struct aa_load_ent { struct list_head list; @@ -36,26 +38,84 @@ struct aa_load_ent *aa_load_ent_alloc(void); #define PACKED_MODE_KILL 2 #define PACKED_MODE_UNCONFINED 3 -/* struct aa_loaddata - buffer of policy load data set */ +struct aa_ns; + +enum { + AAFS_LOADDATA_ABI = 0, + AAFS_LOADDATA_REVISION, + AAFS_LOADDATA_HASH, + AAFS_LOADDATA_DATA, + AAFS_LOADDATA_DIR, /* must be last actual entry */ + AAFS_LOADDATA_NDENTS /* count of entries */ +}; + +/* + * struct aa_loaddata - buffer of policy raw_data set + * + * there is no loaddata ref for being on ns list, nor a ref from + * d_inode(@dentry) when grab a ref from these, @ns->lock must be held + * && __aa_get_loaddata() needs to be used, and the return value + * checked, if NULL the loaddata is already being reaped and should be + * considered dead. + */ struct aa_loaddata { struct kref count; + struct list_head list; + struct work_struct work; + struct dentry *dents[AAFS_LOADDATA_NDENTS]; + struct aa_ns *ns; + char *name; size_t size; + long revision; /* the ns policy revision this caused */ int abi; unsigned char *hash; + char data[]; }; int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); +/** + * __aa_get_loaddata - get a reference count to uncounted data reference + * @data: reference to get a count on + * + * Returns: pointer to reference OR NULL if race is lost and reference is + * being repeated. + * Requires: @data->ns->lock held, and the return code MUST be checked + * + * Use only from inode->i_private and @data->list found references + */ +static inline struct aa_loaddata * +__aa_get_loaddata(struct aa_loaddata *data) +{ + if (data && kref_get_unless_zero(&(data->count))) + return data; + + return NULL; +} + +/** + * aa_get_loaddata - get a reference count from a counted data reference + * @data: reference to get a count on + * + * Returns: point to reference + * Requires: @data to have a valid reference count on it. It is a bug + * if the race to reap can be encountered when it is used. + */ static inline struct aa_loaddata * aa_get_loaddata(struct aa_loaddata *data) { - if (data) - kref_get(&(data->count)); - return data; + struct aa_loaddata *tmp = __aa_get_loaddata(data); + + AA_BUG(data && !tmp); + + return tmp; } +void __aa_loaddata_update(struct aa_loaddata *data, long revision); +bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r); void aa_loaddata_kref(struct kref *kref); +struct aa_loaddata *aa_loaddata_alloc(size_t size); static inline void aa_put_loaddata(struct aa_loaddata *data) { if (data) diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h index 6bd5f33d9533..c8fd99c9357d 100644 --- a/security/apparmor/include/procattr.h +++ b/security/apparmor/include/procattr.h @@ -15,11 +15,7 @@ #ifndef __AA_PROCATTR_H #define __AA_PROCATTR_H -#define AA_DO_TEST 1 -#define AA_ONEXEC 1 - -int aa_getprocattr(struct aa_profile *profile, char **string); -int aa_setprocattr_changehat(char *args, size_t size, int test); -int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test); +int aa_getprocattr(struct aa_label *label, char **string); +int aa_setprocattr_changehat(char *args, size_t size, int flags); #endif /* __AA_PROCATTR_H */ diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h index d3f4cf027957..76f1586c9adb 100644 --- a/security/apparmor/include/resource.h +++ b/security/apparmor/include/resource.h @@ -34,13 +34,13 @@ struct aa_rlimit { struct rlimit limits[RLIM_NLIMITS]; }; -extern struct aa_fs_entry aa_fs_entry_rlimit[]; +extern struct aa_sfs_entry aa_sfs_entry_rlimit[]; int aa_map_resource(int resource); -int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *, +int aa_task_setrlimit(struct aa_label *label, struct task_struct *task, unsigned int resource, struct rlimit *new_rlim); -void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new); +void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new); static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims) { diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index edac790923c3..11e66b5bbc42 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -4,7 +4,7 @@ * This file contains AppArmor ipc mediation * * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * Copyright 2009-2017 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -21,87 +21,103 @@ #include "include/policy.h" #include "include/ipc.h" +/** + * audit_ptrace_mask - convert mask to permission string + * @buffer: buffer to write string to (NOT NULL) + * @mask: permission mask to convert + */ +static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask) +{ + switch (mask) { + case MAY_READ: + audit_log_string(ab, "read"); + break; + case MAY_WRITE: + audit_log_string(ab, "trace"); + break; + case AA_MAY_BE_READ: + audit_log_string(ab, "readby"); + break; + case AA_MAY_BE_TRACED: + audit_log_string(ab, "tracedby"); + break; + } +} + /* call back to audit ptrace fields */ -static void audit_cb(struct audit_buffer *ab, void *va) +static void audit_ptrace_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; + + if (aad(sa)->request & AA_PTRACE_PERM_MASK) { + audit_log_format(ab, " requested_mask="); + audit_ptrace_mask(ab, aad(sa)->request); + + if (aad(sa)->denied & AA_PTRACE_PERM_MASK) { + audit_log_format(ab, " denied_mask="); + audit_ptrace_mask(ab, aad(sa)->denied); + } + } audit_log_format(ab, " peer="); - audit_log_untrustedstring(ab, aad(sa)->peer->base.hname); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + FLAGS_NONE, GFP_ATOMIC); } -/** - * aa_audit_ptrace - do auditing for ptrace - * @profile: profile being enforced (NOT NULL) - * @target: profile being traced (NOT NULL) - * @error: error condition - * - * Returns: %0 or error code - */ -static int aa_audit_ptrace(struct aa_profile *profile, - struct aa_profile *target, int error) +/* TODO: conditionals */ +static int profile_ptrace_perm(struct aa_profile *profile, + struct aa_profile *peer, u32 request, + struct common_audit_data *sa) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE); + struct aa_perms perms = { }; - aad(&sa)->peer = target; - aad(&sa)->error = error; + /* need because of peer in cross check */ + if (profile_unconfined(profile) || + !PROFILE_MEDIATES(profile, AA_CLASS_PTRACE)) + return 0; - return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb); + aad(sa)->peer = &peer->label; + aa_profile_match_label(profile, &peer->label, AA_CLASS_PTRACE, request, + &perms); + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb); } -/** - * aa_may_ptrace - test if tracer task can trace the tracee - * @tracer: profile of the task doing the tracing (NOT NULL) - * @tracee: task to be traced - * @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH - * - * Returns: %0 else error code if permission denied or error - */ -int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, - unsigned int mode) +static int cross_ptrace_perm(struct aa_profile *tracer, + struct aa_profile *tracee, u32 request, + struct common_audit_data *sa) { - /* TODO: currently only based on capability, not extended ptrace - * rules, - * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH - */ - - if (unconfined(tracer) || tracer == tracee) + if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE)) + return xcheck(profile_ptrace_perm(tracer, tracee, request, sa), + profile_ptrace_perm(tracee, tracer, + request << PTRACE_PERM_SHIFT, + sa)); + /* policy uses the old style capability check for ptrace */ + if (profile_unconfined(tracer) || tracer == tracee) return 0; - /* log this capability request */ - return aa_capable(tracer, CAP_SYS_PTRACE, 1); + + aad(sa)->label = &tracer->label; + aad(sa)->peer = &tracee->label; + aad(sa)->request = 0; + aad(sa)->error = aa_capable(&tracer->label, CAP_SYS_PTRACE, 1); + + return aa_audit(AUDIT_APPARMOR_AUTO, tracer, sa, audit_ptrace_cb); } /** - * aa_ptrace - do ptrace permission check and auditing - * @tracer: task doing the tracing (NOT NULL) - * @tracee: task being traced (NOT NULL) - * @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH + * aa_may_ptrace - test if tracer task can trace the tracee + * @tracer: label of the task doing the tracing (NOT NULL) + * @tracee: task label to be traced + * @request: permission request * * Returns: %0 else error code if permission denied or error */ -int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, - unsigned int mode) +int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, + u32 request) { - /* - * tracer can ptrace tracee when - * - tracer is unconfined || - * - tracer is in complain mode - * - tracer has rules allowing it to trace tracee currently this is: - * - confined by the same profile || - * - tracer profile has CAP_SYS_PTRACE - */ - - struct aa_profile *tracer_p = aa_get_task_profile(tracer); - int error = 0; - - if (!unconfined(tracer_p)) { - struct aa_profile *tracee_p = aa_get_task_profile(tracee); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE); - error = aa_may_ptrace(tracer_p, tracee_p, mode); - error = aa_audit_ptrace(tracer_p, tracee_p, error); + return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm, + request, &sa); +} - aa_put_profile(tracee_p); - } - aa_put_profile(tracer_p); - return error; -} diff --git a/security/apparmor/label.c b/security/apparmor/label.c new file mode 100644 index 000000000000..e052eaba1cf6 --- /dev/null +++ b/security/apparmor/label.c @@ -0,0 +1,2120 @@ +/* + * AppArmor security module + * + * This file contains AppArmor label definitions + * + * Copyright 2017 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include <linux/audit.h> +#include <linux/seq_file.h> +#include <linux/sort.h> + +#include "include/apparmor.h" +#include "include/context.h" +#include "include/label.h" +#include "include/policy.h" +#include "include/secid.h" + + +/* + * the aa_label represents the set of profiles confining an object + * + * Labels maintain a reference count to the set of pointers they reference + * Labels are ref counted by + * tasks and object via the security field/security context off the field + * code - will take a ref count on a label if it needs the label + * beyond what is possible with an rcu_read_lock. + * profiles - each profile is a label + * secids - a pinned secid will keep a refcount of the label it is + * referencing + * objects - inode, files, sockets, ... + * + * Labels are not ref counted by the label set, so they maybe removed and + * freed when no longer in use. + * + */ + +#define PROXY_POISON 97 +#define LABEL_POISON 100 + +static void free_proxy(struct aa_proxy *proxy) +{ + if (proxy) { + /* p->label will not updated any more as p is dead */ + aa_put_label(rcu_dereference_protected(proxy->label, true)); + memset(proxy, 0, sizeof(*proxy)); + proxy->label = (struct aa_label *) PROXY_POISON; + kfree(proxy); + } +} + +void aa_proxy_kref(struct kref *kref) +{ + struct aa_proxy *proxy = container_of(kref, struct aa_proxy, count); + + free_proxy(proxy); +} + +struct aa_proxy *aa_alloc_proxy(struct aa_label *label, gfp_t gfp) +{ + struct aa_proxy *new; + + new = kzalloc(sizeof(struct aa_proxy), gfp); + if (new) { + kref_init(&new->count); + rcu_assign_pointer(new->label, aa_get_label(label)); + } + return new; +} + +/* requires profile list write lock held */ +void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new) +{ + struct aa_label *tmp; + + AA_BUG(!orig); + AA_BUG(!new); + AA_BUG(!write_is_locked(&labels_set(orig)->lock)); + + tmp = rcu_dereference_protected(orig->proxy->label, + &labels_ns(orig)->lock); + rcu_assign_pointer(orig->proxy->label, aa_get_label(new)); + orig->flags |= FLAG_STALE; + aa_put_label(tmp); +} + +static void __proxy_share(struct aa_label *old, struct aa_label *new) +{ + struct aa_proxy *proxy = new->proxy; + + new->proxy = aa_get_proxy(old->proxy); + __aa_proxy_redirect(old, new); + aa_put_proxy(proxy); +} + + +/** + * ns_cmp - compare ns for label set ordering + * @a: ns to compare (NOT NULL) + * @b: ns to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int ns_cmp(struct aa_ns *a, struct aa_ns *b) +{ + int res; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!a->base.hname); + AA_BUG(!b->base.hname); + + if (a == b) + return 0; + + res = a->level - b->level; + if (res) + return res; + + return strcmp(a->base.hname, b->base.hname); +} + +/** + * profile_cmp - profile comparision for set ordering + * @a: profile to compare (NOT NULL) + * @b: profile to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int profile_cmp(struct aa_profile *a, struct aa_profile *b) +{ + int res; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!a->ns); + AA_BUG(!b->ns); + AA_BUG(!a->base.hname); + AA_BUG(!b->base.hname); + + if (a == b || a->base.hname == b->base.hname) + return 0; + res = ns_cmp(a->ns, b->ns); + if (res) + return res; + + return strcmp(a->base.hname, b->base.hname); +} + +/** + * vec_cmp - label comparision for set ordering + * @a: label to compare (NOT NULL) + * @vec: vector of profiles to compare (NOT NULL) + * @n: length of @vec + * + * Returns: <0 if a < vec + * ==0 if a == vec + * >0 if a > vec + */ +static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn) +{ + int i; + + AA_BUG(!a); + AA_BUG(!*a); + AA_BUG(!b); + AA_BUG(!*b); + AA_BUG(an <= 0); + AA_BUG(bn <= 0); + + for (i = 0; i < an && i < bn; i++) { + int res = profile_cmp(a[i], b[i]); + + if (res != 0) + return res; + } + + return an - bn; +} + +static bool vec_is_stale(struct aa_profile **vec, int n) +{ + int i; + + AA_BUG(!vec); + + for (i = 0; i < n; i++) { + if (profile_is_stale(vec[i])) + return true; + } + + return false; +} + +static bool vec_unconfined(struct aa_profile **vec, int n) +{ + int i; + + AA_BUG(!vec); + + for (i = 0; i < n; i++) { + if (!profile_unconfined(vec[i])) + return false; + } + + return true; +} + +static int sort_cmp(const void *a, const void *b) +{ + return profile_cmp(*(struct aa_profile **)a, *(struct aa_profile **)b); +} + +/* + * assumes vec is sorted + * Assumes @vec has null terminator at vec[n], and will null terminate + * vec[n - dups] + */ +static inline int unique(struct aa_profile **vec, int n) +{ + int i, pos, dups = 0; + + AA_BUG(n < 1); + AA_BUG(!vec); + + pos = 0; + for (i = 1; i < n; i++) { + int res = profile_cmp(vec[pos], vec[i]); + + AA_BUG(res > 0, "vec not sorted"); + if (res == 0) { + /* drop duplicate */ + aa_put_profile(vec[i]); + dups++; + continue; + } + pos++; + if (dups) + vec[pos] = vec[i]; + } + + AA_BUG(dups < 0); + + return dups; +} + +/** + * aa_vec_unique - canonical sort and unique a list of profiles + * @n: number of refcounted profiles in the list (@n > 0) + * @vec: list of profiles to sort and merge + * + * Returns: the number of duplicates eliminated == references put + * + * If @flags & VEC_FLAG_TERMINATE @vec has null terminator at vec[n], and will + * null terminate vec[n - dups] + */ +int aa_vec_unique(struct aa_profile **vec, int n, int flags) +{ + int i, dups = 0; + + AA_BUG(n < 1); + AA_BUG(!vec); + + /* vecs are usually small and inorder, have a fallback for larger */ + if (n > 8) { + sort(vec, n, sizeof(struct aa_profile *), sort_cmp, NULL); + dups = unique(vec, n); + goto out; + } + + /* insertion sort + unique in one */ + for (i = 1; i < n; i++) { + struct aa_profile *tmp = vec[i]; + int pos, j; + + for (pos = i - 1 - dups; pos >= 0; pos--) { + int res = profile_cmp(vec[pos], tmp); + + if (res == 0) { + /* drop duplicate entry */ + aa_put_profile(tmp); + dups++; + goto continue_outer; + } else if (res < 0) + break; + } + /* pos is at entry < tmp, or index -1. Set to insert pos */ + pos++; + + for (j = i - dups; j > pos; j--) + vec[j] = vec[j - 1]; + vec[pos] = tmp; +continue_outer: + ; + } + + AA_BUG(dups < 0); + +out: + if (flags & VEC_FLAG_TERMINATE) + vec[n - dups] = NULL; + + return dups; +} + + +static void label_destroy(struct aa_label *label) +{ + struct aa_label *tmp; + + AA_BUG(!label); + + if (!label_isprofile(label)) { + struct aa_profile *profile; + struct label_it i; + + aa_put_str(label->hname); + + label_for_each(i, label, profile) { + aa_put_profile(profile); + label->vec[i.i] = (struct aa_profile *) + (LABEL_POISON + (long) i.i); + } + } + + if (rcu_dereference_protected(label->proxy->label, true) == label) + rcu_assign_pointer(label->proxy->label, NULL); + + aa_free_secid(label->secid); + + tmp = rcu_dereference_protected(label->proxy->label, true); + if (tmp == label) + rcu_assign_pointer(label->proxy->label, NULL); + + aa_put_proxy(label->proxy); + label->proxy = (struct aa_proxy *) PROXY_POISON + 1; +} + +void aa_label_free(struct aa_label *label) +{ + if (!label) + return; + + label_destroy(label); + kfree(label); +} + +static void label_free_switch(struct aa_label *label) +{ + if (label->flags & FLAG_NS_COUNT) + aa_free_ns(labels_ns(label)); + else if (label_isprofile(label)) + aa_free_profile(labels_profile(label)); + else + aa_label_free(label); +} + +static void label_free_rcu(struct rcu_head *head) +{ + struct aa_label *label = container_of(head, struct aa_label, rcu); + + if (label->flags & FLAG_IN_TREE) + (void) aa_label_remove(label); + label_free_switch(label); +} + +void aa_label_kref(struct kref *kref) +{ + struct aa_label *label = container_of(kref, struct aa_label, count); + struct aa_ns *ns = labels_ns(label); + + if (!ns) { + /* never live, no rcu callback needed, just using the fn */ + label_free_switch(label); + return; + } + /* TODO: update labels_profile macro so it works here */ + AA_BUG(label_isprofile(label) && + on_list_rcu(&label->vec[0]->base.profiles)); + AA_BUG(label_isprofile(label) && + on_list_rcu(&label->vec[0]->base.list)); + + /* TODO: if compound label and not stale add to reclaim cache */ + call_rcu(&label->rcu, label_free_rcu); +} + +static void label_free_or_put_new(struct aa_label *label, struct aa_label *new) +{ + if (label != new) + /* need to free directly to break circular ref with proxy */ + aa_label_free(new); + else + aa_put_label(new); +} + +bool aa_label_init(struct aa_label *label, int size) +{ + AA_BUG(!label); + AA_BUG(size < 1); + + label->secid = aa_alloc_secid(); + if (label->secid == AA_SECID_INVALID) + return false; + + label->size = size; /* doesn't include null */ + label->vec[size] = NULL; /* null terminate */ + kref_init(&label->count); + RB_CLEAR_NODE(&label->node); + + return true; +} + +/** + * aa_label_alloc - allocate a label with a profile vector of @size length + * @size: size of profile vector in the label + * @proxy: proxy to use OR null if to allocate a new one + * @gfp: memory allocation type + * + * Returns: new label + * else NULL if failed + */ +struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) +{ + struct aa_label *new; + + AA_BUG(size < 1); + + /* + 1 for null terminator entry on vec */ + new = kzalloc(sizeof(*new) + sizeof(struct aa_profile *) * (size + 1), + gfp); + AA_DEBUG("%s (%p)\n", __func__, new); + if (!new) + goto fail; + + if (!aa_label_init(new, size)) + goto fail; + + if (!proxy) { + proxy = aa_alloc_proxy(new, gfp); + if (!proxy) + goto fail; + } else + aa_get_proxy(proxy); + /* just set new's proxy, don't redirect proxy here if it was passed in*/ + new->proxy = proxy; + + return new; + +fail: + kfree(new); + + return NULL; +} + + +/** + * label_cmp - label comparision for set ordering + * @a: label to compare (NOT NULL) + * @b: label to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int label_cmp(struct aa_label *a, struct aa_label *b) +{ + AA_BUG(!b); + + if (a == b) + return 0; + + return vec_cmp(a->vec, a->size, b->vec, b->size); +} + +/* helper fn for label_for_each_confined */ +int aa_label_next_confined(struct aa_label *label, int i) +{ + AA_BUG(!label); + AA_BUG(i < 0); + + for (; i < label->size; i++) { + if (!profile_unconfined(label->vec[i])) + return i; + } + + return i; +} + +/** + * aa_label_next_not_in_set - return the next profile of @sub not in @set + * @I: label iterator + * @set: label to test against + * @sub: label to if is subset of @set + * + * Returns: profile in @sub that is not in @set, with iterator set pos after + * else NULL if @sub is a subset of @set + */ +struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, + struct aa_label *set, + struct aa_label *sub) +{ + AA_BUG(!set); + AA_BUG(!I); + AA_BUG(I->i < 0); + AA_BUG(I->i > set->size); + AA_BUG(!sub); + AA_BUG(I->j < 0); + AA_BUG(I->j > sub->size); + + while (I->j < sub->size && I->i < set->size) { + int res = profile_cmp(sub->vec[I->j], set->vec[I->i]); + + if (res == 0) { + (I->j)++; + (I->i)++; + } else if (res > 0) + (I->i)++; + else + return sub->vec[(I->j)++]; + } + + if (I->j < sub->size) + return sub->vec[(I->j)++]; + + return NULL; +} + +/** + * aa_label_is_subset - test if @sub is a subset of @set + * @set: label to test against + * @sub: label to test if is subset of @set + * + * Returns: true if @sub is subset of @set + * else false + */ +bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) +{ + struct label_it i = { }; + + AA_BUG(!set); + AA_BUG(!sub); + + if (sub == set) + return true; + + return __aa_label_next_not_in_set(&i, set, sub) == NULL; +} + + + +/** + * __label_remove - remove @label from the label set + * @l: label to remove + * @new: label to redirect to + * + * Requires: labels_set(@label)->lock write_lock + * Returns: true if the label was in the tree and removed + */ +static bool __label_remove(struct aa_label *label, struct aa_label *new) +{ + struct aa_labelset *ls = labels_set(label); + + AA_BUG(!ls); + AA_BUG(!label); + AA_BUG(!write_is_locked(&ls->lock)); + + if (new) + __aa_proxy_redirect(label, new); + + if (!label_is_stale(label)) + __label_make_stale(label); + + if (label->flags & FLAG_IN_TREE) { + rb_erase(&label->node, &ls->root); + label->flags &= ~FLAG_IN_TREE; + return true; + } + + return false; +} + +/** + * __label_replace - replace @old with @new in label set + * @old: label to remove from label set + * @new: label to replace @old with + * + * Requires: labels_set(@old)->lock write_lock + * valid ref count be held on @new + * Returns: true if @old was in set and replaced by @new + * + * Note: current implementation requires label set be order in such a way + * that @new directly replaces @old position in the set (ie. + * using pointer comparison of the label address would not work) + */ +static bool __label_replace(struct aa_label *old, struct aa_label *new) +{ + struct aa_labelset *ls = labels_set(old); + + AA_BUG(!ls); + AA_BUG(!old); + AA_BUG(!new); + AA_BUG(!write_is_locked(&ls->lock)); + AA_BUG(new->flags & FLAG_IN_TREE); + + if (!label_is_stale(old)) + __label_make_stale(old); + + if (old->flags & FLAG_IN_TREE) { + rb_replace_node(&old->node, &new->node, &ls->root); + old->flags &= ~FLAG_IN_TREE; + new->flags |= FLAG_IN_TREE; + return true; + } + + return false; +} + +/** + * __label_insert - attempt to insert @l into a label set + * @ls: set of labels to insert @l into (NOT NULL) + * @label: new label to insert (NOT NULL) + * @replace: whether insertion should replace existing entry that is not stale + * + * Requires: @ls->lock + * caller to hold a valid ref on l + * if @replace is true l has a preallocated proxy associated + * Returns: @l if successful in inserting @l - with additional refcount + * else ref counted equivalent label that is already in the set, + * the else condition only happens if @replace is false + */ +static struct aa_label *__label_insert(struct aa_labelset *ls, + struct aa_label *label, bool replace) +{ + struct rb_node **new, *parent = NULL; + + AA_BUG(!ls); + AA_BUG(!label); + AA_BUG(labels_set(label) != ls); + AA_BUG(!write_is_locked(&ls->lock)); + AA_BUG(label->flags & FLAG_IN_TREE); + + /* Figure out where to put new node */ + new = &ls->root.rb_node; + while (*new) { + struct aa_label *this = rb_entry(*new, struct aa_label, node); + int result = label_cmp(label, this); + + parent = *new; + if (result == 0) { + /* !__aa_get_label means queued for destruction, + * so replace in place, however the label has + * died before the replacement so do not share + * the proxy + */ + if (!replace && !label_is_stale(this)) { + if (__aa_get_label(this)) + return this; + } else + __proxy_share(this, label); + AA_BUG(!__label_replace(this, label)); + return aa_get_label(label); + } else if (result < 0) + new = &((*new)->rb_left); + else /* (result > 0) */ + new = &((*new)->rb_right); + } + + /* Add new node and rebalance tree. */ + rb_link_node(&label->node, parent, new); + rb_insert_color(&label->node, &ls->root); + label->flags |= FLAG_IN_TREE; + + return aa_get_label(label); +} + +/** + * __vec_find - find label that matches @vec in label set + * @vec: vec of profiles to find matching label for (NOT NULL) + * @n: length of @vec + * + * Requires: @vec_labelset(vec) lock held + * caller to hold a valid ref on l + * + * Returns: ref counted @label if matching label is in tree + * ref counted label that is equiv to @l in tree + * else NULL if @vec equiv is not in tree + */ +static struct aa_label *__vec_find(struct aa_profile **vec, int n) +{ + struct rb_node *node; + + AA_BUG(!vec); + AA_BUG(!*vec); + AA_BUG(n <= 0); + + node = vec_labelset(vec, n)->root.rb_node; + while (node) { + struct aa_label *this = rb_entry(node, struct aa_label, node); + int result = vec_cmp(this->vec, this->size, vec, n); + + if (result > 0) + node = node->rb_left; + else if (result < 0) + node = node->rb_right; + else + return __aa_get_label(this); + } + + return NULL; +} + +/** + * __label_find - find label @label in label set + * @label: label to find (NOT NULL) + * + * Requires: labels_set(@label)->lock held + * caller to hold a valid ref on l + * + * Returns: ref counted @label if @label is in tree OR + * ref counted label that is equiv to @label in tree + * else NULL if @label or equiv is not in tree + */ +static struct aa_label *__label_find(struct aa_label *label) +{ + AA_BUG(!label); + + return __vec_find(label->vec, label->size); +} + + +/** + * aa_label_remove - remove a label from the labelset + * @label: label to remove + * + * Returns: true if @label was removed from the tree + * else @label was not in tree so it could not be removed + */ +bool aa_label_remove(struct aa_label *label) +{ + struct aa_labelset *ls = labels_set(label); + unsigned long flags; + bool res; + + AA_BUG(!ls); + + write_lock_irqsave(&ls->lock, flags); + res = __label_remove(label, ns_unconfined(labels_ns(label))); + write_unlock_irqrestore(&ls->lock, flags); + + return res; +} + +/** + * aa_label_replace - replace a label @old with a new version @new + * @old: label to replace + * @new: label replacing @old + * + * Returns: true if @old was in tree and replaced + * else @old was not in tree, and @new was not inserted + */ +bool aa_label_replace(struct aa_label *old, struct aa_label *new) +{ + unsigned long flags; + bool res; + + if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) { + write_lock_irqsave(&labels_set(old)->lock, flags); + if (old->proxy != new->proxy) + __proxy_share(old, new); + else + __aa_proxy_redirect(old, new); + res = __label_replace(old, new); + write_unlock_irqrestore(&labels_set(old)->lock, flags); + } else { + struct aa_label *l; + struct aa_labelset *ls = labels_set(old); + + write_lock_irqsave(&ls->lock, flags); + res = __label_remove(old, new); + if (labels_ns(old) != labels_ns(new)) { + write_unlock_irqrestore(&ls->lock, flags); + ls = labels_set(new); + write_lock_irqsave(&ls->lock, flags); + } + l = __label_insert(ls, new, true); + res = (l == new); + write_unlock_irqrestore(&ls->lock, flags); + aa_put_label(l); + } + + return res; +} + +/** + * vec_find - find label @l in label set + * @vec: array of profiles to find equiv label for (NOT NULL) + * @n: length of @vec + * + * Returns: refcounted label if @vec equiv is in tree + * else NULL if @vec equiv is not in tree + */ +static struct aa_label *vec_find(struct aa_profile **vec, int n) +{ + struct aa_labelset *ls; + struct aa_label *label; + unsigned long flags; + + AA_BUG(!vec); + AA_BUG(!*vec); + AA_BUG(n <= 0); + + ls = vec_labelset(vec, n); + read_lock_irqsave(&ls->lock, flags); + label = __vec_find(vec, n); + read_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/* requires sort and merge done first */ +static struct aa_label *vec_create_and_insert_label(struct aa_profile **vec, + int len, gfp_t gfp) +{ + struct aa_label *label = NULL; + struct aa_labelset *ls; + unsigned long flags; + struct aa_label *new; + int i; + + AA_BUG(!vec); + + if (len == 1) + return aa_get_label(&vec[0]->label); + + ls = labels_set(&vec[len - 1]->label); + + /* TODO: enable when read side is lockless + * check if label exists before taking locks + */ + new = aa_label_alloc(len, NULL, gfp); + if (!new) + return NULL; + + for (i = 0; i < len; i++) + new->vec[i] = aa_get_profile(vec[i]); + + write_lock_irqsave(&ls->lock, flags); + label = __label_insert(ls, new, false); + write_unlock_irqrestore(&ls->lock, flags); + label_free_or_put_new(label, new); + + return label; +} + +struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, + gfp_t gfp) +{ + struct aa_label *label = vec_find(vec, len); + + if (label) + return label; + + return vec_create_and_insert_label(vec, len, gfp); +} + +/** + * aa_label_find - find label @label in label set + * @label: label to find (NOT NULL) + * + * Requires: caller to hold a valid ref on l + * + * Returns: refcounted @label if @label is in tree + * refcounted label that is equiv to @label in tree + * else NULL if @label or equiv is not in tree + */ +struct aa_label *aa_label_find(struct aa_label *label) +{ + AA_BUG(!label); + + return vec_find(label->vec, label->size); +} + + +/** + * aa_label_insert - insert label @label into @ls or return existing label + * @ls - labelset to insert @label into + * @label - label to insert + * + * Requires: caller to hold a valid ref on @label + * + * Returns: ref counted @label if successful in inserting @label + * else ref counted equivalent label that is already in the set + */ +struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label) +{ + struct aa_label *l; + unsigned long flags; + + AA_BUG(!ls); + AA_BUG(!label); + + /* check if label exists before taking lock */ + if (!label_is_stale(label)) { + read_lock_irqsave(&ls->lock, flags); + l = __label_find(label); + read_unlock_irqrestore(&ls->lock, flags); + if (l) + return l; + } + + write_lock_irqsave(&ls->lock, flags); + l = __label_insert(ls, label, false); + write_unlock_irqrestore(&ls->lock, flags); + + return l; +} + + +/** + * aa_label_next_in_merge - find the next profile when merging @a and @b + * @I: label iterator + * @a: label to merge + * @b: label to merge + * + * Returns: next profile + * else null if no more profiles + */ +struct aa_profile *aa_label_next_in_merge(struct label_it *I, + struct aa_label *a, + struct aa_label *b) +{ + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!I); + AA_BUG(I->i < 0); + AA_BUG(I->i > a->size); + AA_BUG(I->j < 0); + AA_BUG(I->j > b->size); + + if (I->i < a->size) { + if (I->j < b->size) { + int res = profile_cmp(a->vec[I->i], b->vec[I->j]); + + if (res > 0) + return b->vec[(I->j)++]; + if (res == 0) + (I->j)++; + } + + return a->vec[(I->i)++]; + } + + if (I->j < b->size) + return b->vec[(I->j)++]; + + return NULL; +} + +/** + * label_merge_cmp - cmp of @a merging with @b against @z for set ordering + * @a: label to merge then compare (NOT NULL) + * @b: label to merge then compare (NOT NULL) + * @z: label to compare merge against (NOT NULL) + * + * Assumes: using the most recent versions of @a, @b, and @z + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int label_merge_cmp(struct aa_label *a, struct aa_label *b, + struct aa_label *z) +{ + struct aa_profile *p = NULL; + struct label_it i = { }; + int k; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!z); + + for (k = 0; + k < z->size && (p = aa_label_next_in_merge(&i, a, b)); + k++) { + int res = profile_cmp(p, z->vec[k]); + + if (res != 0) + return res; + } + + if (p) + return 1; + else if (k < z->size) + return -1; + return 0; +} + +/** + * label_merge_insert - create a new label by merging @a and @b + * @new: preallocated label to merge into (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: preallocated proxy + * + * Returns: ref counted label either @new if merge is unique + * @a if @b is a subset of @a + * @b if @a is a subset of @b + * + * NOTE: will not use @new if the merge results in @new == @a or @b + * + * Must be used within labelset write lock to avoid racing with + * setting labels stale. + */ +static struct aa_label *label_merge_insert(struct aa_label *new, + struct aa_label *a, + struct aa_label *b) +{ + struct aa_label *label; + struct aa_labelset *ls; + struct aa_profile *next; + struct label_it i; + unsigned long flags; + int k = 0, invcount = 0; + bool stale = false; + + AA_BUG(!a); + AA_BUG(a->size < 0); + AA_BUG(!b); + AA_BUG(b->size < 0); + AA_BUG(!new); + AA_BUG(new->size < a->size + b->size); + + label_for_each_in_merge(i, a, b, next) { + AA_BUG(!next); + if (profile_is_stale(next)) { + new->vec[k] = aa_get_newest_profile(next); + AA_BUG(!new->vec[k]->label.proxy); + AA_BUG(!new->vec[k]->label.proxy->label); + if (next->label.proxy != new->vec[k]->label.proxy) + invcount++; + k++; + stale = true; + } else + new->vec[k++] = aa_get_profile(next); + } + /* set to actual size which is <= allocated len */ + new->size = k; + new->vec[k] = NULL; + + if (invcount) { + new->size -= aa_vec_unique(&new->vec[0], new->size, + VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (new->size == 1) { + label = aa_get_label(&new->vec[0]->label); + return label; + } + } else if (!stale) { + /* + * merge could be same as a || b, note: it is not possible + * for new->size == a->size == b->size unless a == b + */ + if (k == a->size) + return aa_get_label(a); + else if (k == b->size) + return aa_get_label(b); + } + if (vec_unconfined(new->vec, new->size)) + new->flags |= FLAG_UNCONFINED; + ls = labels_set(new); + write_lock_irqsave(&ls->lock, flags); + label = __label_insert(labels_set(new), new, false); + write_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/** + * labelset_of_merge - find which labelset a merged label should be inserted + * @a: label to merge and insert + * @b: label to merge and insert + * + * Returns: labelset that the merged label should be inserted into + */ +static struct aa_labelset *labelset_of_merge(struct aa_label *a, + struct aa_label *b) +{ + struct aa_ns *nsa = labels_ns(a); + struct aa_ns *nsb = labels_ns(b); + + if (ns_cmp(nsa, nsb) <= 0) + return &nsa->labels; + return &nsb->labels; +} + +/** + * __label_find_merge - find label that is equiv to merge of @a and @b + * @ls: set of labels to search (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: ls->lock read_lock held + * + * Returns: ref counted label that is equiv to merge of @a and @b + * else NULL if merge of @a and @b is not in set + */ +static struct aa_label *__label_find_merge(struct aa_labelset *ls, + struct aa_label *a, + struct aa_label *b) +{ + struct rb_node *node; + + AA_BUG(!ls); + AA_BUG(!a); + AA_BUG(!b); + + if (a == b) + return __label_find(a); + + node = ls->root.rb_node; + while (node) { + struct aa_label *this = container_of(node, struct aa_label, + node); + int result = label_merge_cmp(a, b, this); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return __aa_get_label(this); + } + + return NULL; +} + + +/** + * aa_label_find_merge - find label that is equiv to merge of @a and @b + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: labels be fully constructed with a valid ns + * + * Returns: ref counted label that is equiv to merge of @a and @b + * else NULL if merge of @a and @b is not in set + */ +struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b) +{ + struct aa_labelset *ls; + struct aa_label *label, *ar = NULL, *br = NULL; + unsigned long flags; + + AA_BUG(!a); + AA_BUG(!b); + + if (label_is_stale(a)) + a = ar = aa_get_newest_label(a); + if (label_is_stale(b)) + b = br = aa_get_newest_label(b); + ls = labelset_of_merge(a, b); + read_lock_irqsave(&ls->lock, flags); + label = __label_find_merge(ls, a, b); + read_unlock_irqrestore(&ls->lock, flags); + aa_put_label(ar); + aa_put_label(br); + + return label; +} + +/** + * aa_label_merge - attempt to insert new merged label of @a and @b + * @ls: set of labels to insert label into (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * @gfp: memory allocation type + * + * Requires: caller to hold valid refs on @a and @b + * labels be fully constructed with a valid ns + * + * Returns: ref counted new label if successful in inserting merge of a & b + * else ref counted equivalent label that is already in the set. + * else NULL if could not create label (-ENOMEM) + */ +struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, + gfp_t gfp) +{ + struct aa_label *label = NULL; + + AA_BUG(!a); + AA_BUG(!b); + + if (a == b) + return aa_get_newest_label(a); + + /* TODO: enable when read side is lockless + * check if label exists before taking locks + if (!label_is_stale(a) && !label_is_stale(b)) + label = aa_label_find_merge(a, b); + */ + + if (!label) { + struct aa_label *new; + + a = aa_get_newest_label(a); + b = aa_get_newest_label(b); + + /* could use label_merge_len(a, b), but requires double + * comparison for small savings + */ + new = aa_label_alloc(a->size + b->size, NULL, gfp); + if (!new) + goto out; + + label = label_merge_insert(new, a, b); + label_free_or_put_new(label, new); +out: + aa_put_label(a); + aa_put_label(b); + } + + return label; +} + +static inline bool label_is_visible(struct aa_profile *profile, + struct aa_label *label) +{ + return aa_ns_visible(profile->ns, labels_ns(label), true); +} + +/* match a profile and its associated ns component if needed + * Assumes visibility test has already been done. + * If a subns profile is not to be matched should be prescreened with + * visibility test. + */ +static inline unsigned int match_component(struct aa_profile *profile, + struct aa_profile *tp, + unsigned int state) +{ + const char *ns_name; + + if (profile->ns == tp->ns) + return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); + + /* try matching with namespace name and then profile */ + ns_name = aa_ns_name(profile->ns, tp->ns, true); + state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); + state = aa_dfa_match(profile->policy.dfa, state, ns_name); + state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); + return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); +} + +/** + * label_compound_match - find perms for full compound label + * @profile: profile to find perms for + * @label: label to check access permissions for + * @start: state to start match in + * @subns: whether to do permission checks on components in a subns + * @request: permissions to request + * @perms: perms struct to set + * + * Returns: 0 on success else ERROR + * + * For the label A//&B//&C this does the perm match for A//&B//&C + * @perms should be preinitialized with allperms OR a previous permission + * check to be stacked. + */ +static int label_compound_match(struct aa_profile *profile, + struct aa_label *label, + unsigned int state, bool subns, u32 request, + struct aa_perms *perms) +{ + struct aa_profile *tp; + struct label_it i; + + /* find first subcomponent that is visible */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, state); + if (!state) + goto fail; + goto next; + } + + /* no component visible */ + *perms = allperms; + return 0; + +next: + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = aa_dfa_match(profile->policy.dfa, state, "//&"); + state = match_component(profile, tp, state); + if (!state) + goto fail; + } + aa_compute_perms(profile->policy.dfa, state, perms); + aa_apply_modes_to_perms(profile, perms); + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return state; +} + +/** + * label_components_match - find perms for all subcomponents of a label + * @profile: profile to find perms for + * @label: label to check access permissions for + * @start: state to start match in + * @subns: whether to do permission checks on components in a subns + * @request: permissions to request + * @perms: an initialized perms struct to add accumulation to + * + * Returns: 0 on success else ERROR + * + * For the label A//&B//&C this does the perm match for each of A and B and C + * @perms should be preinitialized with allperms OR a previous permission + * check to be stacked. + */ +static int label_components_match(struct aa_profile *profile, + struct aa_label *label, unsigned int start, + bool subns, u32 request, + struct aa_perms *perms) +{ + struct aa_profile *tp; + struct label_it i; + struct aa_perms tmp; + unsigned int state = 0; + + /* find first subcomponent to test */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, start); + if (!state) + goto fail; + goto next; + } + + /* no subcomponents visible - no change in perms */ + return 0; + +next: + aa_compute_perms(profile->policy.dfa, state, &tmp); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, start); + if (!state) + goto fail; + aa_compute_perms(profile->policy.dfa, state, &tmp); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + } + + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return -EACCES; +} + +/** + * aa_label_match - do a multi-component label match + * @profile: profile to match against (NOT NULL) + * @label: label to match (NOT NULL) + * @state: state to start in + * @subns: whether to match subns components + * @request: permission request + * @perms: Returns computed perms (NOT NULL) + * + * Returns: the state the match finished in, may be the none matching state + */ +int aa_label_match(struct aa_profile *profile, struct aa_label *label, + unsigned int state, bool subns, u32 request, + struct aa_perms *perms) +{ + int error = label_compound_match(profile, label, state, subns, request, + perms); + if (!error) + return error; + + *perms = allperms; + return label_components_match(profile, label, state, subns, request, + perms); +} + + +/** + * aa_update_label_name - update a label to have a stored name + * @ns: ns being viewed from (NOT NULL) + * @label: label to update (NOT NULL) + * @gfp: type of memory allocation + * + * Requires: labels_set(label) not locked in caller + * + * note: only updates the label name if it does not have a name already + * and if it is in the labelset + */ +bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp) +{ + struct aa_labelset *ls; + unsigned long flags; + char __counted *name; + bool res = false; + + AA_BUG(!ns); + AA_BUG(!label); + + if (label->hname || labels_ns(label) != ns) + return res; + + if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, gfp) == -1) + return res; + + ls = labels_set(label); + write_lock_irqsave(&ls->lock, flags); + if (!label->hname && label->flags & FLAG_IN_TREE) { + label->hname = name; + res = true; + } else + aa_put_str(name); + write_unlock_irqrestore(&ls->lock, flags); + + return res; +} + +/* + * cached label name is present and visible + * @label->hname only exists if label is namespace hierachical + */ +static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label) +{ + if (label->hname && labels_ns(label) == ns) + return true; + + return false; +} + +/* helper macro for snprint routines */ +#define update_for_len(total, len, size, str) \ +do { \ + AA_BUG(len < 0); \ + total += len; \ + len = min(len, size); \ + size -= len; \ + str += len; \ +} while (0) + +/** + * aa_profile_snxprint - print a profile name to a buffer + * @str: buffer to write to. (MAY BE NULL if @size == 0) + * @size: size of buffer + * @view: namespace profile is being viewed from + * @profile: profile to view (NOT NULL) + * @flags: whether to include the mode string + * @prev_ns: last ns printed when used in compound print + * + * Returns: size of name written or would be written if larger than + * available buffer + * + * Note: will not print anything if the profile is not visible + */ +static int aa_profile_snxprint(char *str, size_t size, struct aa_ns *view, + struct aa_profile *profile, int flags, + struct aa_ns **prev_ns) +{ + const char *ns_name = NULL; + + AA_BUG(!str && size != 0); + AA_BUG(!profile); + + if (!view) + view = profiles_ns(profile); + + if (view != profile->ns && + (!prev_ns || (prev_ns && *prev_ns != profile->ns))) { + if (prev_ns) + *prev_ns = profile->ns; + ns_name = aa_ns_name(view, profile->ns, + flags & FLAG_VIEW_SUBNS); + if (ns_name == aa_hidden_ns_name) { + if (flags & FLAG_HIDDEN_UNCONFINED) + return snprintf(str, size, "%s", "unconfined"); + return snprintf(str, size, "%s", ns_name); + } + } + + if ((flags & FLAG_SHOW_MODE) && profile != profile->ns->unconfined) { + const char *modestr = aa_profile_mode_names[profile->mode]; + + if (ns_name) + return snprintf(str, size, ":%s:%s (%s)", ns_name, + profile->base.hname, modestr); + return snprintf(str, size, "%s (%s)", profile->base.hname, + modestr); + } + + if (ns_name) + return snprintf(str, size, ":%s:%s", ns_name, + profile->base.hname); + return snprintf(str, size, "%s", profile->base.hname); +} + +static const char *label_modename(struct aa_ns *ns, struct aa_label *label, + int flags) +{ + struct aa_profile *profile; + struct label_it i; + int mode = -1, count = 0; + + label_for_each(i, label, profile) { + if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { + if (profile->mode == APPARMOR_UNCONFINED) + /* special case unconfined so stacks with + * unconfined don't report as mixed. ie. + * profile_foo//&:ns1:unconfined (mixed) + */ + continue; + count++; + if (mode == -1) + mode = profile->mode; + else if (mode != profile->mode) + return "mixed"; + } + } + + if (count == 0) + return "-"; + if (mode == -1) + /* everything was unconfined */ + mode = APPARMOR_UNCONFINED; + + return aa_profile_mode_names[mode]; +} + +/* if any visible label is not unconfined the display_mode returns true */ +static inline bool display_mode(struct aa_ns *ns, struct aa_label *label, + int flags) +{ + if ((flags & FLAG_SHOW_MODE)) { + struct aa_profile *profile; + struct label_it i; + + label_for_each(i, label, profile) { + if (aa_ns_visible(ns, profile->ns, + flags & FLAG_VIEW_SUBNS) && + profile != profile->ns->unconfined) + return true; + } + /* only ns->unconfined in set of profiles in ns */ + return false; + } + + return false; +} + +/** + * aa_label_snxprint - print a label name to a string buffer + * @str: buffer to write to. (MAY BE NULL if @size == 0) + * @size: size of buffer + * @ns: namespace profile is being viewed from + * @label: label to view (NOT NULL) + * @flags: whether to include the mode string + * + * Returns: size of name written or would be written if larger than + * available buffer + * + * Note: labels do not have to be strictly hierarchical to the ns as + * objects may be shared across different namespaces and thus + * pickup labeling from each ns. If a particular part of the + * label is not visible it will just be excluded. And if none + * of the label is visible "---" will be used. + */ +int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, + struct aa_label *label, int flags) +{ + struct aa_profile *profile; + struct aa_ns *prev_ns = NULL; + struct label_it i; + int count = 0, total = 0; + size_t len; + + AA_BUG(!str && size != 0); + AA_BUG(!label); + + if (!ns) + ns = labels_ns(label); + + label_for_each(i, label, profile) { + if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { + if (count > 0) { + len = snprintf(str, size, "//&"); + update_for_len(total, len, size, str); + } + len = aa_profile_snxprint(str, size, ns, profile, + flags & FLAG_VIEW_SUBNS, + &prev_ns); + update_for_len(total, len, size, str); + count++; + } + } + + if (count == 0) { + if (flags & FLAG_HIDDEN_UNCONFINED) + return snprintf(str, size, "%s", "unconfined"); + return snprintf(str, size, "%s", aa_hidden_ns_name); + } + + /* count == 1 && ... is for backwards compat where the mode + * is not displayed for 'unconfined' in the current ns + */ + if (display_mode(ns, label, flags)) { + len = snprintf(str, size, " (%s)", + label_modename(ns, label, flags)); + update_for_len(total, len, size, str); + } + + return total; +} +#undef update_for_len + +/** + * aa_label_asxprint - allocate a string buffer and print label into it + * @strp: Returns - the allocated buffer with the label name. (NOT NULL) + * @ns: namespace profile is being viewed from + * @label: label to view (NOT NULL) + * @flags: flags controlling what label info is printed + * @gfp: kernel memory allocation type + * + * Returns: size of name written or would be written if larger than + * available buffer + */ +int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, + int flags, gfp_t gfp) +{ + int size; + + AA_BUG(!strp); + AA_BUG(!label); + + size = aa_label_snxprint(NULL, 0, ns, label, flags); + if (size < 0) + return size; + + *strp = kmalloc(size + 1, gfp); + if (!*strp) + return -ENOMEM; + return aa_label_snxprint(*strp, size + 1, ns, label, flags); +} + +/** + * aa_label_acntsxprint - allocate a __counted string buffer and print label + * @strp: buffer to write to. (MAY BE NULL if @size == 0) + * @ns: namespace profile is being viewed from + * @label: label to view (NOT NULL) + * @flags: flags controlling what label info is printed + * @gfp: kernel memory allocation type + * + * Returns: size of name written or would be written if larger than + * available buffer + */ +int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + int size; + + AA_BUG(!strp); + AA_BUG(!label); + + size = aa_label_snxprint(NULL, 0, ns, label, flags); + if (size < 0) + return size; + + *strp = aa_str_alloc(size + 1, gfp); + if (!*strp) + return -ENOMEM; + return aa_label_snxprint(*strp, size + 1, ns, label, flags); +} + + +void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + const char *str; + char *name = NULL; + int len; + + AA_BUG(!ab); + AA_BUG(!label); + + if (!ns) + ns = labels_ns(label); + + if (!use_label_hname(ns, label) || display_mode(ns, label, flags)) { + len = aa_label_asxprint(&name, ns, label, flags, gfp); + if (len == -1) { + AA_DEBUG("label print error"); + return; + } + str = name; + } else { + str = (char *) label->hname; + len = strlen(str); + } + if (audit_string_contains_control(str, len)) + audit_log_n_hex(ab, str, len); + else + audit_log_n_string(ab, str, len); + + kfree(name); +} + +void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + AA_BUG(!f); + AA_BUG(!label); + + if (!ns) + ns = labels_ns(label); + + if (!use_label_hname(ns, label)) { + char *str; + int len; + + len = aa_label_asxprint(&str, ns, label, flags, gfp); + if (len == -1) { + AA_DEBUG("label print error"); + return; + } + seq_printf(f, "%s", str); + kfree(str); + } else if (display_mode(ns, label, flags)) + seq_printf(f, "%s (%s)", label->hname, + label_modename(ns, label, flags)); + else + seq_printf(f, "%s", label->hname); +} + +void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, + gfp_t gfp) +{ + AA_BUG(!label); + + if (!ns) + ns = labels_ns(label); + + if (!use_label_hname(ns, label)) { + char *str; + int len; + + len = aa_label_asxprint(&str, ns, label, flags, gfp); + if (len == -1) { + AA_DEBUG("label print error"); + return; + } + pr_info("%s", str); + kfree(str); + } else if (display_mode(ns, label, flags)) + pr_info("%s (%s)", label->hname, + label_modename(ns, label, flags)); + else + pr_info("%s", label->hname); +} + +void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp) +{ + struct aa_ns *ns = aa_get_current_ns(); + + aa_label_xaudit(ab, ns, label, FLAG_VIEW_SUBNS, gfp); + aa_put_ns(ns); +} + +void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp) +{ + struct aa_ns *ns = aa_get_current_ns(); + + aa_label_seq_xprint(f, ns, label, FLAG_VIEW_SUBNS, gfp); + aa_put_ns(ns); +} + +void aa_label_printk(struct aa_label *label, gfp_t gfp) +{ + struct aa_ns *ns = aa_get_current_ns(); + + aa_label_xprintk(ns, label, FLAG_VIEW_SUBNS, gfp); + aa_put_ns(ns); +} + +static int label_count_str_entries(const char *str) +{ + const char *split; + int count = 1; + + AA_BUG(!str); + + for (split = strstr(str, "//&"); split; split = strstr(str, "//&")) { + count++; + str = split + 3; + } + + return count; +} + +/* + * ensure stacks with components like + * :ns:A//&B + * have :ns: applied to both 'A' and 'B' by making the lookup relative + * to the base if the lookup specifies an ns, else making the stacked lookup + * relative to the last embedded ns in the string. + */ +static struct aa_profile *fqlookupn_profile(struct aa_label *base, + struct aa_label *currentbase, + const char *str, size_t n) +{ + const char *first = skipn_spaces(str, n); + + if (first && *first == ':') + return aa_fqlookupn_profile(base, str, n); + + return aa_fqlookupn_profile(currentbase, str, n); +} + +/** + * aa_label_parse - parse, validate and convert a text string to a label + * @base: base label to use for lookups (NOT NULL) + * @str: null terminated text string (NOT NULL) + * @gfp: allocation type + * @create: true if should create compound labels if they don't exist + * @force_stack: true if should stack even if no leading & + * + * Returns: the matching refcounted label if present + * else ERRPTR + */ +struct aa_label *aa_label_parse(struct aa_label *base, const char *str, + gfp_t gfp, bool create, bool force_stack) +{ + DEFINE_VEC(profile, vec); + struct aa_label *label, *currbase = base; + int i, len, stack = 0, error; + char *split; + + AA_BUG(!base); + AA_BUG(!str); + + str = skip_spaces(str); + len = label_count_str_entries(str); + if (*str == '&' || force_stack) { + /* stack on top of base */ + stack = base->size; + len += stack; + if (*str == '&') + str++; + } + error = vec_setup(profile, vec, len, gfp); + if (error) + return ERR_PTR(error); + + for (i = 0; i < stack; i++) + vec[i] = aa_get_profile(base->vec[i]); + + for (split = strstr(str, "//&"), i = stack; split && i < len; i++) { + vec[i] = fqlookupn_profile(base, currbase, str, split - str); + if (!vec[i]) + goto fail; + /* + * if component specified a new ns it becomes the new base + * so that subsequent lookups are relative to it + */ + if (vec[i]->ns != labels_ns(currbase)) + currbase = &vec[i]->label; + str = split + 3; + split = strstr(str, "//&"); + } + /* last element doesn't have a split */ + if (i < len) { + vec[i] = fqlookupn_profile(base, currbase, str, strlen(str)); + if (!vec[i]) + goto fail; + } + if (len == 1) + /* no need to free vec as len < LOCAL_VEC_ENTRIES */ + return &vec[0]->label; + + len -= aa_vec_unique(vec, len, VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (len == 1) { + label = aa_get_label(&vec[0]->label); + goto out; + } + + if (create) + label = aa_vec_find_or_create_label(vec, len, gfp); + else + label = vec_find(vec, len); + if (!label) + goto fail; + +out: + /* use adjusted len from after vec_unique, not original */ + vec_cleanup(profile, vec, len); + return label; + +fail: + label = ERR_PTR(-ENOENT); + goto out; +} + + +/** + * aa_labelset_destroy - remove all labels from the label set + * @ls: label set to cleanup (NOT NULL) + * + * Labels that are removed from the set may still exist beyond the set + * being destroyed depending on their reference counting + */ +void aa_labelset_destroy(struct aa_labelset *ls) +{ + struct rb_node *node; + unsigned long flags; + + AA_BUG(!ls); + + write_lock_irqsave(&ls->lock, flags); + for (node = rb_first(&ls->root); node; node = rb_first(&ls->root)) { + struct aa_label *this = rb_entry(node, struct aa_label, node); + + if (labels_ns(this) != root_ns) + __label_remove(this, + ns_unconfined(labels_ns(this)->parent)); + else + __label_remove(this, NULL); + } + write_unlock_irqrestore(&ls->lock, flags); +} + +/* + * @ls: labelset to init (NOT NULL) + */ +void aa_labelset_init(struct aa_labelset *ls) +{ + AA_BUG(!ls); + + rwlock_init(&ls->lock); + ls->root = RB_ROOT; +} + +static struct aa_label *labelset_next_stale(struct aa_labelset *ls) +{ + struct aa_label *label; + struct rb_node *node; + unsigned long flags; + + AA_BUG(!ls); + + read_lock_irqsave(&ls->lock, flags); + + __labelset_for_each(ls, node) { + label = rb_entry(node, struct aa_label, node); + if ((label_is_stale(label) || + vec_is_stale(label->vec, label->size)) && + __aa_get_label(label)) + goto out; + + } + label = NULL; + +out: + read_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/** + * __label_update - insert updated version of @label into labelset + * @label - the label to update/repace + * + * Returns: new label that is up to date + * else NULL on failure + * + * Requires: @ns lock be held + * + * Note: worst case is the stale @label does not get updated and has + * to be updated at a later time. + */ +static struct aa_label *__label_update(struct aa_label *label) +{ + struct aa_label *new, *tmp; + struct aa_labelset *ls; + unsigned long flags; + int i, invcount = 0; + + AA_BUG(!label); + AA_BUG(!mutex_is_locked(&labels_ns(label)->lock)); + + new = aa_label_alloc(label->size, label->proxy, GFP_KERNEL); + if (!new) + return NULL; + + /* + * while holding the ns_lock will stop profile replacement, removal, + * and label updates, label merging and removal can be occurring + */ + ls = labels_set(label); + write_lock_irqsave(&ls->lock, flags); + for (i = 0; i < label->size; i++) { + AA_BUG(!label->vec[i]); + new->vec[i] = aa_get_newest_profile(label->vec[i]); + AA_BUG(!new->vec[i]); + AA_BUG(!new->vec[i]->label.proxy); + AA_BUG(!new->vec[i]->label.proxy->label); + if (new->vec[i]->label.proxy != label->vec[i]->label.proxy) + invcount++; + } + + /* updated stale label by being removed/renamed from labelset */ + if (invcount) { + new->size -= aa_vec_unique(&new->vec[0], new->size, + VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (new->size == 1) { + tmp = aa_get_label(&new->vec[0]->label); + AA_BUG(tmp == label); + goto remove; + } + if (labels_set(label) != labels_set(new)) { + write_unlock_irqrestore(&ls->lock, flags); + tmp = aa_label_insert(labels_set(new), new); + write_lock_irqsave(&ls->lock, flags); + goto remove; + } + } else + AA_BUG(labels_ns(label) != labels_ns(new)); + + tmp = __label_insert(labels_set(label), new, true); +remove: + /* ensure label is removed, and redirected correctly */ + __label_remove(label, tmp); + write_unlock_irqrestore(&ls->lock, flags); + label_free_or_put_new(tmp, new); + + return tmp; +} + +/** + * __labelset_update - update labels in @ns + * @ns: namespace to update labels in (NOT NULL) + * + * Requires: @ns lock be held + * + * Walk the labelset ensuring that all labels are up to date and valid + * Any label that has a stale component is marked stale and replaced and + * by an updated version. + * + * If failures happen due to memory pressures then stale labels will + * be left in place until the next pass. + */ +static void __labelset_update(struct aa_ns *ns) +{ + struct aa_label *label; + + AA_BUG(!ns); + AA_BUG(!mutex_is_locked(&ns->lock)); + + do { + label = labelset_next_stale(&ns->labels); + if (label) { + struct aa_label *l = __label_update(label); + + aa_put_label(l); + aa_put_label(label); + } + } while (label); +} + +/** + * __aa_labelset_udate_subtree - update all labels with a stale component + * @ns: ns to start update at (NOT NULL) + * + * Requires: @ns lock be held + * + * Invalidates labels based on @p in @ns and any children namespaces. + */ +void __aa_labelset_update_subtree(struct aa_ns *ns) +{ + struct aa_ns *child; + + AA_BUG(!ns); + AA_BUG(!mutex_is_locked(&ns->lock)); + + __labelset_update(ns); + + list_for_each_entry(child, &ns->sub_ns, base.list) { + mutex_lock(&child->lock); + __aa_labelset_update_subtree(child); + mutex_unlock(&child->lock); + } +} diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7cd788a9445b..08ca26bcca77 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -21,8 +21,14 @@ #include "include/audit.h" #include "include/apparmor.h" #include "include/lib.h" +#include "include/perms.h" #include "include/policy.h" +struct aa_perms nullperms; +struct aa_perms allperms = { .allow = ALL_PERMS_MASK, + .quiet = ALL_PERMS_MASK, + .hide = ALL_PERMS_MASK }; + /** * aa_split_fqname - split a fqname into a profile and namespace name * @fqname: a full qualified name in namespace profile format (NOT NULL) @@ -69,7 +75,7 @@ char *aa_split_fqname(char *fqname, char **ns_name) * if all whitespace will return NULL */ -static const char *skipn_spaces(const char *str, size_t n) +const char *skipn_spaces(const char *str, size_t n) { for (; n && isspace(*str); --n) ++str; @@ -128,11 +134,350 @@ void aa_info_message(const char *str) printk(KERN_INFO "AppArmor: %s\n", str); } +__counted char *aa_str_alloc(int size, gfp_t gfp) +{ + struct counted_str *str; + + str = kmalloc(sizeof(struct counted_str) + size, gfp); + if (!str) + return NULL; + + kref_init(&str->count); + return str->name; +} + +void aa_str_kref(struct kref *kref) +{ + kfree(container_of(kref, struct counted_str, count)); +} + + +const char aa_file_perm_chrs[] = "xwracd km l "; +const char *aa_file_perm_names[] = { + "exec", + "write", + "read", + "append", + + "create", + "delete", + "open", + "rename", + + "setattr", + "getattr", + "setcred", + "getcred", + + "chmod", + "chown", + "chgrp", + "lock", + + "mmap", + "mprot", + "link", + "snapshot", + + "unknown", + "unknown", + "unknown", + "unknown", + + "unknown", + "unknown", + "unknown", + "unknown", + + "stack", + "change_onexec", + "change_profile", + "change_hat", +}; + +/** + * aa_perm_mask_to_str - convert a perm mask to its short string + * @str: character buffer to store string in (at least 10 characters) + * @mask: permission mask to convert + */ +void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask) +{ + unsigned int i, perm = 1; + + for (i = 0; i < 32; perm <<= 1, i++) { + if (mask & perm) + *str++ = chrs[i]; + } + *str = '\0'; +} + +void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask) +{ + const char *fmt = "%s"; + unsigned int i, perm = 1; + bool prev = false; + + for (i = 0; i < 32; perm <<= 1, i++) { + if (mask & perm) { + audit_log_format(ab, fmt, names[i]); + if (!prev) { + prev = true; + fmt = " %s"; + } + } + } +} + +void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, + u32 chrsmask, const char **names, u32 namesmask) +{ + char str[33]; + + audit_log_format(ab, "\""); + if ((mask & chrsmask) && chrs) { + aa_perm_mask_to_str(str, chrs, mask & chrsmask); + mask &= ~chrsmask; + audit_log_format(ab, "%s", str); + if (mask & namesmask) + audit_log_format(ab, " "); + } + if ((mask & namesmask) && names) + aa_audit_perm_names(ab, names, mask & namesmask); + audit_log_format(ab, "\""); +} + +/** + * aa_audit_perms_cb - generic callback fn for auditing perms + * @ab: audit buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void aa_audit_perms_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (aad(sa)->request) { + audit_log_format(ab, " requested_mask="); + aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs, + PERMS_CHRS_MASK, aa_file_perm_names, + PERMS_NAMES_MASK); + } + if (aad(sa)->denied) { + audit_log_format(ab, "denied_mask="); + aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs, + PERMS_CHRS_MASK, aa_file_perm_names, + PERMS_NAMES_MASK); + } + audit_log_format(ab, " peer="); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + FLAGS_NONE, GFP_ATOMIC); +} + +/** + * aa_apply_modes_to_perms - apply namespace and profile flags to perms + * @profile: that perms where computed from + * @perms: perms to apply mode modifiers to + * + * TODO: split into profile and ns based flags for when accumulating perms + */ +void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) +{ + switch (AUDIT_MODE(profile)) { + case AUDIT_ALL: + perms->audit = ALL_PERMS_MASK; + /* fall through */ + case AUDIT_NOQUIET: + perms->quiet = 0; + break; + case AUDIT_QUIET: + perms->audit = 0; + /* fall through */ + case AUDIT_QUIET_DENIED: + perms->quiet = ALL_PERMS_MASK; + break; + } + + if (KILL_MODE(profile)) + perms->kill = ALL_PERMS_MASK; + else if (COMPLAIN_MODE(profile)) + perms->complain = ALL_PERMS_MASK; +/* + * TODO: + * else if (PROMPT_MODE(profile)) + * perms->prompt = ALL_PERMS_MASK; + */ +} + +static u32 map_other(u32 x) +{ + return ((x & 0x3) << 8) | /* SETATTR/GETATTR */ + ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */ + ((x & 0x60) << 19); /* SETOPT/GETOPT */ +} + +void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, + struct aa_perms *perms) +{ + perms->deny = 0; + perms->kill = perms->stop = 0; + perms->complain = perms->cond = 0; + perms->hide = 0; + perms->prompt = 0; + perms->allow = dfa_user_allow(dfa, state); + perms->audit = dfa_user_audit(dfa, state); + perms->quiet = dfa_user_quiet(dfa, state); + + /* for v5 perm mapping in the policydb, the other set is used + * to extend the general perm set + */ + perms->allow |= map_other(dfa_other_allow(dfa, state)); + perms->audit |= map_other(dfa_other_audit(dfa, state)); + perms->quiet |= map_other(dfa_other_quiet(dfa, state)); +// perms->xindex = dfa_user_xindex(dfa, state); +} + +/** + * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms + * @accum - perms struct to accumulate into + * @addend - perms struct to add to @accum + */ +void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) +{ + accum->deny |= addend->deny; + accum->allow &= addend->allow & ~addend->deny; + accum->audit |= addend->audit & addend->allow; + accum->quiet &= addend->quiet & ~addend->allow; + accum->kill |= addend->kill & ~addend->allow; + accum->stop |= addend->stop & ~addend->allow; + accum->complain |= addend->complain & ~addend->allow & ~addend->deny; + accum->cond |= addend->cond & ~addend->allow & ~addend->deny; + accum->hide &= addend->hide & ~addend->allow; + accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny; +} + +/** + * aa_perms_accum - accumulate perms, masking off overlapping perms + * @accum - perms struct to accumulate into + * @addend - perms struct to add to @accum + */ +void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) +{ + accum->deny |= addend->deny; + accum->allow &= addend->allow & ~accum->deny; + accum->audit |= addend->audit & accum->allow; + accum->quiet &= addend->quiet & ~accum->allow; + accum->kill |= addend->kill & ~accum->allow; + accum->stop |= addend->stop & ~accum->allow; + accum->complain |= addend->complain & ~accum->allow & ~accum->deny; + accum->cond |= addend->cond & ~accum->allow & ~accum->deny; + accum->hide &= addend->hide & ~accum->allow; + accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny; +} + +void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, + int type, u32 request, struct aa_perms *perms) +{ + /* TODO: doesn't yet handle extended types */ + unsigned int state; + + state = aa_dfa_next(profile->policy.dfa, + profile->policy.start[AA_CLASS_LABEL], + type); + aa_label_match(profile, label, state, false, request, perms); +} + + +/* currently unused */ +int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, + u32 request, int type, u32 *deny, + struct common_audit_data *sa) +{ + struct aa_perms perms; + + aad(sa)->label = &profile->label; + aad(sa)->peer = &target->label; + aad(sa)->request = request; + + aa_profile_match_label(profile, &target->label, type, request, &perms); + aa_apply_modes_to_perms(profile, &perms); + *deny |= request & perms.deny; + return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb); +} + +/** + * aa_check_perms - do audit mode selection based on perms set + * @profile: profile being checked + * @perms: perms computed for the request + * @request: requested perms + * @deny: Returns: explicit deny set + * @sa: initialized audit structure (MAY BE NULL if not auditing) + * @cb: callback fn for tpye specific fields (MAY BE NULL) + * + * Returns: 0 if permission else error code + * + * Note: profile audit modes need to be set before calling by setting the + * perm masks appropriately. + * + * If not auditing then complain mode is not enabled and the + * error code will indicate whether there was an explicit deny + * with a positive value. + */ +int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, + u32 request, struct common_audit_data *sa, + void (*cb)(struct audit_buffer *, void *)) +{ + int type, error; + bool stop = false; + u32 denied = request & (~perms->allow | perms->deny); + + if (likely(!denied)) { + /* mask off perms that are not being force audited */ + request &= perms->audit; + if (!request || !sa) + return 0; + + type = AUDIT_APPARMOR_AUDIT; + error = 0; + } else { + error = -EACCES; + + if (denied & perms->kill) + type = AUDIT_APPARMOR_KILL; + else if (denied == (denied & perms->complain)) + type = AUDIT_APPARMOR_ALLOWED; + else + type = AUDIT_APPARMOR_DENIED; + + if (denied & perms->stop) + stop = true; + if (denied == (denied & perms->hide)) + error = -ENOENT; + + denied &= ~perms->quiet; + if (!sa || !denied) + return error; + } + + if (sa) { + aad(sa)->label = &profile->label; + aad(sa)->request = request; + aad(sa)->denied = denied; + aad(sa)->error = error; + aa_audit_msg(type, sa, cb); + } + + if (type == AUDIT_APPARMOR_ALLOWED) + error = 0; + + return error; +} + + /** * aa_policy_init - initialize a policy structure * @policy: policy to initialize (NOT NULL) * @prefix: prefix name if any is required. (MAYBE NULL) * @name: name of the policy, init will make a copy of it (NOT NULL) + * @gfp: allocation mode * * Note: this fn creates a copy of strings passed in * @@ -141,16 +486,21 @@ void aa_info_message(const char *str) bool aa_policy_init(struct aa_policy *policy, const char *prefix, const char *name, gfp_t gfp) { + char *hname; + /* freed by policy_free */ if (prefix) { - policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3, - gfp); - if (policy->hname) - sprintf((char *)policy->hname, "%s//%s", prefix, name); - } else - policy->hname = kstrdup(name, gfp); - if (!policy->hname) + hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp); + if (hname) + sprintf(hname, "%s//%s", prefix, name); + } else { + hname = aa_str_alloc(strlen(name) + 1, gfp); + if (hname) + strcpy(hname, name); + } + if (!hname) return false; + policy->hname = hname; /* base.name is a substring of fqname */ policy->name = basename(policy->hname); INIT_LIST_HEAD(&policy->list); @@ -169,5 +519,5 @@ void aa_policy_destroy(struct aa_policy *policy) AA_BUG(on_list_rcu(&policy->list)); /* don't free name as its a subset of hname */ - kzfree(policy->hname); + aa_put_str(policy->hname); } diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8f3c0f7aca5a..867bcd154c7e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -34,6 +34,7 @@ #include "include/file.h" #include "include/ipc.h" #include "include/path.h" +#include "include/label.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/procattr.h" @@ -49,7 +50,7 @@ DEFINE_PER_CPU(struct aa_buffers, aa_buffers); */ /* - * free the associated aa_task_ctx and put its profiles + * free the associated aa_task_ctx and put its labels */ static void apparmor_cred_free(struct cred *cred) { @@ -103,34 +104,63 @@ static void apparmor_cred_transfer(struct cred *new, const struct cred *old) static int apparmor_ptrace_access_check(struct task_struct *child, unsigned int mode) { - return aa_ptrace(current, child, mode); + struct aa_label *tracer, *tracee; + int error; + + tracer = begin_current_label_crit_section(); + tracee = aa_get_task_label(child); + error = aa_may_ptrace(tracer, tracee, + mode == PTRACE_MODE_READ ? AA_PTRACE_READ : AA_PTRACE_TRACE); + aa_put_label(tracee); + end_current_label_crit_section(tracer); + + return error; } static int apparmor_ptrace_traceme(struct task_struct *parent) { - return aa_ptrace(parent, current, PTRACE_MODE_ATTACH); + struct aa_label *tracer, *tracee; + int error; + + tracee = begin_current_label_crit_section(); + tracer = aa_get_task_label(parent); + error = aa_may_ptrace(tracer, tracee, AA_PTRACE_TRACE); + aa_put_label(tracer); + end_current_label_crit_section(tracee); + + return error; } /* Derived from security/commoncap.c:cap_capget */ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) { - struct aa_profile *profile; + struct aa_label *label; const struct cred *cred; rcu_read_lock(); cred = __task_cred(target); - profile = aa_cred_profile(cred); + label = aa_get_newest_cred_label(cred); /* * cap_capget is stacked ahead of this and will * initialize effective and permitted. */ - if (!unconfined(profile) && !COMPLAIN_MODE(profile)) { - *effective = cap_intersect(*effective, profile->caps.allow); - *permitted = cap_intersect(*permitted, profile->caps.allow); + if (!unconfined(label)) { + struct aa_profile *profile; + struct label_it i; + + label_for_each_confined(i, label, profile) { + if (COMPLAIN_MODE(profile)) + continue; + *effective = cap_intersect(*effective, + profile->caps.allow); + *permitted = cap_intersect(*permitted, + profile->caps.allow); + } } rcu_read_unlock(); + aa_put_label(label); return 0; } @@ -138,12 +168,14 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, int cap, int audit) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; - profile = aa_cred_profile(cred); - if (!unconfined(profile)) - error = aa_capable(profile, cap, audit); + label = aa_get_newest_cred_label(cred); + if (!unconfined(label)) + error = aa_capable(label, cap, audit); + aa_put_label(label); + return error; } @@ -159,12 +191,13 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, static int common_perm(const char *op, const struct path *path, u32 mask, struct path_cond *cond) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; - profile = __aa_current_profile(); - if (!unconfined(profile)) - error = aa_path_perm(op, profile, path, 0, mask, cond); + label = __begin_current_label_crit_section(); + if (!unconfined(label)) + error = aa_path_perm(op, label, path, 0, mask, cond); + __end_current_label_crit_section(label); return error; } @@ -278,7 +311,7 @@ static int apparmor_path_mknod(const struct path *dir, struct dentry *dentry, static int apparmor_path_truncate(const struct path *path) { - return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE); + return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR); } static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, @@ -291,29 +324,31 @@ static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; if (!path_mediated_fs(old_dentry)) return 0; - profile = aa_current_profile(); - if (!unconfined(profile)) - error = aa_path_link(profile, old_dentry, new_dir, new_dentry); + label = begin_current_label_crit_section(); + if (!unconfined(label)) + error = aa_path_link(label, old_dentry, new_dir, new_dentry); + end_current_label_crit_section(label); + return error; } static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; if (!path_mediated_fs(old_dentry)) return 0; - profile = aa_current_profile(); - if (!unconfined(profile)) { + label = begin_current_label_crit_section(); + if (!unconfined(label)) { struct path old_path = { .mnt = old_dir->mnt, .dentry = old_dentry }; struct path new_path = { .mnt = new_dir->mnt, @@ -322,16 +357,18 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d d_backing_inode(old_dentry)->i_mode }; - error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0, - MAY_READ | AA_MAY_META_READ | MAY_WRITE | - AA_MAY_META_WRITE | AA_MAY_DELETE, + error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0, + MAY_READ | AA_MAY_GETATTR | MAY_WRITE | + AA_MAY_SETATTR | AA_MAY_DELETE, &cond); if (!error) - error = aa_path_perm(OP_RENAME_DEST, profile, &new_path, - 0, MAY_WRITE | AA_MAY_META_WRITE | + error = aa_path_perm(OP_RENAME_DEST, label, &new_path, + 0, MAY_WRITE | AA_MAY_SETATTR | AA_MAY_CREATE, &cond); } + end_current_label_crit_section(label); + return error; } @@ -347,13 +384,13 @@ static int apparmor_path_chown(const struct path *path, kuid_t uid, kgid_t gid) static int apparmor_inode_getattr(const struct path *path) { - return common_perm_cond(OP_GETATTR, path, AA_MAY_META_READ); + return common_perm_cond(OP_GETATTR, path, AA_MAY_GETATTR); } static int apparmor_file_open(struct file *file, const struct cred *cred) { - struct aa_file_ctx *fctx = file->f_security; - struct aa_profile *profile; + struct aa_file_ctx *fctx = file_ctx(file); + struct aa_label *label; int error = 0; if (!path_mediated_fs(file->f_path.dentry)) @@ -369,65 +406,61 @@ static int apparmor_file_open(struct file *file, const struct cred *cred) return 0; } - profile = aa_cred_profile(cred); - if (!unconfined(profile)) { + label = aa_get_newest_cred_label(cred); + if (!unconfined(label)) { struct inode *inode = file_inode(file); struct path_cond cond = { inode->i_uid, inode->i_mode }; - error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0, + error = aa_path_perm(OP_OPEN, label, &file->f_path, 0, aa_map_file_to_perms(file), &cond); /* todo cache full allowed permissions set and state */ fctx->allow = aa_map_file_to_perms(file); } + aa_put_label(label); return error; } static int apparmor_file_alloc_security(struct file *file) { + int error = 0; + /* freed by apparmor_file_free_security */ - file->f_security = aa_alloc_file_context(GFP_KERNEL); - if (!file->f_security) - return -ENOMEM; - return 0; + struct aa_label *label = begin_current_label_crit_section(); + file->f_security = aa_alloc_file_ctx(label, GFP_KERNEL); + if (!file_ctx(file)) + error = -ENOMEM; + end_current_label_crit_section(label); + return error; } static void apparmor_file_free_security(struct file *file) { - struct aa_file_ctx *ctx = file->f_security; - - aa_free_file_context(ctx); + aa_free_file_ctx(file_ctx(file)); } static int common_file_perm(const char *op, struct file *file, u32 mask) { - struct aa_file_ctx *fctx = file->f_security; - struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred); + struct aa_label *label; int error = 0; - AA_BUG(!fprofile); + /* don't reaudit files closed during inheritance */ + if (file->f_path.dentry == aa_null.dentry) + return -EACCES; - if (!file->f_path.mnt || - !path_mediated_fs(file->f_path.dentry)) - return 0; - - profile = __aa_current_profile(); - - /* revalidate access, if task is unconfined, or the cached cred - * doesn't match or if the request is for more permissions than - * was granted. - * - * Note: the test for !unconfined(fprofile) is to handle file - * delegation from unconfined tasks - */ - if (!unconfined(profile) && !unconfined(fprofile) && - ((fprofile != profile) || (mask & ~fctx->allow))) - error = aa_file_perm(op, profile, file, mask); + label = __begin_current_label_crit_section(); + error = aa_file_perm(op, label, file, mask); + __end_current_label_crit_section(label); return error; } +static int apparmor_file_receive(struct file *file) +{ + return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file)); +} + static int apparmor_file_permission(struct file *file, int mask) { return common_file_perm(OP_FPERM, file, mask); @@ -448,7 +481,7 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot, { int mask = 0; - if (!file || !file->f_security) + if (!file || !file_ctx(file)) return 0; if (prot & PROT_READ) @@ -485,21 +518,21 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, /* released below */ const struct cred *cred = get_task_cred(task); struct aa_task_ctx *ctx = cred_ctx(cred); - struct aa_profile *profile = NULL; + struct aa_label *label = NULL; if (strcmp(name, "current") == 0) - profile = aa_get_newest_profile(ctx->profile); + label = aa_get_newest_label(ctx->label); else if (strcmp(name, "prev") == 0 && ctx->previous) - profile = aa_get_newest_profile(ctx->previous); + label = aa_get_newest_label(ctx->previous); else if (strcmp(name, "exec") == 0 && ctx->onexec) - profile = aa_get_newest_profile(ctx->onexec); + label = aa_get_newest_label(ctx->onexec); else error = -EINVAL; - if (profile) - error = aa_getprocattr(profile, value); + if (label) + error = aa_getprocattr(label, value); - aa_put_profile(profile); + aa_put_label(label); put_cred(cred); return error; @@ -539,22 +572,24 @@ static int apparmor_setprocattr(const char *name, void *value, if (strcmp(name, "current") == 0) { if (strcmp(command, "changehat") == 0) { error = aa_setprocattr_changehat(args, arg_size, - !AA_DO_TEST); + AA_CHANGE_NOFLAGS); } else if (strcmp(command, "permhat") == 0) { error = aa_setprocattr_changehat(args, arg_size, - AA_DO_TEST); + AA_CHANGE_TEST); } else if (strcmp(command, "changeprofile") == 0) { - error = aa_change_profile(args, !AA_ONEXEC, - !AA_DO_TEST, false); + error = aa_change_profile(args, AA_CHANGE_NOFLAGS); } else if (strcmp(command, "permprofile") == 0) { - error = aa_change_profile(args, !AA_ONEXEC, AA_DO_TEST, - false); + error = aa_change_profile(args, AA_CHANGE_TEST); + } else if (strcmp(command, "stack") == 0) { + error = aa_change_profile(args, AA_CHANGE_STACK); } else goto fail; } else if (strcmp(name, "exec") == 0) { if (strcmp(command, "exec") == 0) - error = aa_change_profile(args, AA_ONEXEC, !AA_DO_TEST, - false); + error = aa_change_profile(args, AA_CHANGE_ONEXEC); + else if (strcmp(command, "stack") == 0) + error = aa_change_profile(args, (AA_CHANGE_ONEXEC | + AA_CHANGE_STACK)); else goto fail; } else @@ -568,21 +603,55 @@ out: return error; fail: - aad(&sa)->profile = aa_current_profile(); + aad(&sa)->label = begin_current_label_crit_section(); aad(&sa)->info = name; aad(&sa)->error = error = -EINVAL; aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); + end_current_label_crit_section(aad(&sa)->label); goto out; } +/** + * apparmor_bprm_committing_creds - do task cleanup on committing new creds + * @bprm: binprm for the exec (NOT NULL) + */ +static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) +{ + struct aa_label *label = aa_current_raw_label(); + struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); + + /* bail out if unconfined or not changing profile */ + if ((new_ctx->label->proxy == label->proxy) || + (unconfined(new_ctx->label))) + return; + + aa_inherit_files(bprm->cred, current->files); + + current->pdeath_signal = 0; + + /* reset soft limits and set hard limits for the new label */ + __aa_transition_rlimits(label, new_ctx->label); +} + +/** + * apparmor_bprm_committed_cred - do cleanup after new creds committed + * @bprm: binprm for the exec (NOT NULL) + */ +static void apparmor_bprm_committed_creds(struct linux_binprm *bprm) +{ + /* TODO: cleanup signals - ipc mediation */ + return; +} + static int apparmor_task_setrlimit(struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_profile *profile = __aa_current_profile(); + struct aa_label *label = __begin_current_label_crit_section(); int error = 0; - if (!unconfined(profile)) - error = aa_task_setrlimit(profile, task, resource, new_rlim); + if (!unconfined(label)) + error = aa_task_setrlimit(label, task, resource, new_rlim); + __end_current_label_crit_section(label); return error; } @@ -606,6 +675,7 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr), LSM_HOOK_INIT(file_open, apparmor_file_open), + LSM_HOOK_INIT(file_receive, apparmor_file_receive), LSM_HOOK_INIT(file_permission, apparmor_file_permission), LSM_HOOK_INIT(file_alloc_security, apparmor_file_alloc_security), LSM_HOOK_INIT(file_free_security, apparmor_file_free_security), @@ -774,11 +844,18 @@ static int param_get_aabool(char *buffer, const struct kernel_param *kp) static int param_set_aauint(const char *val, const struct kernel_param *kp) { + int error; + if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_admin_capable(NULL)) + /* file is ro but enforce 2nd line check */ + if (apparmor_initialized) return -EPERM; - return param_set_uint(val, kp); + + error = param_set_uint(val, kp); + pr_info("AppArmor: buffer size set to %d bytes\n", aa_g_path_max); + + return error; } static int param_get_aauint(char *buffer, const struct kernel_param *kp) @@ -869,7 +946,7 @@ static int __init set_init_ctx(void) if (!ctx) return -ENOMEM; - ctx->profile = aa_get_profile(root_ns->unconfined); + ctx->label = aa_get_label(ns_unconfined(root_ns)); cred_ctx(cred) = ctx; return 0; diff --git a/security/apparmor/path.c b/security/apparmor/path.c index a8fc7d08c144..9d5de1d05be4 100644 --- a/security/apparmor/path.c +++ b/security/apparmor/path.c @@ -50,7 +50,7 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen) * namespace root. */ static int disconnect(const struct path *path, char *buf, char **name, - int flags) + int flags, const char *disconnected) { int error = 0; @@ -63,9 +63,14 @@ static int disconnect(const struct path *path, char *buf, char **name, error = -EACCES; if (**name == '/') *name = *name + 1; - } else if (**name != '/') - /* CONNECT_PATH with missing root */ - error = prepend(name, *name - buf, "/", 1); + } else { + if (**name != '/') + /* CONNECT_PATH with missing root */ + error = prepend(name, *name - buf, "/", 1); + if (!error && disconnected) + error = prepend(name, *name - buf, disconnected, + strlen(disconnected)); + } return error; } @@ -74,9 +79,9 @@ static int disconnect(const struct path *path, char *buf, char **name, * d_namespace_path - lookup a name associated with a given path * @path: path to lookup (NOT NULL) * @buf: buffer to store path to (NOT NULL) - * @buflen: length of @buf * @name: Returns - pointer for start of path name with in @buf (NOT NULL) * @flags: flags controlling path lookup + * @disconnected: string to prefix to disconnected paths * * Handle path name lookup. * @@ -84,12 +89,14 @@ static int disconnect(const struct path *path, char *buf, char **name, * When no error the path name is returned in @name which points to * to a position in @buf */ -static int d_namespace_path(const struct path *path, char *buf, int buflen, - char **name, int flags) +static int d_namespace_path(const struct path *path, char *buf, char **name, + int flags, const char *disconnected) { char *res; int error = 0; int connected = 1; + int isdir = (flags & PATH_IS_DIR) ? 1 : 0; + int buflen = aa_g_path_max - isdir; if (path->mnt->mnt_flags & MNT_INTERNAL) { /* it's not mounted anywhere */ @@ -104,10 +111,12 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, /* TODO: convert over to using a per namespace * control instead of hard coded /proc */ - return prepend(name, *name - buf, "/proc", 5); + error = prepend(name, *name - buf, "/proc", 5); + goto out; } else - return disconnect(path, buf, name, flags); - return 0; + error = disconnect(path, buf, name, flags, + disconnected); + goto out; } /* resolve paths relative to chroot?*/ @@ -126,8 +135,11 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, * be returned. */ if (!res || IS_ERR(res)) { - if (PTR_ERR(res) == -ENAMETOOLONG) - return -ENAMETOOLONG; + if (PTR_ERR(res) == -ENAMETOOLONG) { + error = -ENAMETOOLONG; + *name = buf; + goto out; + } connected = 0; res = dentry_path_raw(path->dentry, buf, buflen); if (IS_ERR(res)) { @@ -140,6 +152,9 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, *name = res; + if (!connected) + error = disconnect(path, buf, name, flags, disconnected); + /* Handle two cases: * 1. A deleted dentry && profile is not allowing mediation of deleted * 2. On some filesystems, newly allocated dentries appear to the @@ -147,62 +162,30 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, * allocated. */ if (d_unlinked(path->dentry) && d_is_positive(path->dentry) && - !(flags & PATH_MEDIATE_DELETED)) { + !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) { error = -ENOENT; goto out; } - if (!connected) - error = disconnect(path, buf, name, flags); - out: - return error; -} - -/** - * get_name_to_buffer - get the pathname to a buffer ensure dir / is appended - * @path: path to get name for (NOT NULL) - * @flags: flags controlling path lookup - * @buffer: buffer to put name in (NOT NULL) - * @size: size of buffer - * @name: Returns - contains position of path name in @buffer (NOT NULL) - * - * Returns: %0 else error on failure - */ -static int get_name_to_buffer(const struct path *path, int flags, char *buffer, - int size, char **name, const char **info) -{ - int adjust = (flags & PATH_IS_DIR) ? 1 : 0; - int error = d_namespace_path(path, buffer, size - adjust, name, flags); - - if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0') - /* - * Append "/" to the pathname. The root directory is a special - * case; it already ends in slash. - */ - strcpy(&buffer[size - 2], "/"); - - if (info && error) { - if (error == -ENOENT) - *info = "Failed name lookup - deleted entry"; - else if (error == -EACCES) - *info = "Failed name lookup - disconnected path"; - else if (error == -ENAMETOOLONG) - *info = "Failed name lookup - name too long"; - else - *info = "Failed name lookup"; - } + /* + * Append "/" to the pathname. The root directory is a special + * case; it already ends in slash. + */ + if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/')) + strcpy(&buf[aa_g_path_max - 2], "/"); return error; } /** - * aa_path_name - compute the pathname of a file + * aa_path_name - get the pathname to a buffer ensure dir / is appended * @path: path the file (NOT NULL) * @flags: flags controlling path name generation - * @buffer: buffer that aa_get_name() allocated (NOT NULL) + * @buffer: buffer to put name in (NOT NULL) * @name: Returns - the generated path name if !error (NOT NULL) * @info: Returns - information on why the path lookup failed (MAYBE NULL) + * @disconnected: string to prepend to disconnected paths * * @name is a pointer to the beginning of the pathname (which usually differs * from the beginning of the buffer), or NULL. If there is an error @name @@ -215,32 +198,23 @@ static int get_name_to_buffer(const struct path *path, int flags, char *buffer, * * Returns: %0 else error code if could retrieve name */ -int aa_path_name(const struct path *path, int flags, char **buffer, - const char **name, const char **info) +int aa_path_name(const struct path *path, int flags, char *buffer, + const char **name, const char **info, const char *disconnected) { - char *buf, *str = NULL; - int size = 256; - int error; - - *name = NULL; - *buffer = NULL; - for (;;) { - /* freed by caller */ - buf = kmalloc(size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - error = get_name_to_buffer(path, flags, buf, size, &str, info); - if (error != -ENAMETOOLONG) - break; - - kfree(buf); - size <<= 1; - if (size > aa_g_path_max) - return -ENAMETOOLONG; - *info = NULL; + char *str = NULL; + int error = d_namespace_path(path, buffer, &str, flags, disconnected); + + if (info && error) { + if (error == -ENOENT) + *info = "Failed name lookup - deleted entry"; + else if (error == -EACCES) + *info = "Failed name lookup - disconnected path"; + else if (error == -ENAMETOOLONG) + *info = "Failed name lookup - name too long"; + else + *info = "Failed name lookup"; } - *buffer = buf; + *name = str; return error; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index cf9d670dca94..244ea4a4a8f0 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -101,20 +101,9 @@ const char *const aa_profile_mode_names[] = { "unconfined", }; -/* requires profile list write lock held */ -void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new) -{ - struct aa_profile *tmp; - - tmp = rcu_dereference_protected(orig->proxy->profile, - mutex_is_locked(&orig->ns->lock)); - rcu_assign_pointer(orig->proxy->profile, aa_get_profile(new)); - orig->flags |= PFLAG_STALE; - aa_put_profile(tmp); -} /** - * __list_add_profile - add a profile to a list + * __add_profile - add a profiles to list and label tree * @list: list to add it to (NOT NULL) * @profile: the profile to add (NOT NULL) * @@ -122,12 +111,21 @@ void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new) * * Requires: namespace lock be held, or list not be shared */ -static void __list_add_profile(struct list_head *list, - struct aa_profile *profile) +static void __add_profile(struct list_head *list, struct aa_profile *profile) { + struct aa_label *l; + + AA_BUG(!list); + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); + list_add_rcu(&profile->base.list, list); /* get list reference */ aa_get_profile(profile); + l = aa_label_insert(&profile->ns->labels, &profile->label); + AA_BUG(l != &profile->label); + aa_put_label(l); } /** @@ -144,6 +142,10 @@ static void __list_add_profile(struct list_head *list, */ static void __list_remove_profile(struct aa_profile *profile) { + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); + list_del_rcu(&profile->base.list); aa_put_profile(profile); } @@ -156,11 +158,15 @@ static void __list_remove_profile(struct aa_profile *profile) */ static void __remove_profile(struct aa_profile *profile) { + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); + /* release any children lists first */ __aa_profile_list_release(&profile->base.profiles); /* released by free_profile */ - __aa_update_proxy(profile, profile->ns->unconfined); - __aa_fs_profile_rmdir(profile); + aa_label_remove(&profile->label); + __aafs_profile_rmdir(profile); __list_remove_profile(profile); } @@ -177,24 +183,6 @@ void __aa_profile_list_release(struct list_head *head) __remove_profile(profile); } - -static void free_proxy(struct aa_proxy *p) -{ - if (p) { - /* r->profile will not be updated any more as r is dead */ - aa_put_profile(rcu_dereference_protected(p->profile, true)); - kzfree(p); - } -} - - -void aa_free_proxy_kref(struct kref *kref) -{ - struct aa_proxy *p = container_of(kref, struct aa_proxy, count); - - free_proxy(p); -} - /** * aa_free_data - free a data blob * @ptr: data to free @@ -242,7 +230,6 @@ void aa_free_profile(struct aa_profile *profile) kzfree(profile->dirname); aa_put_dfa(profile->xmatch); aa_put_dfa(profile->policy.dfa); - aa_put_proxy(profile->proxy); if (profile->data) { rht = profile->data; @@ -253,30 +240,8 @@ void aa_free_profile(struct aa_profile *profile) kzfree(profile->hash); aa_put_loaddata(profile->rawdata); - kzfree(profile); -} - -/** - * aa_free_profile_rcu - free aa_profile by rcu (called by aa_free_profile_kref) - * @head: rcu_head callback for freeing of a profile (NOT NULL) - */ -static void aa_free_profile_rcu(struct rcu_head *head) -{ - struct aa_profile *p = container_of(head, struct aa_profile, rcu); - if (p->flags & PFLAG_NS_COUNT) - aa_free_ns(p->ns); - else - aa_free_profile(p); -} -/** - * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile) - * @kr: kref callback for freeing of a profile (NOT NULL) - */ -void aa_free_profile_kref(struct kref *kref) -{ - struct aa_profile *p = container_of(kref, struct aa_profile, count); - call_rcu(&p->rcu, aa_free_profile_rcu); + kzfree(profile); } /** @@ -286,30 +251,40 @@ void aa_free_profile_kref(struct kref *kref) * * Returns: refcount profile or NULL on failure */ -struct aa_profile *aa_alloc_profile(const char *hname, gfp_t gfp) +struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, + gfp_t gfp) { struct aa_profile *profile; /* freed by free_profile - usually through aa_put_profile */ - profile = kzalloc(sizeof(*profile), gfp); + profile = kzalloc(sizeof(*profile) + sizeof(struct aa_profile *) * 2, + gfp); if (!profile) return NULL; - profile->proxy = kzalloc(sizeof(struct aa_proxy), gfp); - if (!profile->proxy) - goto fail; - kref_init(&profile->proxy->count); - if (!aa_policy_init(&profile->base, NULL, hname, gfp)) goto fail; - kref_init(&profile->count); + if (!aa_label_init(&profile->label, 1)) + goto fail; + + /* update being set needed by fs interface */ + if (!proxy) { + proxy = aa_alloc_proxy(&profile->label, gfp); + if (!proxy) + goto fail; + } else + aa_get_proxy(proxy); + profile->label.proxy = proxy; + + profile->label.hname = profile->base.hname; + profile->label.flags |= FLAG_PROFILE; + profile->label.vec[0] = profile; /* refcount released by caller */ return profile; fail: - kzfree(profile->proxy); - kzfree(profile); + aa_free_profile(profile); return NULL; } @@ -362,14 +337,14 @@ name: if (profile) goto out; - profile = aa_alloc_profile(name, gfp); + profile = aa_alloc_profile(name, NULL, gfp); if (!profile) goto fail; profile->mode = APPARMOR_COMPLAIN; - profile->flags |= PFLAG_NULL; + profile->label.flags |= FLAG_NULL; if (hat) - profile->flags |= PFLAG_HAT; + profile->label.flags |= FLAG_HAT; profile->path_flags = parent->path_flags; /* released on free_profile */ @@ -379,7 +354,7 @@ name: profile->policy.dfa = aa_get_dfa(nulldfa); mutex_lock(&profile->ns->lock); - __list_add_profile(&parent->base.profiles, profile); + __add_profile(&parent->base.profiles, profile); mutex_unlock(&profile->ns->lock); /* refcount released by caller */ @@ -389,7 +364,6 @@ out: return profile; fail: - kfree(name); aa_free_profile(profile); return NULL; } @@ -397,33 +371,33 @@ fail: /* TODO: profile accounting - setup in remove */ /** - * __find_child - find a profile on @head list with a name matching @name + * __strn_find_child - find a profile on @head list using substring of @name * @head: list to search (NOT NULL) * @name: name of profile (NOT NULL) + * @len: length of @name substring to match * * Requires: rcu_read_lock be held * * Returns: unrefcounted profile ptr, or NULL if not found */ -static struct aa_profile *__find_child(struct list_head *head, const char *name) +static struct aa_profile *__strn_find_child(struct list_head *head, + const char *name, int len) { - return (struct aa_profile *)__policy_find(head, name); + return (struct aa_profile *)__policy_strn_find(head, name, len); } /** - * __strn_find_child - find a profile on @head list using substring of @name + * __find_child - find a profile on @head list with a name matching @name * @head: list to search (NOT NULL) * @name: name of profile (NOT NULL) - * @len: length of @name substring to match * * Requires: rcu_read_lock be held * * Returns: unrefcounted profile ptr, or NULL if not found */ -static struct aa_profile *__strn_find_child(struct list_head *head, - const char *name, int len) +static struct aa_profile *__find_child(struct list_head *head, const char *name) { - return (struct aa_profile *)__policy_strn_find(head, name, len); + return __strn_find_child(head, name, strlen(name)); } /** @@ -556,7 +530,7 @@ struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *hname) return aa_lookupn_profile(ns, hname, strlen(hname)); } -struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, +struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n) { struct aa_profile *profile; @@ -566,11 +540,11 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, name = aa_splitn_fqname(fqname, n, &ns_name, &ns_len); if (ns_name) { - ns = aa_findn_ns(base->ns, ns_name, ns_len); + ns = aa_lookupn_ns(labels_ns(base), ns_name, ns_len); if (!ns) return NULL; } else - ns = aa_get_ns(base->ns); + ns = aa_get_ns(labels_ns(base)); if (name) profile = aa_lookupn_profile(ns, name, n - (name - fqname)); @@ -596,7 +570,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, const char **info) { if (profile) { - if (profile->flags & PFLAG_IMMUTABLE) { + if (profile->label.flags & FLAG_IMMUTIBLE) { *info = "cannot replace immutible profile"; return -EPERM; } else if (noreplace) { @@ -619,29 +593,31 @@ static void audit_cb(struct audit_buffer *ab, void *va) } /** - * aa_audit_policy - Do auditing of policy changes - * @profile: profile to check if it can manage policy + * audit_policy - Do auditing of policy changes + * @label: label to check if it can manage policy * @op: policy operation being performed - * @gfp: memory allocation flags - * @nsname: name of the ns being manipulated (MAY BE NULL) + * @ns_name: name of namespace being manipulated * @name: name of profile being manipulated (NOT NULL) * @info: any extra information to be audited (MAYBE NULL) * @error: error code * * Returns: the error to be returned after audit is done */ -static int audit_policy(struct aa_profile *profile, const char *op, - const char *nsname, const char *name, +static int audit_policy(struct aa_label *label, const char *op, + const char *ns_name, const char *name, const char *info, int error) { DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); - aad(&sa)->iface.ns = nsname; + aad(&sa)->iface.ns = ns_name; aad(&sa)->name = name; aad(&sa)->info = info; aad(&sa)->error = error; + aad(&sa)->label = label; + + aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, audit_cb); - return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); + return error; } /** @@ -685,22 +661,30 @@ bool policy_admin_capable(struct aa_ns *ns) /** * aa_may_manage_policy - can the current task manage policy - * @profile: profile to check if it can manage policy + * @label: label to check if it can manage policy * @op: the policy manipulation operation being done * * Returns: 0 if the task is allowed to manipulate policy else error */ -int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns, - const char *op) +int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask) { + const char *op; + + if (mask & AA_MAY_REMOVE_POLICY) + op = OP_PROF_RM; + else if (mask & AA_MAY_REPLACE_POLICY) + op = OP_PROF_REPL; + else + op = OP_PROF_LOAD; + /* check if loading policy is locked out */ if (aa_g_lock_policy) - return audit_policy(profile, op, NULL, NULL, - "policy_locked", -EACCES); + return audit_policy(label, op, NULL, NULL, "policy_locked", + -EACCES); if (!policy_admin_capable(ns)) - return audit_policy(profile, op, NULL, NULL, - "not policy admin", -EACCES); + return audit_policy(label, op, NULL, NULL, "not policy admin", + -EACCES); /* TODO: add fine grained mediation of policy loads */ return 0; @@ -742,8 +726,7 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh, * * Requires: namespace list lock be held, or list not be shared */ -static void __replace_profile(struct aa_profile *old, struct aa_profile *new, - bool share_proxy) +static void __replace_profile(struct aa_profile *old, struct aa_profile *new) { struct aa_profile *child, *tmp; @@ -758,7 +741,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, p = __find_child(&new->base.profiles, child->base.name); if (p) { /* @p replaces @child */ - __replace_profile(child, p, share_proxy); + __replace_profile(child, p); continue; } @@ -776,15 +759,9 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, struct aa_profile *parent = aa_deref_parent(old); rcu_assign_pointer(new->parent, aa_get_profile(parent)); } - __aa_update_proxy(old, new); - if (share_proxy) { - aa_put_proxy(new->proxy); - new->proxy = aa_get_proxy(old->proxy); - } else if (!rcu_access_pointer(new->proxy->profile)) - /* aafs interface uses proxy */ - rcu_assign_pointer(new->proxy->profile, - aa_get_profile(new)); - __aa_fs_profile_migrate_dents(old, new); + aa_label_replace(&old->label, &new->label); + /* migrate dents must come after label replacement b/c update */ + __aafs_profile_migrate_dents(old, new); if (list_empty(&new->base.list)) { /* new is not on a list already */ @@ -821,11 +798,41 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, return 0; } +static void share_name(struct aa_profile *old, struct aa_profile *new) +{ + aa_put_str(new->base.hname); + aa_get_str(old->base.hname); + new->base.hname = old->base.hname; + new->base.name = old->base.name; + new->label.hname = old->label.hname; +} + +/* Update to newest version of parent after previous replacements + * Returns: unrefcount newest version of parent + */ +static struct aa_profile *update_to_newest_parent(struct aa_profile *new) +{ + struct aa_profile *parent, *newest; + + parent = rcu_dereference_protected(new->parent, + mutex_is_locked(&new->ns->lock)); + newest = aa_get_newest_profile(parent); + + /* parent replaced in this atomic set? */ + if (newest != parent) { + aa_put_profile(parent); + rcu_assign_pointer(new->parent, newest); + } else + aa_put_profile(newest); + + return newest; +} + /** * aa_replace_profiles - replace profile(s) on the profile list - * @view: namespace load is viewed from + * @policy_ns: namespace load is occurring on * @label: label that is attempting to load/replace policy - * @noreplace: true if only doing addition, no replacement allowed + * @mask: permission mask * @udata: serialized data stream (NOT NULL) * * unpack and replace a profile on the profile list and uses of that profile @@ -834,16 +841,19 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, * * Returns: size of data consumed else error code on failure. */ -ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, - bool noreplace, struct aa_loaddata *udata) +ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, + u32 mask, struct aa_loaddata *udata) { const char *ns_name, *info = NULL; struct aa_ns *ns = NULL; struct aa_load_ent *ent, *tmp; - const char *op = OP_PROF_REPL; + struct aa_loaddata *rawdata_ent; + const char *op; ssize_t count, error; LIST_HEAD(lh); + op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; + aa_get_loaddata(udata); /* released below */ error = aa_unpack(udata, &lh, &ns_name); if (error) @@ -874,7 +884,8 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, count++; } if (ns_name) { - ns = aa_prepare_ns(view, ns_name); + ns = aa_prepare_ns(policy_ns ? policy_ns : labels_ns(label), + ns_name); if (IS_ERR(ns)) { op = OP_PROF_LOAD; info = "failed to prepare namespace"; @@ -884,22 +895,38 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, goto fail; } } else - ns = aa_get_ns(view); + ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(label)); mutex_lock(&ns->lock); + /* check for duplicate rawdata blobs: space and file dedup */ + list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) { + if (aa_rawdata_eq(rawdata_ent, udata)) { + struct aa_loaddata *tmp; + + tmp = __aa_get_loaddata(rawdata_ent); + /* check we didn't fail the race */ + if (tmp) { + aa_put_loaddata(udata); + udata = tmp; + break; + } + } + } /* setup parent and ns info */ list_for_each_entry(ent, &lh, list) { struct aa_policy *policy; + ent->new->rawdata = aa_get_loaddata(udata); - error = __lookup_replace(ns, ent->new->base.hname, noreplace, + error = __lookup_replace(ns, ent->new->base.hname, + !(mask & AA_MAY_REPLACE_POLICY), &ent->old, &info); if (error) goto fail_lock; if (ent->new->rename) { error = __lookup_replace(ns, ent->new->rename, - noreplace, &ent->rename, - &info); + !(mask & AA_MAY_REPLACE_POLICY), + &ent->rename, &info); if (error) goto fail_lock; } @@ -929,15 +956,16 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, } /* create new fs entries for introspection if needed */ + if (!udata->dents[AAFS_LOADDATA_DIR]) { + error = __aa_fs_create_rawdata(ns, udata); + if (error) { + info = "failed to create raw_data dir and files"; + ent = NULL; + goto fail_lock; + } + } list_for_each_entry(ent, &lh, list) { - if (ent->old) { - /* inherit old interface files */ - - /* if (ent->rename) - TODO: support rename */ - /* } else if (ent->rename) { - TODO: support rename */ - } else { + if (!ent->old) { struct dentry *parent; if (rcu_access_pointer(ent->new->parent)) { struct aa_profile *p; @@ -945,65 +973,61 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, parent = prof_child_dir(p); } else parent = ns_subprofs_dir(ent->new->ns); - error = __aa_fs_profile_mkdir(ent->new, parent); + error = __aafs_profile_mkdir(ent->new, parent); } if (error) { - info = "failed to create "; + info = "failed to create"; goto fail_lock; } } /* Done with checks that may fail - do actual replacement */ + __aa_bump_ns_revision(ns); + __aa_loaddata_update(udata, ns->revision); list_for_each_entry_safe(ent, tmp, &lh, list) { list_del_init(&ent->list); op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL; - audit_policy(profile, op, NULL, ent->new->base.hname, - NULL, error); + if (ent->old && ent->old->rawdata == ent->new->rawdata) { + /* dedup actual profile replacement */ + audit_policy(label, op, ns_name, ent->new->base.hname, + "same as current profile, skipping", + error); + goto skip; + } + + /* + * TODO: finer dedup based on profile range in data. Load set + * can differ but profile may remain unchanged + */ + audit_policy(label, op, ns_name, ent->new->base.hname, NULL, + error); if (ent->old) { - __replace_profile(ent->old, ent->new, 1); - if (ent->rename) { - /* aafs interface uses proxy */ - struct aa_proxy *r = ent->new->proxy; - rcu_assign_pointer(r->profile, - aa_get_profile(ent->new)); - __replace_profile(ent->rename, ent->new, 0); - } - } else if (ent->rename) { - /* aafs interface uses proxy */ - rcu_assign_pointer(ent->new->proxy->profile, - aa_get_profile(ent->new)); - __replace_profile(ent->rename, ent->new, 0); - } else if (ent->new->parent) { - struct aa_profile *parent, *newest; - parent = aa_deref_parent(ent->new); - newest = aa_get_newest_profile(parent); - - /* parent replaced in this atomic set? */ - if (newest != parent) { - aa_get_profile(newest); - rcu_assign_pointer(ent->new->parent, newest); - aa_put_profile(parent); - } - /* aafs interface uses proxy */ - rcu_assign_pointer(ent->new->proxy->profile, - aa_get_profile(ent->new)); - __list_add_profile(&newest->base.profiles, ent->new); - aa_put_profile(newest); + share_name(ent->old, ent->new); + __replace_profile(ent->old, ent->new); } else { - /* aafs interface uses proxy */ - rcu_assign_pointer(ent->new->proxy->profile, - aa_get_profile(ent->new)); - __list_add_profile(&ns->base.profiles, ent->new); + struct list_head *lh; + + if (rcu_access_pointer(ent->new->parent)) { + struct aa_profile *parent; + + parent = update_to_newest_parent(ent->new); + lh = &parent->base.profiles; + } else + lh = &ns->base.profiles; + __add_profile(lh, ent->new); } + skip: aa_load_ent_free(ent); } + __aa_labelset_update_subtree(ns); mutex_unlock(&ns->lock); out: aa_put_ns(ns); + aa_put_loaddata(udata); if (error) return error; @@ -1013,10 +1037,10 @@ fail_lock: mutex_unlock(&ns->lock); /* audit cause of failure */ - op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL; + op = (ent && !ent->old) ? OP_PROF_LOAD : OP_PROF_REPL; fail: - audit_policy(profile, op, ns_name, ent ? ent->new->base.hname : NULL, - info, error); + audit_policy(label, op, ns_name, ent ? ent->new->base.hname : NULL, + info, error); /* audit status that rest of profiles in the atomic set failed too */ info = "valid profile in failed atomic policy load"; list_for_each_entry(tmp, &lh, list) { @@ -1026,8 +1050,8 @@ fail: continue; } op = (!tmp->old) ? OP_PROF_LOAD : OP_PROF_REPL; - audit_policy(profile, op, ns_name, - tmp->new->base.hname, info, error); + audit_policy(label, op, ns_name, tmp->new->base.hname, info, + error); } list_for_each_entry_safe(ent, tmp, &lh, list) { list_del_init(&ent->list); @@ -1039,8 +1063,8 @@ fail: /** * aa_remove_profiles - remove profile(s) from the system - * @view: namespace the remove is being done from - * @subj: profile attempting to remove policy + * @policy_ns: namespace the remove is being done from + * @subj: label attempting to remove policy * @fqname: name of the profile or namespace to remove (NOT NULL) * @size: size of the name * @@ -1051,13 +1075,13 @@ fail: * * Returns: size of data consume else error code if fails */ -ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, +ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj, char *fqname, size_t size) { - struct aa_ns *root = NULL, *ns = NULL; + struct aa_ns *ns = NULL; struct aa_profile *profile = NULL; const char *name = fqname, *info = NULL; - char *ns_name = NULL; + const char *ns_name = NULL; ssize_t error = 0; if (*fqname == 0) { @@ -1066,12 +1090,13 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, goto fail; } - root = view; - if (fqname[0] == ':') { - name = aa_split_fqname(fqname, &ns_name); + size_t ns_len; + + name = aa_splitn_fqname(fqname, size, &ns_name, &ns_len); /* released below */ - ns = aa_find_ns(root, ns_name); + ns = aa_lookupn_ns(policy_ns ? policy_ns : labels_ns(subj), + ns_name, ns_len); if (!ns) { info = "namespace does not exist"; error = -ENOENT; @@ -1079,12 +1104,13 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, } } else /* released below */ - ns = aa_get_ns(root); + ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(subj)); if (!name) { /* remove namespace - can only happen if fqname[0] == ':' */ mutex_lock(&ns->parent->lock); __aa_remove_ns(ns); + __aa_bump_ns_revision(ns); mutex_unlock(&ns->parent->lock); } else { /* remove profile */ @@ -1097,6 +1123,8 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, } name = profile->base.hname; __remove_profile(profile); + __aa_labelset_update_subtree(ns); + __aa_bump_ns_revision(ns); mutex_unlock(&ns->lock); } diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 93d1826c4b09..351d3bab3a3d 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -23,6 +23,7 @@ #include "include/apparmor.h" #include "include/context.h" #include "include/policy_ns.h" +#include "include/label.h" #include "include/policy.h" /* root profile namespace */ @@ -99,15 +100,17 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) goto fail_ns; INIT_LIST_HEAD(&ns->sub_ns); + INIT_LIST_HEAD(&ns->rawdata_list); mutex_init(&ns->lock); + init_waitqueue_head(&ns->wait); /* released by aa_free_ns() */ - ns->unconfined = aa_alloc_profile("unconfined", GFP_KERNEL); + ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL); if (!ns->unconfined) goto fail_unconfined; - ns->unconfined->flags = PFLAG_IX_ON_NAME_ERROR | - PFLAG_IMMUTABLE | PFLAG_NS_COUNT; + ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR | + FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED; ns->unconfined->mode = APPARMOR_UNCONFINED; /* ns and ns->unconfined share ns->unconfined refcount */ @@ -115,6 +118,8 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) atomic_set(&ns->uniq_null, 0); + aa_labelset_init(&ns->labels); + return ns; fail_unconfined: @@ -137,6 +142,7 @@ void aa_free_ns(struct aa_ns *ns) return; aa_policy_destroy(&ns->base); + aa_labelset_destroy(&ns->labels); aa_put_ns(ns->parent); ns->unconfined->ns = NULL; @@ -181,6 +187,60 @@ struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) return aa_findn_ns(root, name, strlen(name)); } +/** + * __aa_lookupn_ns - lookup the namespace matching @hname + * @base: base list to start looking up profile name from (NOT NULL) + * @hname: hierarchical ns name (NOT NULL) + * @n: length of @hname + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted ns pointer or NULL if not found + * + * Do a relative name lookup, recursing through profile tree. + */ +struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n) +{ + struct aa_ns *ns = view; + const char *split; + + for (split = strnstr(hname, "//", n); split; + split = strnstr(hname, "//", n)) { + ns = __aa_findn_ns(&ns->sub_ns, hname, split - hname); + if (!ns) + return NULL; + + n -= split + 2 - hname; + hname = split + 2; + } + + if (n) + return __aa_findn_ns(&ns->sub_ns, hname, n); + return NULL; +} + +/** + * aa_lookupn_ns - look up a policy namespace relative to @view + * @view: namespace to search in (NOT NULL) + * @name: name of namespace to find (NOT NULL) + * @n: length of @name + * + * Returns: a refcounted namespace on the list, or NULL if no namespace + * called @name exists. + * + * refcount released by caller + */ +struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n) +{ + struct aa_ns *ns = NULL; + + rcu_read_lock(); + ns = aa_get_ns(__aa_lookupn_ns(view, name, n)); + rcu_read_unlock(); + + return ns; +} + static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, struct dentry *dir) { @@ -195,7 +255,7 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, if (!ns) return NULL; mutex_lock(&ns->lock); - error = __aa_fs_ns_mkdir(ns, ns_subns_dir(parent), name); + error = __aafs_ns_mkdir(ns, ns_subns_dir(parent), name, dir); if (error) { AA_ERROR("Failed to create interface for ns %s\n", ns->base.name); @@ -281,9 +341,15 @@ static void destroy_ns(struct aa_ns *ns) /* release all sub namespaces */ __ns_list_release(&ns->sub_ns); - if (ns->parent) - __aa_update_proxy(ns->unconfined, ns->parent->unconfined); - __aa_fs_ns_rmdir(ns); + if (ns->parent) { + unsigned long flags; + + write_lock_irqsave(&ns->labels.lock, flags); + __aa_proxy_redirect(ns_unconfined(ns), + ns_unconfined(ns->parent)); + write_unlock_irqrestore(&ns->labels.lock, flags); + } + __aafs_ns_rmdir(ns); mutex_unlock(&ns->lock); } diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 981d570eebba..c600f4dd1783 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -26,6 +26,7 @@ #include "include/context.h" #include "include/crypto.h" #include "include/match.h" +#include "include/path.h" #include "include/policy.h" #include "include/policy_unpack.h" @@ -107,7 +108,7 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, const char *name, const char *info, struct aa_ext *e, int error) { - struct aa_profile *profile = __aa_current_profile(); + struct aa_profile *profile = labels_profile(aa_current_raw_label()); DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); if (e) aad(&sa)->iface.pos = e->pos - e->start; @@ -122,16 +123,73 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); } +void __aa_loaddata_update(struct aa_loaddata *data, long revision) +{ + AA_BUG(!data); + AA_BUG(!data->ns); + AA_BUG(!data->dents[AAFS_LOADDATA_REVISION]); + AA_BUG(!mutex_is_locked(&data->ns->lock)); + AA_BUG(data->revision > revision); + + data->revision = revision; + d_inode(data->dents[AAFS_LOADDATA_DIR])->i_mtime = + current_time(d_inode(data->dents[AAFS_LOADDATA_DIR])); + d_inode(data->dents[AAFS_LOADDATA_REVISION])->i_mtime = + current_time(d_inode(data->dents[AAFS_LOADDATA_REVISION])); +} + +bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r) +{ + if (l->size != r->size) + return false; + if (aa_g_hash_policy && memcmp(l->hash, r->hash, aa_hash_size()) != 0) + return false; + return memcmp(l->data, r->data, r->size) == 0; +} + +/* + * need to take the ns mutex lock which is NOT safe most places that + * put_loaddata is called, so we have to delay freeing it + */ +static void do_loaddata_free(struct work_struct *work) +{ + struct aa_loaddata *d = container_of(work, struct aa_loaddata, work); + struct aa_ns *ns = aa_get_ns(d->ns); + + if (ns) { + mutex_lock(&ns->lock); + __aa_fs_remove_rawdata(d); + mutex_unlock(&ns->lock); + aa_put_ns(ns); + } + + kzfree(d->hash); + kfree(d->name); + kvfree(d); +} + void aa_loaddata_kref(struct kref *kref) { struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count); if (d) { - kzfree(d->hash); - kvfree(d); + INIT_WORK(&d->work, do_loaddata_free); + schedule_work(&d->work); } } +struct aa_loaddata *aa_loaddata_alloc(size_t size) +{ + struct aa_loaddata *d = kvzalloc(sizeof(*d) + size, GFP_KERNEL); + + if (d == NULL) + return ERR_PTR(-ENOMEM); + kref_init(&d->count); + INIT_LIST_HEAD(&d->list); + + return d; +} + /* test if read will be in packed data bounds */ static bool inbounds(struct aa_ext *e, size_t size) { @@ -408,7 +466,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) profile->file.trans.size = size; for (i = 0; i < size; i++) { char *str; - int c, j, size2 = unpack_strdup(e, &str, NULL); + int c, j, pos, size2 = unpack_strdup(e, &str, NULL); /* unpack_strdup verifies that the last character is * null termination byte. */ @@ -420,19 +478,25 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) goto fail; /* count internal # of internal \0 */ - for (c = j = 0; j < size2 - 2; j++) { - if (!str[j]) + for (c = j = 0; j < size2 - 1; j++) { + if (!str[j]) { + pos = j; c++; + } } if (*str == ':') { + /* first character after : must be valid */ + if (!str[1]) + goto fail; /* beginning with : requires an embedded \0, * verify that exactly 1 internal \0 exists * trailing \0 already verified by unpack_strdup + * + * convert \0 back to : for label_parse */ - if (c != 1) - goto fail; - /* first character after : must be valid */ - if (!str[1]) + if (c == 1) + str[pos] = ':'; + else if (c > 1) goto fail; } else if (c) /* fail - all other cases with embedded \0 */ @@ -545,7 +609,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) name = tmpname; } - profile = aa_alloc_profile(name, GFP_KERNEL); + profile = aa_alloc_profile(name, NULL, GFP_KERNEL); if (!profile) return ERR_PTR(-ENOMEM); @@ -569,13 +633,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) profile->xmatch_len = tmp; } + /* disconnected attachment string is optional */ + (void) unpack_str(e, &profile->disconnected, "disconnected"); + /* per profile debug flags (complain, audit) */ if (!unpack_nameX(e, AA_STRUCT, "flags")) goto fail; if (!unpack_u32(e, &tmp, NULL)) goto fail; if (tmp & PACKED_FLAG_HAT) - profile->flags |= PFLAG_HAT; + profile->label.flags |= FLAG_HAT; if (!unpack_u32(e, &tmp, NULL)) goto fail; if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) @@ -594,10 +661,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) /* path_flags is optional */ if (unpack_u32(e, &profile->path_flags, "path_flags")) - profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED; + profile->path_flags |= profile->label.flags & + PATH_MEDIATE_DELETED; else /* set a default value if path_flags field is not present */ - profile->path_flags = PFLAG_MEDIATE_DELETED; + profile->path_flags = PATH_MEDIATE_DELETED; if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) goto fail; diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index 3466a27bca09..d81617379d63 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -34,50 +34,41 @@ * * Returns: size of string placed in @string else error code on failure */ -int aa_getprocattr(struct aa_profile *profile, char **string) +int aa_getprocattr(struct aa_label *label, char **string) { - char *str; - int len = 0, mode_len = 0, ns_len = 0, name_len; - const char *mode_str = aa_profile_mode_names[profile->mode]; - const char *ns_name = NULL; - struct aa_ns *ns = profile->ns; - struct aa_ns *current_ns = __aa_current_profile()->ns; - char *s; - - if (!aa_ns_visible(current_ns, ns, true)) - return -EACCES; - - ns_name = aa_ns_name(current_ns, ns, true); - ns_len = strlen(ns_name); + struct aa_ns *ns = labels_ns(label); + struct aa_ns *current_ns = aa_get_current_ns(); + int len; - /* if the visible ns_name is > 0 increase size for : :// seperator */ - if (ns_len) - ns_len += 4; + if (!aa_ns_visible(current_ns, ns, true)) { + aa_put_ns(current_ns); + return -EACCES; + } - /* unconfined profiles don't have a mode string appended */ - if (!unconfined(profile)) - mode_len = strlen(mode_str) + 3; /* + 3 for _() */ + len = aa_label_snxprint(NULL, 0, current_ns, label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | + FLAG_HIDDEN_UNCONFINED); + AA_BUG(len < 0); - name_len = strlen(profile->base.hname); - len = mode_len + ns_len + name_len + 1; /* + 1 for \n */ - s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */ - if (!str) + *string = kmalloc(len + 2, GFP_KERNEL); + if (!*string) { + aa_put_ns(current_ns); return -ENOMEM; + } - if (ns_len) { - /* skip over prefix current_ns->base.hname and separating // */ - sprintf(s, ":%s://", ns_name); - s += ns_len; + len = aa_label_snxprint(*string, len + 2, current_ns, label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | + FLAG_HIDDEN_UNCONFINED); + if (len < 0) { + aa_put_ns(current_ns); + return len; } - if (unconfined(profile)) - /* mode string not being appended */ - sprintf(s, "%s\n", profile->base.hname); - else - sprintf(s, "%s (%s)\n", profile->base.hname, mode_str); - *string = str; - - /* NOTE: len does not include \0 of string, not saved as part of file */ - return len; + + (*string)[len] = '\n'; + (*string)[len + 1] = 0; + + aa_put_ns(current_ns); + return len + 1; } /** @@ -108,11 +99,11 @@ static char *split_token_from_name(const char *op, char *args, u64 *token) * aa_setprocattr_chagnehat - handle procattr interface to change_hat * @args: args received from writing to /proc/<pid>/attr/current (NOT NULL) * @size: size of the args - * @test: true if this is a test of change_hat permissions + * @flags: set of flags governing behavior * * Returns: %0 or error code if change_hat fails */ -int aa_setprocattr_changehat(char *args, size_t size, int test) +int aa_setprocattr_changehat(char *args, size_t size, int flags) { char *hat; u64 token; @@ -147,5 +138,5 @@ int aa_setprocattr_changehat(char *args, size_t size, int test) AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", __func__, current->pid, token, count, "<NULL>"); - return aa_change_hat(hats, count, token, test); + return aa_change_hat(hats, count, token, flags); } diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index 86a941afd956..d8bc842594ed 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -13,6 +13,7 @@ */ #include <linux/audit.h> +#include <linux/security.h> #include "include/audit.h" #include "include/context.h" @@ -24,8 +25,8 @@ */ #include "rlim_names.h" -struct aa_fs_entry aa_fs_entry_rlimit[] = { - AA_FS_FILE_STRING("mask", AA_FS_RLIMIT_MASK), +struct aa_sfs_entry aa_sfs_entry_rlimit[] = { + AA_SFS_FILE_STRING("mask", AA_SFS_RLIMIT_MASK), { } }; @@ -36,6 +37,11 @@ static void audit_cb(struct audit_buffer *ab, void *va) audit_log_format(ab, " rlimit=%s value=%lu", rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max); + if (aad(sa)->peer) { + audit_log_format(ab, " peer="); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + FLAGS_NONE, GFP_ATOMIC); + } } /** @@ -48,13 +54,17 @@ static void audit_cb(struct audit_buffer *ab, void *va) * Returns: 0 or sa->error else other error code on failure */ static int audit_resource(struct aa_profile *profile, unsigned int resource, - unsigned long value, int error) + unsigned long value, struct aa_label *peer, + const char *info, int error) { DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT); aad(&sa)->rlim.rlim = resource; aad(&sa)->rlim.max = value; + aad(&sa)->peer = peer; + aad(&sa)->info = info; aad(&sa)->error = error; + return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb); } @@ -72,9 +82,21 @@ int aa_map_resource(int resource) return rlim_map[resource]; } +static int profile_setrlimit(struct aa_profile *profile, unsigned int resource, + struct rlimit *new_rlim) +{ + int e = 0; + + if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max > + profile->rlimits.limits[resource].rlim_max) + e = -EACCES; + return audit_resource(profile, resource, new_rlim->rlim_max, NULL, NULL, + e); +} + /** * aa_task_setrlimit - test permission to set an rlimit - * @profile - profile confining the task (NOT NULL) + * @label - label confining the task (NOT NULL) * @task - task the resource is being set on * @resource - the resource being set * @new_rlim - the new resource limit (NOT NULL) @@ -83,14 +105,15 @@ int aa_map_resource(int resource) * * Returns: 0 or error code if setting resource failed */ -int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, +int aa_task_setrlimit(struct aa_label *label, struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_profile *task_profile; + struct aa_profile *profile; + struct aa_label *peer; int error = 0; rcu_read_lock(); - task_profile = aa_get_profile(aa_cred_profile(__task_cred(task))); + peer = aa_get_newest_cred_label(__task_cred(task)); rcu_read_unlock(); /* TODO: extend resource control to handle other (non current) @@ -99,53 +122,70 @@ int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, * the same profile or that the task setting the resource of another * task has CAP_SYS_RESOURCE. */ - if ((profile != task_profile && - aa_capable(profile, CAP_SYS_RESOURCE, 1)) || - (profile->rlimits.mask & (1 << resource) && - new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)) - error = -EACCES; - aa_put_profile(task_profile); - - return audit_resource(profile, resource, new_rlim->rlim_max, error); + if (label != peer && + !aa_capable(label, CAP_SYS_RESOURCE, SECURITY_CAP_NOAUDIT)) + error = fn_for_each(label, profile, + audit_resource(profile, resource, + new_rlim->rlim_max, peer, + "cap_sys_resoure", -EACCES)); + else + error = fn_for_each_confined(label, profile, + profile_setrlimit(profile, resource, new_rlim)); + aa_put_label(peer); + + return error; } /** * __aa_transition_rlimits - apply new profile rlimits - * @old: old profile on task (NOT NULL) - * @new: new profile with rlimits to apply (NOT NULL) + * @old_l: old label on task (NOT NULL) + * @new_l: new label with rlimits to apply (NOT NULL) */ -void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new) +void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) { unsigned int mask = 0; struct rlimit *rlim, *initrlim; - int i; + struct aa_profile *old, *new; + struct label_it i; + + old = labels_profile(old_l); + new = labels_profile(new_l); - /* for any rlimits the profile controlled reset the soft limit - * to the less of the tasks hard limit and the init tasks soft limit + /* for any rlimits the profile controlled, reset the soft limit + * to the lesser of the tasks hard limit and the init tasks soft limit */ - if (old->rlimits.mask) { - for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { - if (old->rlimits.mask & mask) { - rlim = current->signal->rlim + i; - initrlim = init_task.signal->rlim + i; - rlim->rlim_cur = min(rlim->rlim_max, - initrlim->rlim_cur); + label_for_each_confined(i, old_l, old) { + if (old->rlimits.mask) { + int j; + + for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, + mask <<= 1) { + if (old->rlimits.mask & mask) { + rlim = current->signal->rlim + j; + initrlim = init_task.signal->rlim + j; + rlim->rlim_cur = min(rlim->rlim_max, + initrlim->rlim_cur); + } } } } /* set any new hard limits as dictated by the new profile */ - if (!new->rlimits.mask) - return; - for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { - if (!(new->rlimits.mask & mask)) - continue; + label_for_each_confined(i, new_l, new) { + int j; - rlim = current->signal->rlim + i; - rlim->rlim_max = min(rlim->rlim_max, - new->rlimits.limits[i].rlim_max); - /* soft limit should not exceed hard limit */ - rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + if (!new->rlimits.mask) + continue; + for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { + if (!(new->rlimits.mask & mask)) + continue; + + rlim = current->signal->rlim + j; + rlim->rlim_max = min(rlim->rlim_max, + new->rlimits.limits[j].rlim_max); + /* soft limit should not exceed hard limit */ + rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + } } } |