diff options
Diffstat (limited to 'security')
68 files changed, 8342 insertions, 2111 deletions
diff --git a/security/Kconfig b/security/Kconfig index 93027fdf47d1..d540bfe73190 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -54,6 +54,15 @@ config SECURITY_NETWORK implement socket and networking access controls. If you are unsure how to answer this question, answer N. +config SECURITY_INFINIBAND + bool "Infiniband Security Hooks" + depends on SECURITY && INFINIBAND + help + This enables the Infiniband security hooks. + If enabled, a security module can use these hooks to + implement Infiniband access controls. + If you are unsure how to answer this question, answer N. + config SECURITY_NETWORK_XFRM bool "XFRM (IPSec) Networking Security Hooks" depends on XFRM && SECURITY_NETWORK @@ -139,7 +148,7 @@ config HARDENED_USERCOPY copying memory to/from the kernel (via copy_to_user() and copy_from_user() functions) by rejecting memory ranges that are larger than the specified heap object, span multiple - separately allocates pages, are not on the process stack, + separately allocated pages, are not on the process stack, or are part of the kernel text. This kills entire classes of heap overflow exploits and similar kernel memory exposures. 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, |