diff options
Diffstat (limited to 'security/apparmor')
36 files changed, 9012 insertions, 0 deletions
diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore new file mode 100644 index 000000000..9cdec70d7 --- /dev/null +++ b/security/apparmor/.gitignore @@ -0,0 +1,5 @@ +# +# Generated include files +# +capability_names.h +rlim_names.h diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig new file mode 100644 index 000000000..d49c53960 --- /dev/null +++ b/security/apparmor/Kconfig @@ -0,0 +1,43 @@ +config SECURITY_APPARMOR + bool "AppArmor support" + depends on SECURITY && NET + select AUDIT + select SECURITY_PATH + select SECURITYFS + select SECURITY_NETWORK + default n + help + This enables the AppArmor security module. + Required userspace tools (if they are not included in your + distribution) and further information may be found at + http://apparmor.wiki.kernel.org + + If you are unsure how to answer this question, answer N. + +config SECURITY_APPARMOR_BOOTPARAM_VALUE + int "AppArmor boot parameter default value" + depends on SECURITY_APPARMOR + range 0 1 + default 1 + help + This option sets the default value for the kernel parameter + 'apparmor', which allows AppArmor to be enabled or disabled + at boot. If this option is set to 0 (zero), the AppArmor + kernel parameter will default to 0, disabling AppArmor at + boot. If this option is set to 1 (one), the AppArmor + kernel parameter will default to 1, enabling AppArmor at + boot. + + If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_HASH + bool "SHA1 hash of loaded profiles" + depends on SECURITY_APPARMOR + depends on CRYPTO + select CRYPTO_SHA1 + default y + + help + This option selects whether sha1 hashing is done against loaded + profiles and exported for inspection to user space via the apparmor + filesystem. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile new file mode 100644 index 000000000..d693df874 --- /dev/null +++ b/security/apparmor/Makefile @@ -0,0 +1,70 @@ +# Makefile for AppArmor Linux Security Module +# +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 sid.o file.o +apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o + +clean-files := capability_names.h rlim_names.h + + +# Build a lower case string table of capability names +# Transforms lines from +# #define CAP_DAC_OVERRIDE 1 +# to +# [1] = "dac_override", +quiet_cmd_make-caps = GEN $@ +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 "' >> $@ ;\ + 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/' >> $@ + + +# Build a lower case string table of rlimit names. +# Transforms lines from +# #define RLIMIT_STACK 3 /* max stack size */ +# to +# [RLIMIT_STACK] = "stack", +# +# and build a second integer table (with the second sed cmd), that maps +# RLIMIT defines to the order defined in asm-generic/resource.h This is +# required by policy load to map policy ordering of RLIMITs to internal +# ordering for architectures that redefine an RLIMIT. +# Transforms lines from +# #define RLIMIT_STACK 3 /* max stack size */ +# to +# RLIMIT_STACK, +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define RLIMIT_FSIZE 1 /* Maximum filesize */ +# #define RLIMIT_STACK 3 /* max stack size */ +# to +# #define AA_FS_RLIMIT_MASK "fsize stack" +quiet_cmd_make-rlim = GEN $@ +cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ + > $@ ;\ + sed $< >> $@ -r -n \ + -e 's/^\# ?define[ \t]+(RLIMIT_([A-Z0-9_]+)).*/[\1] = "\L\2",/p';\ + echo "};" >> $@ ;\ + 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 "' >> $@ ;\ + sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \ + tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +$(obj)/capability.o : $(obj)/capability_names.h +$(obj)/resource.o : $(obj)/rlim_names.h +$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ + $(src)/Makefile + $(call cmd,make-caps) +$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ + $(src)/Makefile + $(call cmd,make-rlim) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c new file mode 100644 index 000000000..ad4fa49ad --- /dev/null +++ b/security/apparmor/apparmorfs.c @@ -0,0 +1,969 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/security/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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/ctype.h> +#include <linux/security.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/namei.h> +#include <linux/capability.h> +#include <linux/rcupdate.h> + +#include "include/apparmor.h" +#include "include/apparmorfs.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/crypto.h" +#include "include/policy.h" +#include "include/resource.h" + +/** + * aa_mangle_name - mangle a profile name to std profile layout form + * @name: profile name to mangle (NOT NULL) + * @target: buffer to store mangled name, same length as @name (MAYBE NULL) + * + * Returns: length of mangled name + */ +static int mangle_name(char *name, char *target) +{ + char *t = target; + + while (*name == '/' || *name == '.') + name++; + + if (target) { + for (; *name; name++) { + if (*name == '/') + *(t)++ = '.'; + else if (isspace(*name)) + *(t)++ = '_'; + else if (isalnum(*name) || strchr("._-", *name)) + *(t)++ = *name; + } + + *t = 0; + } else { + int len = 0; + for (; *name; name++) { + if (isalnum(*name) || isspace(*name) || + strchr("/._-", *name)) + len++; + } + + return len; + } + + return t - target; +} + +/** + * aa_simple_write_to_buffer - common routine for getting policy from user + * @op: operation doing the user buffer copy + * @userbuf: user buffer to copy data from (NOT NULL) + * @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size) + * @copy_size: size of data to copy from user buffer + * @pos: position write is at in the file (NOT NULL) + * + * Returns: kernel buffer containing copy of user buffer data or an + * ERR_PTR on failure. + */ +static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, + size_t alloc_size, size_t copy_size, + loff_t *pos) +{ + char *data; + + BUG_ON(copy_size > alloc_size); + + if (*pos != 0) + /* only writes from pos 0, that is complete writes */ + return ERR_PTR(-ESPIPE); + + /* + * Don't allow profile load/replace/remove from profiles that don't + * have CAP_MAC_ADMIN + */ + if (!aa_may_manage_policy(op)) + return ERR_PTR(-EACCES); + + /* freed by caller to simple_write_to_buffer */ + data = kvmalloc(alloc_size); + if (data == NULL) + return ERR_PTR(-ENOMEM); + + if (copy_from_user(data, userbuf, copy_size)) { + kvfree(data); + return ERR_PTR(-EFAULT); + } + + return data; +} + + +/* .load file hook fn to load policy */ +static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, + loff_t *pos) +{ + char *data; + ssize_t error; + + data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos); + + error = PTR_ERR(data); + if (!IS_ERR(data)) { + error = aa_replace_profiles(data, size, PROF_ADD); + kvfree(data); + } + + return error; +} + +static const struct file_operations aa_fs_profile_load = { + .write = profile_load, + .llseek = default_llseek, +}; + +/* .replace file hook fn to load and/or replace policy */ +static ssize_t profile_replace(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos); + error = PTR_ERR(data); + if (!IS_ERR(data)) { + error = aa_replace_profiles(data, size, PROF_REPLACE); + kvfree(data); + } + + return error; +} + +static const struct file_operations aa_fs_profile_replace = { + .write = profile_replace, + .llseek = default_llseek, +}; + +/* .remove file hook fn to remove loaded policy */ +static ssize_t profile_remove(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + /* + * aa_remove_profile needs a null terminated string so 1 extra + * byte is allocated and the copied data is null terminated. + */ + data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos); + + error = PTR_ERR(data); + if (!IS_ERR(data)) { + data[size] = 0; + error = aa_remove_profiles(data, size); + kvfree(data); + } + + return error; +} + +static const struct file_operations aa_fs_profile_remove = { + .write = profile_remove, + .llseek = default_llseek, +}; + +static int aa_fs_seq_show(struct seq_file *seq, void *v) +{ + struct aa_fs_entry *fs_file = seq->private; + + if (!fs_file) + return 0; + + switch (fs_file->v_type) { + case AA_FS_TYPE_BOOLEAN: + seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); + break; + case AA_FS_TYPE_STRING: + seq_printf(seq, "%s\n", fs_file->v.string); + break; + case AA_FS_TYPE_U64: + seq_printf(seq, "%#08lx\n", fs_file->v.u64); + break; + default: + /* Ignore unpritable entry types. */ + break; + } + + return 0; +} + +static int aa_fs_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, aa_fs_seq_show, inode->i_private); +} + +const struct file_operations aa_fs_seq_file_ops = { + .owner = THIS_MODULE, + .open = aa_fs_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 *)) +{ + struct aa_replacedby *r = aa_get_replacedby(inode->i_private); + int error = single_open(file, show, r); + + if (error) { + file->private_data = NULL; + aa_put_replacedby(r); + } + + return error; +} + +static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) +{ + struct seq_file *seq = (struct seq_file *) file->private_data; + if (seq) + aa_put_replacedby(seq->private); + return single_release(inode, file); +} + +static int aa_fs_seq_profname_show(struct seq_file *seq, void *v) +{ + struct aa_replacedby *r = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&r->profile); + seq_printf(seq, "%s\n", profile->base.name); + aa_put_profile(profile); + + 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) +{ + struct aa_replacedby *r = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&r->profile); + seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]); + aa_put_profile(profile); + + 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) +{ + struct aa_replacedby *r = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&r->profile); + 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); + + 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) +{ + struct aa_replacedby *r = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&r->profile); + 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"); + } + + return 0; +} + +static int aa_fs_seq_hash_open(struct inode *inode, struct file *file) +{ + return single_open(file, aa_fs_seq_hash_show, inode->i_private); +} + +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, +}; + +/** fns to setup dynamic per profile/namespace files **/ +void __aa_fs_profile_rmdir(struct aa_profile *profile) +{ + struct aa_profile *child; + int i; + + if (!profile) + return; + + list_for_each_entry(child, &profile->base.profiles, base.list) + __aa_fs_profile_rmdir(child); + + for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) { + struct aa_replacedby *r; + if (!profile->dents[i]) + continue; + + r = d_inode(profile->dents[i])->i_private; + securityfs_remove(profile->dents[i]); + aa_put_replacedby(r); + profile->dents[i] = NULL; + } +} + +void __aa_fs_profile_migrate_dents(struct aa_profile *old, + struct aa_profile *new) +{ + int i; + + for (i = 0; i < AAFS_PROF_SIZEOF; i++) { + new->dents[i] = old->dents[i]; + old->dents[i] = NULL; + } +} + +static struct dentry *create_profile_file(struct dentry *dir, const char *name, + struct aa_profile *profile, + const struct file_operations *fops) +{ + struct aa_replacedby *r = aa_get_replacedby(profile->replacedby); + struct dentry *dent; + + dent = securityfs_create_file(name, S_IFREG | 0444, dir, r, fops); + if (IS_ERR(dent)) + aa_put_replacedby(r); + + return dent; +} + +/* requires lock be held */ +int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) +{ + struct aa_profile *child; + struct dentry *dent = NULL, *dir; + int error; + + if (!parent) { + struct aa_profile *p; + p = aa_deref_parent(profile); + dent = prof_dir(p); + /* adding to parent that previously didn't have children */ + dent = securityfs_create_dir("profiles", dent); + if (IS_ERR(dent)) + goto fail; + prof_child_dir(p) = parent = dent; + } + + if (!profile->dirname) { + int len, id_len; + len = mangle_name(profile->base.name, NULL); + id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id); + + profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL); + if (!profile->dirname) + goto fail; + + mangle_name(profile->base.name, profile->dirname); + sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++); + } + + dent = securityfs_create_dir(profile->dirname, parent); + if (IS_ERR(dent)) + goto fail; + prof_dir(profile) = dir = dent; + + dent = create_profile_file(dir, "name", profile, &aa_fs_profname_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_NAME] = dent; + + dent = create_profile_file(dir, "mode", profile, &aa_fs_profmode_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_MODE] = dent; + + dent = create_profile_file(dir, "attach", profile, + &aa_fs_profattach_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); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_HASH] = dent; + } + + list_for_each_entry(child, &profile->base.profiles, base.list) { + error = __aa_fs_profile_mkdir(child, prof_child_dir(profile)); + if (error) + goto fail2; + } + + return 0; + +fail: + error = PTR_ERR(dent); + +fail2: + __aa_fs_profile_rmdir(profile); + + return error; +} + +void __aa_fs_namespace_rmdir(struct aa_namespace *ns) +{ + struct aa_namespace *sub; + struct aa_profile *child; + int i; + + if (!ns) + return; + + list_for_each_entry(child, &ns->base.profiles, base.list) + __aa_fs_profile_rmdir(child); + + list_for_each_entry(sub, &ns->sub_ns, base.list) { + mutex_lock(&sub->lock); + __aa_fs_namespace_rmdir(sub); + mutex_unlock(&sub->lock); + } + + for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { + securityfs_remove(ns->dents[i]); + ns->dents[i] = NULL; + } +} + +int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, + const char *name) +{ + struct aa_namespace *sub; + struct aa_profile *child; + struct dentry *dent, *dir; + int error; + + if (!name) + name = ns->base.name; + + dent = securityfs_create_dir(name, parent); + if (IS_ERR(dent)) + goto fail; + ns_dir(ns) = dir = dent; + + dent = securityfs_create_dir("profiles", dir); + if (IS_ERR(dent)) + goto fail; + ns_subprofs_dir(ns) = dent; + + dent = securityfs_create_dir("namespaces", dir); + if (IS_ERR(dent)) + goto fail; + ns_subns_dir(ns) = dent; + + list_for_each_entry(child, &ns->base.profiles, base.list) { + error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns)); + if (error) + goto fail2; + } + + list_for_each_entry(sub, &ns->sub_ns, base.list) { + mutex_lock(&sub->lock); + error = __aa_fs_namespace_mkdir(sub, ns_subns_dir(ns), NULL); + mutex_unlock(&sub->lock); + if (error) + goto fail2; + } + + return 0; + +fail: + error = PTR_ERR(dent); + +fail2: + __aa_fs_namespace_rmdir(ns); + + return error; +} + + +#define list_entry_next(pos, member) \ + list_entry(pos->member.next, typeof(*pos), member) +#define list_entry_is_head(pos, head, member) (&pos->member == (head)) + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * Requires: ns->parent->lock to be held + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent, *next; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + mutex_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (ns != root) { + mutex_unlock(&ns->lock); + next = list_entry_next(ns, base.list); + if (!list_entry_is_head(next, &parent->sub_ns, base.list)) { + mutex_lock(&next->lock); + return next; + } + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for (; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first traversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, sibling, .. */ + parent = rcu_dereference_protected(p->parent, + mutex_is_locked(&p->ns->lock)); + while (parent) { + p = list_entry_next(p, base.list); + if (!list_entry_is_head(p, &parent->base.profiles, base.list)) + return p; + p = parent; + parent = rcu_dereference_protected(parent->parent, + mutex_is_locked(&parent->ns->lock)); + } + + /* is next another profile in the namespace */ + p = list_entry_next(p, base.list); + if (!list_entry_is_head(p, &ns->base.profiles, base.list)) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + mutex_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *ns = f->private; + (*pos)++; + + return next_profile(ns, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + mutex_unlock(&ns->lock); + } + mutex_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + aa_profile_mode_names[profile->mode]); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +static const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; + + +/** 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_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), + { } +}; + +static struct aa_fs_entry aa_fs_entry_policy[] = { + AA_FS_FILE_BOOLEAN("set_load", 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_fs_entry aa_fs_entry_apparmor[] = { + AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), + AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), + AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), + AA_FS_DIR("features", aa_fs_entry_features), + { } +}; + +static struct aa_fs_entry aa_fs_entry = + AA_FS_DIR("apparmor", aa_fs_entry_apparmor); + +/** + * aafs_create_file - create a file entry in the apparmor securityfs + * @fs_file: aa_fs_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. + */ +static int __init aafs_create_file(struct aa_fs_entry *fs_file, + struct dentry *parent) +{ + int error = 0; + + fs_file->dentry = securityfs_create_file(fs_file->name, + S_IFREG | fs_file->mode, + parent, fs_file, + fs_file->file_ops); + if (IS_ERR(fs_file->dentry)) { + error = PTR_ERR(fs_file->dentry); + fs_file->dentry = NULL; + } + return error; +} + +static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir); +/** + * aafs_create_dir - recursively create a directory entry in the securityfs + * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL) + * @parent: the parent dentry in the securityfs + * + * Use aafs_remove_dir to remove entries created with this fn. + */ +static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, + struct dentry *parent) +{ + struct aa_fs_entry *fs_file; + struct dentry *dir; + int error; + + dir = securityfs_create_dir(fs_dir->name, parent); + if (IS_ERR(dir)) + return PTR_ERR(dir); + fs_dir->dentry = dir; + + for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { + if (fs_file->v_type == AA_FS_TYPE_DIR) + error = aafs_create_dir(fs_file, fs_dir->dentry); + else + error = aafs_create_file(fs_file, fs_dir->dentry); + if (error) + goto failed; + } + + return 0; + +failed: + aafs_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) + */ +static void __init aafs_remove_file(struct aa_fs_entry *fs_file) +{ + if (!fs_file->dentry) + return; + + securityfs_remove(fs_file->dentry); + fs_file->dentry = NULL; +} + +/** + * aafs_remove_dir - recursively drop a directory entry from the securityfs + * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL) + */ +static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) +{ + struct aa_fs_entry *fs_file; + + for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { + if (fs_file->v_type == AA_FS_TYPE_DIR) + aafs_remove_dir(fs_file); + else + aafs_remove_file(fs_file); + } + + aafs_remove_file(fs_dir); +} + +/** + * aa_destroy_aafs - cleanup and free aafs + * + * releases dentries allocated by aa_create_aafs + */ +void __init aa_destroy_aafs(void) +{ + aafs_remove_dir(&aa_fs_entry); +} + +/** + * aa_create_aafs - create the apparmor security filesystem + * + * dentries created here are released by aa_destroy_aafs + * + * Returns: error on failure + */ +static int __init aa_create_aafs(void) +{ + int error; + + if (!apparmor_initialized) + return 0; + + if (aa_fs_entry.dentry) { + AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); + return -EEXIST; + } + + /* Populate fs tree. */ + error = aafs_create_dir(&aa_fs_entry, NULL); + if (error) + goto error; + + error = __aa_fs_namespace_mkdir(root_ns, aa_fs_entry.dentry, + "policy"); + if (error) + goto error; + + /* TODO: add support for apparmorfs_null and apparmorfs_mnt */ + + /* Report that AppArmor fs is enabled */ + aa_info_message("AppArmor Filesystem Enabled"); + return 0; + +error: + aa_destroy_aafs(); + AA_ERROR("Error creating AppArmor securityfs\n"); + return error; +} + +fs_initcall(aa_create_aafs); diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c new file mode 100644 index 000000000..89c786580 --- /dev/null +++ b/security/apparmor/audit.c @@ -0,0 +1,209 @@ +/* + * AppArmor security module + * + * This file contains AppArmor auditing functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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/socket.h> + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/policy.h" + +const char *const op_table[] = { + "null", + + "sysctl", + "capable", + + "unlink", + "mkdir", + "rmdir", + "mknod", + "truncate", + "link", + "symlink", + "rename_src", + "rename_dest", + "chmod", + "chown", + "getattr", + "open", + + "file_perm", + "file_lock", + "file_mmap", + "file_mprotect", + + "create", + "post_create", + "bind", + "connect", + "listen", + "accept", + "sendmsg", + "recvmsg", + "getsockname", + "getpeername", + "getsockopt", + "setsockopt", + "socket_shutdown", + + "ptrace", + + "exec", + "change_hat", + "change_profile", + "change_onexec", + + "setprocattr", + "setrlimit", + + "profile_replace", + "profile_load", + "profile_remove" +}; + +const char *const audit_mode_names[] = { + "normal", + "quiet_denied", + "quiet", + "noquiet", + "all" +}; + +static const char *const aa_audit_type[] = { + "AUDIT", + "ALLOWED", + "DENIED", + "HINT", + "STATUS", + "ERROR", + "KILLED", + "AUTO" +}; + +/* + * Currently AppArmor auditing is fed straight into the audit framework. + * + * TODO: + * netlink interface for complain mode + * user auditing, - send user auditing to netlink interface + * system control of whether user audit messages go to system log + */ + +/** + * audit_base - core AppArmor function. + * @ab: audit buffer to fill (NOT NULL) + * @ca: audit structure containing data to audit (NOT NULL) + * + * Record common AppArmor audit data from @sa + */ +static void audit_pre(struct audit_buffer *ab, void *ca) +{ + struct common_audit_data *sa = ca; + + if (aa_g_audit_header) { + audit_log_format(ab, "apparmor="); + audit_log_string(ab, aa_audit_type[sa->aad->type]); + } + + if (sa->aad->op) { + audit_log_format(ab, " operation="); + audit_log_string(ab, op_table[sa->aad->op]); + } + + if (sa->aad->info) { + audit_log_format(ab, " info="); + audit_log_string(ab, sa->aad->info); + if (sa->aad->error) + audit_log_format(ab, " error=%d", sa->aad->error); + } + + if (sa->aad->profile) { + struct aa_profile *profile = sa->aad->profile; + 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); + } + + if (sa->aad->name) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, sa->aad->name); + } +} + +/** + * aa_audit_msg - Log a message to the audit subsystem + * @sa: audit event structure (NOT NULL) + * @cb: optional callback fn for type specific fields (MAYBE NULL) + */ +void aa_audit_msg(int type, struct common_audit_data *sa, + void (*cb) (struct audit_buffer *, void *)) +{ + sa->aad->type = type; + common_lsm_audit(sa, audit_pre, cb); +} + +/** + * aa_audit - Log a profile based audit event to the audit subsystem + * @type: audit type for the message + * @profile: profile to check against (NOT NULL) + * @gfp: allocation flags to use + * @sa: audit event (NOT NULL) + * @cb: optional callback fn for type specific fields (MAYBE NULL) + * + * Handle default message switching based off of audit mode flags + * + * Returns: error on failure + */ +int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, + struct common_audit_data *sa, + void (*cb) (struct audit_buffer *, void *)) +{ + BUG_ON(!profile); + + if (type == AUDIT_APPARMOR_AUTO) { + if (likely(!sa->aad->error)) { + if (AUDIT_MODE(profile) != AUDIT_ALL) + return 0; + type = AUDIT_APPARMOR_AUDIT; + } else if (COMPLAIN_MODE(profile)) + type = AUDIT_APPARMOR_ALLOWED; + else + type = AUDIT_APPARMOR_DENIED; + } + if (AUDIT_MODE(profile) == AUDIT_QUIET || + (type == AUDIT_APPARMOR_DENIED && + AUDIT_MODE(profile) == AUDIT_QUIET)) + return sa->aad->error; + + if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) + type = AUDIT_APPARMOR_KILL; + + if (!unconfined(profile)) + sa->aad->profile = profile; + + aa_audit_msg(type, sa, cb); + + if (sa->aad->type == AUDIT_APPARMOR_KILL) + (void)send_sig_info(SIGKILL, NULL, + sa->u.tsk ? sa->u.tsk : current); + + if (sa->aad->type == AUDIT_APPARMOR_ALLOWED) + return complain_error(sa->aad->error); + + return sa->aad->error; +} diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c new file mode 100644 index 000000000..1101c6f64 --- /dev/null +++ b/security/apparmor/capability.c @@ -0,0 +1,143 @@ +/* + * AppArmor security module + * + * This file contains AppArmor capability mediation functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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/capability.h> +#include <linux/errno.h> +#include <linux/gfp.h> + +#include "include/apparmor.h" +#include "include/capability.h" +#include "include/context.h" +#include "include/policy.h" +#include "include/audit.h" + +/* + * Table of capability names: we generate it from capabilities.h. + */ +#include "capability_names.h" + +struct aa_fs_entry aa_fs_entry_caps[] = { + AA_FS_FILE_STRING("mask", AA_FS_CAPS_MASK), + { } +}; + +struct audit_cache { + struct aa_profile *profile; + kernel_cap_t caps; +}; + +static DEFINE_PER_CPU(struct audit_cache, audit_cache); + +/** + * audit_cb - call back for capability components of audit struct + * @ab - audit buffer (NOT NULL) + * @va - audit struct to audit data from (NOT NULL) + */ +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 + * @profile: profile being tested for confinement (NOT NULL) + * @cap: capability tested + * @error: error code returned by test + * + * Do auditing of capability and handle, audit/complain/kill modes switching + * and duplicate message elimination. + * + * Returns: 0 or sa->error on success, error code on failure + */ +static int audit_caps(struct aa_profile *profile, int cap, int error) +{ + struct audit_cache *ent; + int type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_CAP; + sa.aad = &aad; + sa.u.cap = cap; + sa.aad->op = OP_CAPABLE; + sa.aad->error = error; + + if (likely(!error)) { + /* test if auditing is being forced */ + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !cap_raised(profile->caps.audit, cap))) + return 0; + type = AUDIT_APPARMOR_AUDIT; + } else if (KILL_MODE(profile) || + cap_raised(profile->caps.kill, cap)) { + type = AUDIT_APPARMOR_KILL; + } else if (cap_raised(profile->caps.quiet, cap) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) { + /* quiet auditing */ + return error; + } + + /* Do simple duplicate message elimination */ + ent = &get_cpu_var(audit_cache); + if (profile == ent->profile && cap_raised(ent->caps, cap)) { + put_cpu_var(audit_cache); + if (COMPLAIN_MODE(profile)) + return complain_error(error); + return error; + } else { + aa_put_profile(ent->profile); + ent->profile = aa_get_profile(profile); + cap_raise(ent->caps, cap); + } + put_cpu_var(audit_cache); + + return aa_audit(type, profile, GFP_ATOMIC, &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 + * + * Returns: 0 if allowed else -EPERM + */ +static int profile_capable(struct aa_profile *profile, int cap) +{ + return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM; +} + +/** + * aa_capable - test permission to use capability + * @profile: profile being tested against (NOT NULL) + * @cap: capability to be tested + * @audit: whether an audit record should be generated + * + * Look up capability in profile capability set. + * + * Returns: 0 on success, or else an error code. + */ +int aa_capable(struct aa_profile *profile, int cap, int audit) +{ + int error = profile_capable(profile, cap); + + if (!audit) { + if (COMPLAIN_MODE(profile)) + return complain_error(error); + return error; + } + + return audit_caps(profile, cap, error); +} diff --git a/security/apparmor/context.c b/security/apparmor/context.c new file mode 100644 index 000000000..3064c6ced --- /dev/null +++ b/security/apparmor/context.c @@ -0,0 +1,222 @@ +/* + * AppArmor security module + * + * This file contains AppArmor functions used to manipulate object security + * contexts. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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. + * + * + * AppArmor sets confinement on every task, via the the aa_task_cxt and + * the aa_task_cxt.profile, both of which are required and are not allowed + * to be NULL. The aa_task_cxt is not reference counted and is unique + * to each cred (which is reference count). The profile pointed to by + * the task_cxt is reference counted. + * + * TODO + * If a task uses change_hat it currently does not return to the old + * cred or task context but instead creates a new one. Ideally the task + * should return to the previous cred if it has not been modified. + * + */ + +#include "include/context.h" +#include "include/policy.h" + +/** + * aa_alloc_task_context - allocate a new task_cxt + * @flags: gfp flags for allocation + * + * Returns: allocated buffer or NULL on failure + */ +struct aa_task_cxt *aa_alloc_task_context(gfp_t flags) +{ + return kzalloc(sizeof(struct aa_task_cxt), flags); +} + +/** + * aa_free_task_context - free a task_cxt + * @cxt: task_cxt to free (MAYBE NULL) + */ +void aa_free_task_context(struct aa_task_cxt *cxt) +{ + if (cxt) { + aa_put_profile(cxt->profile); + aa_put_profile(cxt->previous); + aa_put_profile(cxt->onexec); + + kzfree(cxt); + } +} + +/** + * aa_dup_task_context - duplicate a task context, incrementing reference counts + * @new: a blank task context (NOT NULL) + * @old: the task context to copy (NOT NULL) + */ +void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old) +{ + *new = *old; + aa_get_profile(new->profile); + aa_get_profile(new->previous); + aa_get_profile(new->onexec); +} + +/** + * aa_get_task_profile - Get another task's profile + * @task: task to query (NOT NULL) + * + * Returns: counted reference to @task's profile + */ +struct aa_profile *aa_get_task_profile(struct task_struct *task) +{ + struct aa_profile *p; + + rcu_read_lock(); + p = aa_get_profile(__aa_task_profile(task)); + rcu_read_unlock(); + + return p; +} + +/** + * aa_replace_current_profile - replace the current tasks profiles + * @profile: new profile (NOT NULL) + * + * Returns: 0 or error on failure + */ +int aa_replace_current_profile(struct aa_profile *profile) +{ + struct aa_task_cxt *cxt = current_cxt(); + struct cred *new; + BUG_ON(!profile); + + if (cxt->profile == profile) + return 0; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + cxt = cred_cxt(new); + if (unconfined(profile) || (cxt->profile->ns != profile->ns)) + /* if switching to unconfined or a different profile namespace + * clear out context state + */ + aa_clear_task_cxt_trans(cxt); + + /* be careful switching cxt->profile, when racing replacement it + * is possible that cxt->profile->replacedby->profile is the reference + * keeping @profile valid, so make sure to get its reference before + * dropping the reference on cxt->profile */ + aa_get_profile(profile); + aa_put_profile(cxt->profile); + cxt->profile = profile; + + commit_creds(new); + return 0; +} + +/** + * aa_set_current_onexec - set the tasks change_profile to happen onexec + * @profile: system profile to set at exec (MAYBE NULL to clear value) + * + * Returns: 0 or error on failure + */ +int aa_set_current_onexec(struct aa_profile *profile) +{ + struct aa_task_cxt *cxt; + struct cred *new = prepare_creds(); + if (!new) + return -ENOMEM; + + cxt = cred_cxt(new); + aa_get_profile(profile); + aa_put_profile(cxt->onexec); + cxt->onexec = profile; + + commit_creds(new); + return 0; +} + +/** + * aa_set_current_hat - set the current tasks hat + * @profile: profile 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 + * validate the token to match. + * + * Returns: 0 or error on failure + */ +int aa_set_current_hat(struct aa_profile *profile, u64 token) +{ + struct aa_task_cxt *cxt; + struct cred *new = prepare_creds(); + if (!new) + return -ENOMEM; + BUG_ON(!profile); + + cxt = cred_cxt(new); + if (!cxt->previous) { + /* transfer refcount */ + cxt->previous = cxt->profile; + cxt->token = token; + } else if (cxt->token == token) { + aa_put_profile(cxt->profile); + } else { + /* previous_profile && cxt->token != token */ + abort_creds(new); + return -EACCES; + } + cxt->profile = aa_get_newest_profile(profile); + /* clear exec on switching context */ + aa_put_profile(cxt->onexec); + cxt->onexec = NULL; + + commit_creds(new); + return 0; +} + +/** + * aa_restore_previous_profile - exit from hat context restoring the profile + * @token: the token that must be matched to exit hat context + * + * Attempt to return out of a hat to the previous profile. The token + * must match the stored token value. + * + * Returns: 0 or error of failure + */ +int aa_restore_previous_profile(u64 token) +{ + struct aa_task_cxt *cxt; + struct cred *new = prepare_creds(); + if (!new) + return -ENOMEM; + + cxt = cred_cxt(new); + if (cxt->token != token) { + abort_creds(new); + return -EACCES; + } + /* ignore restores when there is no saved profile */ + if (!cxt->previous) { + abort_creds(new); + return 0; + } + + aa_put_profile(cxt->profile); + cxt->profile = aa_get_newest_profile(cxt->previous); + BUG_ON(!cxt->profile); + /* clear exec && prev information when restoring to previous context */ + aa_clear_task_cxt_trans(cxt); + + commit_creds(new); + return 0; +} diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c new file mode 100644 index 000000000..532471d0b --- /dev/null +++ b/security/apparmor/crypto.c @@ -0,0 +1,95 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy loading interface function definitions. + * + * Copyright 2013 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. + * + * Fns to provide a checksum of policy that has been loaded this can be + * compared to userspace policy compiles to check loaded policy is what + * it should be. + */ + +#include <crypto/hash.h> + +#include "include/apparmor.h" +#include "include/crypto.h" + +static unsigned int apparmor_hash_size; + +static struct crypto_shash *apparmor_tfm; + +unsigned int aa_hash_size(void) +{ + return apparmor_hash_size; +} + +int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, + size_t len) +{ + struct { + struct shash_desc shash; + char ctx[crypto_shash_descsize(apparmor_tfm)]; + } desc; + int error = -ENOMEM; + u32 le32_version = cpu_to_le32(version); + + if (!apparmor_tfm) + return 0; + + profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL); + if (!profile->hash) + goto fail; + + desc.shash.tfm = apparmor_tfm; + desc.shash.flags = 0; + + error = crypto_shash_init(&desc.shash); + if (error) + goto fail; + error = crypto_shash_update(&desc.shash, (u8 *) &le32_version, 4); + if (error) + goto fail; + error = crypto_shash_update(&desc.shash, (u8 *) start, len); + if (error) + goto fail; + error = crypto_shash_final(&desc.shash, profile->hash); + if (error) + goto fail; + + return 0; + +fail: + kfree(profile->hash); + profile->hash = NULL; + + return error; +} + +static int __init init_profile_hash(void) +{ + struct crypto_shash *tfm; + + if (!apparmor_initialized) + return 0; + + tfm = crypto_alloc_shash("sha1", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + int error = PTR_ERR(tfm); + AA_ERROR("failed to setup profile sha1 hashing: %d\n", error); + return error; + } + apparmor_tfm = tfm; + apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm); + + aa_info_message("AppArmor sha1 policy hashing enabled"); + + return 0; +} + +late_initcall(init_profile_hash); diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c new file mode 100644 index 000000000..d97cba3e3 --- /dev/null +++ b/security/apparmor/domain.c @@ -0,0 +1,854 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy attachment and domain transitions + * + * Copyright (C) 2002-2008 Novell/SUSE + * Copyright 2009-2010 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/errno.h> +#include <linux/fdtable.h> +#include <linux/file.h> +#include <linux/mount.h> +#include <linux/syscalls.h> +#include <linux/tracehook.h> +#include <linux/personality.h> + +#include "include/audit.h" +#include "include/apparmorfs.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/ipc.h" +#include "include/match.h" +#include "include/path.h" +#include "include/policy.h" + +/** + * aa_free_domain_entries - free entries in a domain table + * @domain: the domain table to free (MAYBE NULL) + */ +void aa_free_domain_entries(struct aa_domain *domain) +{ + int i; + if (domain) { + if (!domain->table) + return; + + for (i = 0; i < domain->size; i++) + kzfree(domain->table[i]); + kzfree(domain->table); + domain->table = NULL; + } +} + +/** + * may_change_ptraced_domain - check if can change profile on ptraced task + * @to_profile: profile to change to (NOT NULL) + * + * 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) +{ + struct task_struct *tracer; + struct aa_profile *tracerp = NULL; + int error = 0; + + rcu_read_lock(); + tracer = ptrace_parent(current); + if (tracer) + /* released below */ + tracerp = aa_get_task_profile(tracer); + + /* not ptraced */ + if (!tracer || unconfined(tracerp)) + goto out; + + error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH); + +out: + rcu_read_unlock(); + aa_put_profile(tracerp); + + return error; +} + +/** + * 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) + * @request: requested perms + * @start: state to start matching in + * + * Returns: permission set + */ +static struct file_perms change_profile_perms(struct aa_profile *profile, + struct aa_namespace *ns, + const char *name, u32 request, + unsigned int start) +{ + 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; + } + + /* 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; +} + +/** + * __attach_match_ - find an attachment match + * @name - to match against (NOT NULL) + * @head - profile list to walk (NOT NULL) + * + * Do a linear search on the profiles in the list. There is a matching + * preference where an exact match is preferred over a name which uses + * expressions to match, and matching expressions with the greatest + * xmatch_len are preferred. + * + * Requires: @head not be shared or have appropriate locks held + * + * Returns: profile or NULL if no match found + */ +static struct aa_profile *__attach_match(const char *name, + struct list_head *head) +{ + int len = 0; + struct aa_profile *profile, *candidate = NULL; + + list_for_each_entry_rcu(profile, head, base.list) { + if (profile->flags & PFLAG_NULL) + continue; + if (profile->xmatch && profile->xmatch_len > len) { + unsigned int state = aa_dfa_match(profile->xmatch, + DFA_START, name); + u32 perm = dfa_user_allow(profile->xmatch, state); + /* any accepting state means a valid match. */ + if (perm & MAY_EXEC) { + candidate = profile; + len = profile->xmatch_len; + } + } else if (!strcmp(profile->base.name, name)) + /* exact non-re match, no more searching required */ + return profile; + } + + return candidate; +} + +/** + * find_attach - do attachment search for unconfined processes + * @ns: the current namespace (NOT NULL) + * @list: list to search (NOT NULL) + * @name: the executable name to match against (NOT NULL) + * + * Returns: profile or NULL if no match found + */ +static struct aa_profile *find_attach(struct aa_namespace *ns, + struct list_head *list, const char *name) +{ + struct aa_profile *profile; + + rcu_read_lock(); + 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; +} + +static const char *next_name(int xtype, const char *name) +{ + return NULL; +} + +/** + * x_table_lookup - lookup an x transition name via transition table + * @profile: current profile (NOT NULL) + * @xindex: index into x transition table + * + * Returns: refcounted profile, or NULL on failure (MAYBE NULL) + */ +static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +{ + struct aa_profile *new_profile = NULL; + struct aa_namespace *ns = profile->ns; + 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_namespace *new_ns; + const char *xname = NULL; + + new_ns = NULL; + if (xindex & AA_X_CHILD) { + /* release by caller */ + new_profile = aa_find_child(profile, name); + 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_namespace(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_namespace(new_ns); + } + + /* released by caller */ + return new_profile; +} + +/** + * x_to_profile - get target profile for a given xindex + * @profile: current profile (NOT NULL) + * @name: name to lookup (NOT NULL) + * @xindex: index into x transition table + * + * find profile for a transition index + * + * Returns: refcounted profile or NULL if not found available + */ +static struct aa_profile *x_to_profile(struct aa_profile *profile, + const char *name, u32 xindex) +{ + struct aa_profile *new_profile = NULL; + struct aa_namespace *ns = profile->ns; + u32 xtype = xindex & AA_X_TYPE_MASK; + + switch (xtype) { + case AA_X_NONE: + /* fail exec unless ix || ux fallback - handled by caller */ + return NULL; + case AA_X_NAME: + if (xindex & AA_X_CHILD) + /* released by caller */ + new_profile = 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); + break; + } + + /* released by caller */ + return new_profile; +} + +/** + * 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) +{ + struct aa_task_cxt *cxt; + struct aa_profile *profile, *new_profile = NULL; + struct aa_namespace *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, *target = NULL, *info = NULL; + int error = cap_bprm_set_creds(bprm); + if (error) + return error; + + if (bprm->cred_prepared) + return 0; + + cxt = cred_cxt(bprm->cred); + BUG_ON(!cxt); + + profile = aa_get_newest_profile(cxt->profile); + /* + * get the namespace from the replacement profile as replacement + * can change the namespace + */ + ns = profile->ns; + state = profile->file.start; + + /* buffer freed below, name is pointer into buffer */ + error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, + &name, &info); + if (error) { + if (unconfined(profile) || + (profile->flags & PFLAG_IX_ON_NAME_ERROR)) + error = 0; + name = bprm->filename; + goto audit; + } + + /* Test for onexec first as onexec directives override other + * x transitions. + */ + if (unconfined(profile)) { + /* unconfined task */ + if (cxt->onexec) + /* change_profile on exec already been granted */ + new_profile = aa_get_profile(cxt->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; + } + + /* find exec permissions for name */ + state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms); + if (cxt->onexec) { + struct file_perms cp; + info = "change_profile 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, cxt->onexec->ns, + cxt->onexec->base.name, + AA_MAY_ONEXEC, state); + + if (!(cp.allow & AA_MAY_ONEXEC)) + goto audit; + new_profile = aa_get_newest_profile(cxt->onexec); + goto apply; + } + + 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 = -ENOENT; + info = "profile 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, 0); + if (!new_profile) { + error = -ENOMEM; + info = "could not create null profile"; + } else { + error = -EACCES; + target = new_profile->base.hname; + } + 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 (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) { + aa_put_profile(new_profile); + error = -EPERM; + goto cleanup; + } + + if (!new_profile) + goto audit; + + if (bprm->unsafe & LSM_UNSAFE_SHARE) { + /* FIXME: currently don't mediate shared state */ + ; + } + + if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { + error = may_change_ptraced_domain(new_profile); + if (error) { + aa_put_profile(new_profile); + 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); + bprm->unsafe |= AA_SECURE_X_NEEDED; + } +apply: + target = new_profile->base.hname; + /* when transitioning profiles clear unsafe personality bits */ + bprm->per_clear |= PER_CLEAR_ON_SETID; + +x_clear: + aa_put_profile(cxt->profile); + /* transfer new profile reference will be released when cxt is freed */ + cxt->profile = new_profile; + + /* clear out all temporary/transitional state from the context */ + aa_clear_task_cxt_trans(cxt); + +audit: + error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC, + name, target, cond.uid, info, error); + +cleanup: + aa_put_profile(profile); + kfree(buffer); + + return error; +} + +/** + * apparmor_bprm_secureexec - determine if secureexec is needed + * @bprm: binprm for exec (NOT NULL) + * + * Returns: %1 if secureexec is needed else %0 + */ +int apparmor_bprm_secureexec(struct linux_binprm *bprm) +{ + int ret = cap_bprm_secureexec(bprm); + + /* the decision to use secure exec is computed in set_creds + * and stored in bprm->unsafe. + */ + if (!ret && (bprm->unsafe & AA_SECURE_X_NEEDED)) + ret = 1; + + return ret; +} + +/** + * apparmor_bprm_committing_creds - do task cleanup on committing new creds + * @bprm: binprm for the exec (NOT NULL) + */ +void apparmor_bprm_committing_creds(struct linux_binprm *bprm) +{ + struct aa_profile *profile = __aa_current_profile(); + struct aa_task_cxt *new_cxt = cred_cxt(bprm->cred); + + /* bail out if unconfined or not changing profile */ + if ((new_cxt->profile == profile) || + (unconfined(new_cxt->profile))) + return; + + current->pdeath_signal = 0; + + /* reset soft limits and set hard limits for the new profile */ + __aa_transition_rlimits(profile, new_cxt->profile); +} + +/** + * apparmor_bprm_commited_cred - do cleanup after new creds committed + * @bprm: binprm for the exec (NOT NULL) + */ +void apparmor_bprm_committed_creds(struct linux_binprm *bprm) +{ + /* TODO: cleanup signals - ipc mediation */ + return; +} + +/* + * Functions for self directed profile change + */ + +/** + * new_compound_name - create an hname with @n2 appended to @n1 + * @n1: base of hname (NOT NULL) + * @n2: name to append (NOT NULL) + * + * Returns: new name or NULL on error + */ +static char *new_compound_name(const char *n1, const char *n2) +{ + char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); + if (name) + sprintf(name, "%s//%s", n1, n2); + return name; +} + +/** + * aa_change_hat - change hat to/from subprofile + * @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 + * + * 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. + */ +int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) +{ + const struct cred *cred; + struct aa_task_cxt *cxt; + struct aa_profile *profile, *previous_profile, *hat = NULL; + char *name = NULL; + int i; + struct file_perms perms = {}; + const char *target = NULL, *info = NULL; + int error = 0; + + /* + * Fail explicitly requested domain transitions if no_new_privs. + * There is no exception for unconfined as change_hat is not + * available. + */ + if (task_no_new_privs(current)) + return -EPERM; + + /* released below */ + cred = get_current_cred(); + cxt = cred_cxt(cred); + profile = aa_cred_profile(cred); + previous_profile = cxt->previous; + + if (unconfined(profile)) { + info = "unconfined"; + error = -EPERM; + goto audit; + } + + 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, 1); + 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; + } + } + + error = may_change_ptraced_domain(hat); + 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; + } + } 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, GFP_KERNEL, + OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, + target, GLOBAL_ROOT_UID, info, error); + +out: + aa_put_profile(hat); + kfree(name); + put_cred(cred); + + return error; +} + +/** + * aa_change_profile - perform a one-way profile transition + * @ns_name: name of the profile namespace to change to (MAYBE NULL) + * @hname: name of profile to change to (MAYBE NULL) + * @onexec: whether this transition is to take place immediately or at exec + * @permtest: true if this is just a permission test + * + * 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 + * used. + * If @onexec then the transition is delayed until + * the next exec. + * + * Returns %0 on success, error otherwise. + */ +int aa_change_profile(const char *ns_name, const char *hname, bool onexec, + bool permtest) +{ + const struct cred *cred; + struct aa_profile *profile, *target = NULL; + struct aa_namespace *ns = NULL; + struct file_perms perms = {}; + const char *name = NULL, *info = NULL; + int op, error = 0; + u32 request; + + if (!hname && !ns_name) + return -EINVAL; + + if (onexec) { + request = AA_MAY_ONEXEC; + op = OP_CHANGE_ONEXEC; + } else { + request = AA_MAY_CHANGE_PROFILE; + op = OP_CHANGE_PROFILE; + } + + cred = get_current_cred(); + profile = aa_cred_profile(cred); + + /* + * 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 (ns_name) { + /* released below */ + ns = aa_find_namespace(profile->ns, ns_name); + if (!ns) { + /* we don't create new namespace in complain mode */ + name = ns_name; + info = "namespace not found"; + error = -ENOENT; + goto audit; + } + } else + /* released below */ + ns = aa_get_namespace(profile->ns); + + /* if the name was not specified, use the name of the current profile */ + if (!hname) { + if (unconfined(profile)) + hname = ns->unconfined->base.hname; + else + hname = profile->base.hname; + } + + perms = change_profile_perms(profile, ns, hname, request, + profile->file.start); + if (!(perms.allow & request)) { + error = -EACCES; + goto audit; + } + + /* released below */ + target = aa_lookup_profile(ns, hname); + if (!target) { + info = "profile not found"; + error = -ENOENT; + if (permtest || !COMPLAIN_MODE(profile)) + goto audit; + /* released below */ + target = aa_new_null_profile(profile, 0); + if (!target) { + info = "failed null profile create"; + error = -ENOMEM; + goto audit; + } + } + + /* check if tracing task is allowed to trace target domain */ + error = may_change_ptraced_domain(target); + if (error) { + info = "ptrace prevents transition"; + goto audit; + } + + if (permtest) + goto audit; + + if (onexec) + error = aa_set_current_onexec(target); + else + error = aa_replace_current_profile(target); + +audit: + if (!permtest) + error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, + name, hname, GLOBAL_ROOT_UID, info, error); + + aa_put_namespace(ns); + aa_put_profile(target); + put_cred(cred); + + return error; +} diff --git a/security/apparmor/file.c b/security/apparmor/file.c new file mode 100644 index 000000000..913f377a0 --- /dev/null +++ b/security/apparmor/file.c @@ -0,0 +1,458 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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 "include/apparmor.h" +#include "include/audit.h" +#include "include/file.h" +#include "include/match.h" +#include "include/path.h" +#include "include/policy.h" + +struct file_perms nullperms; + + +/** + * audit_file_mask - convert mask to permission string + * @buffer: buffer to write string to (NOT NULL) + * @mask: permission mask to convert + */ +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'; + + audit_log_string(ab, str); +} + +/** + * file_audit_cb - call back for file specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void file_audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + kuid_t fsuid = current_fsuid(); + + if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { + audit_log_format(ab, " requested_mask="); + audit_file_mask(ab, sa->aad->fs.request); + } + if (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) { + audit_log_format(ab, " denied_mask="); + audit_file_mask(ab, sa->aad->fs.denied); + } + if (sa->aad->fs.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, sa->aad->fs.ouid)); + } + + if (sa->aad->fs.target) { + audit_log_format(ab, " target="); + audit_log_untrustedstring(ab, sa->aad->fs.target); + } +} + +/** + * 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) + * @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, + gfp_t gfp, int op, u32 request, const char *name, + const char *target, kuid_t ouid, const char *info, int error) +{ + int type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = op, + aad.fs.request = request; + aad.name = name; + aad.fs.target = target; + aad.fs.ouid = ouid; + aad.info = info; + aad.error = error; + + if (likely(!sa.aad->error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + sa.aad->fs.request &= mask; + + if (likely(!sa.aad->fs.request)) + return 0; + type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + sa.aad->fs.request = sa.aad->fs.request & ~perms->allow; + + if (sa.aad->fs.request & perms->kill) + type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((sa.aad->fs.request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + sa.aad->fs.request &= ~perms->quiet; + + if (!sa.aad->fs.request) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow; + return aa_audit(type, profile, gfp, &sa, file_audit_cb); +} + +/** + * map_old_perms - map old file perms layout to the new layout + * @old: permission set in old mapping + * + * Returns: new permission mapping + */ +static u32 map_old_perms(u32 old) +{ + u32 new = old & 0xf; + if (old & MAY_READ) + new |= AA_MAY_META_READ; + if (old & MAY_WRITE) + new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE | + AA_MAY_CHMOD | AA_MAY_CHOWN; + if (old & 0x10) + new |= AA_MAY_LINK; + /* the old mapping lock and link_subset flags where overlaid + * and use was determined by part of a pair that they were in + */ + if (old & 0x20) + new |= AA_MAY_LOCK | AA_LINK_SUBSET; + if (old & 0x40) /* AA_EXEC_MMAP */ + new |= AA_EXEC_MMAP; + + return new; +} + +/** + * compute_perms - 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) + * + * TODO: convert from dfa + state to permission entry, do computation conversion + * at load time. + * + * Returns: computed permission set + */ +static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, + struct path_cond *cond) +{ + struct file_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; + + if (uid_eq(current_fsuid(), cond->uid)) { + perms.allow = map_old_perms(dfa_user_allow(dfa, state)); + perms.audit = map_old_perms(dfa_user_audit(dfa, state)); + perms.quiet = map_old_perms(dfa_user_quiet(dfa, state)); + perms.xindex = dfa_user_xindex(dfa, state); + } else { + perms.allow = map_old_perms(dfa_other_allow(dfa, state)); + perms.audit = map_old_perms(dfa_other_audit(dfa, state)); + perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); + perms.xindex = dfa_other_xindex(dfa, state); + } + perms.allow |= AA_MAY_META_READ; + + /* change_profile wasn't determined by ownership in old mapping */ + if (ACCEPT_TABLE(dfa)[state] & 0x80000000) + perms.allow |= AA_MAY_CHANGE_PROFILE; + if (ACCEPT_TABLE(dfa)[state] & 0x40000000) + perms.allow |= AA_MAY_ONEXEC; + + return perms; +} + +/** + * aa_str_perms - find permission that match @name + * @dfa: to match against (MAYBE NULL) + * @state: state to start matching in + * @name: string to match against dfa (NOT NULL) + * @cond: conditions to consider for permission set computation (NOT NULL) + * @perms: Returns - the permissions found when matching @name + * + * Returns: the final state in @dfa when beginning @start and walking @name + */ +unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, + const char *name, struct path_cond *cond, + struct file_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); + + 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) +{ + if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) + return 1; + return 0; +} + +/** + * aa_path_perm - do permissions check & audit for @path + * @op: operation being checked + * @profile: 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 + * @cond: conditional info for this request (NOT NULL) + * + * Returns: %0 else error if access denied or other error + */ +int aa_path_perm(int op, struct aa_profile *profile, struct path *path, + int flags, u32 request, struct path_cond *cond) +{ + 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, GFP_KERNEL, op, request, name, + NULL, cond->uid, info, error); + kfree(buffer); + + return error; +} + +/** + * xindex_is_subset - helper for aa_path_link + * @link: link permission set + * @target: target permission set + * + * test target x permissions are equal OR a subset of link x permissions + * this is done as part of the subset test, where a hardlink must have + * a subset of permissions that the target has. + * + * Returns: %1 if subset else %0 + */ +static inline bool xindex_is_subset(u32 link, u32 target) +{ + if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) || + ((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE))) + return 0; + + 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, + struct path *new_dir, struct dentry *new_dentry) +{ + struct path link = { new_dir->mnt, new_dentry }; + struct path target = { new_dir->mnt, 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; + 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); + if (error) + goto audit; + + /* buffer2 freed below, tname is pointer in buffer2 */ + error = aa_path_name(&target, profile->path_flags, &buffer2, &tname, + &info); + 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); + + 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); + + /* force audit/quiet masks for link are stored in the second entry + * in the link pair. + */ + lperms.audit = perms.audit; + lperms.quiet = perms.quiet; + lperms.kill = perms.kill; + + if (!(perms.allow & AA_MAY_LINK)) { + info = "target restricted"; + goto audit; + } + + /* done if link subset test is not required */ + 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. + */ + aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond, + &perms); + + /* AA_MAY_LINK is not considered in the subset test */ + request = lperms.allow & ~AA_MAY_LINK; + lperms.allow &= perms.allow | AA_MAY_LINK; + + request |= AA_AUDIT_FILE_MASK & (lperms.allow & ~perms.allow); + if (request & ~lperms.allow) { + goto audit; + } else if ((lperms.allow & MAY_EXEC) && + !xindex_is_subset(lperms.xindex, perms.xindex)) { + lperms.allow &= ~MAY_EXEC; + request |= MAY_EXEC; + info = "link not subset of target"; + goto audit; + } + +done_tests: + error = 0; + +audit: + error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request, + lname, tname, cond.uid, info, error); + kfree(buffer); + kfree(buffer2); + + return error; +} + +/** + * aa_file_perm - do permission revalidation check & audit for @file + * @op: operation being checked + * @profile: profile 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(int op, struct aa_profile *profile, struct file *file, + u32 request) +{ + struct path_cond cond = { + .uid = file_inode(file)->i_uid, + .mode = file_inode(file)->i_mode + }; + + return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, + request, &cond); +} diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h new file mode 100644 index 000000000..e4ea62663 --- /dev/null +++ b/security/apparmor/include/apparmor.h @@ -0,0 +1,120 @@ +/* + * AppArmor security module + * + * This file contains AppArmor basic global and lib definitions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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 __APPARMOR_H +#define __APPARMOR_H + +#include <linux/slab.h> +#include <linux/fs.h> + +#include "match.h" + +/* + * Class of mediation types in the AppArmor policy db + */ +#define AA_CLASS_ENTRY 0 +#define AA_CLASS_UNKNOWN 1 +#define AA_CLASS_FILE 2 +#define AA_CLASS_CAP 3 +#define AA_CLASS_NET 4 +#define AA_CLASS_RLIMITS 5 +#define AA_CLASS_DOMAIN 6 + +#define AA_CLASS_LAST AA_CLASS_DOMAIN + +/* Control parameters settable through module/boot flags */ +extern enum audit_mode aa_g_audit; +extern bool aa_g_audit_header; +extern bool aa_g_debug; +extern bool aa_g_lock_policy; +extern bool aa_g_logsyscall; +extern bool aa_g_paranoid_load; +extern unsigned int aa_g_path_max; + +/* + * DEBUG remains global (no per profile flag) since it is mostly used in sysctl + * which is not related to profile accesses. + */ + +#define AA_DEBUG(fmt, args...) \ + do { \ + if (aa_g_debug && printk_ratelimit()) \ + printk(KERN_DEBUG "AppArmor: " fmt, ##args); \ + } while (0) + +#define AA_ERROR(fmt, args...) \ + do { \ + if (printk_ratelimit()) \ + printk(KERN_ERR "AppArmor: " fmt, ##args); \ + } while (0) + +/* Flag indicating whether initialization completed */ +extern int apparmor_initialized __initdata; + +/* fn's in lib */ +char *aa_split_fqname(char *args, char **ns_name); +void aa_info_message(const char *str); +void *__aa_kvmalloc(size_t size, gfp_t flags); + +static inline void *kvmalloc(size_t size) +{ + return __aa_kvmalloc(size, 0); +} + +static inline void *kvzalloc(size_t size) +{ + return __aa_kvmalloc(size, __GFP_ZERO); +} + +/* returns 0 if kref not incremented */ +static inline int kref_get_not0(struct kref *kref) +{ + return atomic_inc_not_zero(&kref->refcount); +} + +/** + * aa_strneq - compare null terminated @str to a non null terminated substring + * @str: a null terminated string + * @sub: a substring, not necessarily null terminated + * @len: length of @sub to compare + * + * The @str string must be full consumed for this to be considered a match + */ +static inline bool aa_strneq(const char *str, const char *sub, int len) +{ + return !strncmp(str, sub, len) && !str[len]; +} + +/** + * aa_dfa_null_transition - step to next state after null character + * @dfa: the dfa to match against + * @start: the state of the dfa to start matching in + * + * aa_dfa_null_transition transitions to the next state after a null + * character which is not used in standard matching and is only + * used to separate pairs. + */ +static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, + unsigned int start) +{ + /* the null transition only needs the string's null terminator byte */ + return aa_dfa_next(dfa, start, 0); +} + +static inline bool mediated_filesystem(struct dentry *dentry) +{ + return !(dentry->d_sb->s_flags & MS_NOUSER); +} + +#endif /* __APPARMOR_H */ diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h new file mode 100644 index 000000000..414e56878 --- /dev/null +++ b/security/apparmor/include/apparmorfs.h @@ -0,0 +1,104 @@ +/* + * AppArmor security module + * + * This file contains AppArmor filesystem definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_APPARMORFS_H +#define __AA_APPARMORFS_H + +enum aa_fs_type { + AA_FS_TYPE_BOOLEAN, + AA_FS_TYPE_STRING, + AA_FS_TYPE_U64, + AA_FS_TYPE_FOPS, + AA_FS_TYPE_DIR, +}; + +struct aa_fs_entry; + +struct aa_fs_entry { + const char *name; + struct dentry *dentry; + umode_t mode; + enum aa_fs_type v_type; + union { + bool boolean; + char *string; + unsigned long u64; + struct aa_fs_entry *files; + } v; + const struct file_operations *file_ops; +}; + +extern const struct file_operations aa_fs_seq_file_ops; + +#define AA_FS_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) \ + { .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) \ + { .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, \ + .mode = (_mode), .file_ops = (_fops) } +#define AA_FS_DIR(_name, _value) \ + { .name = (_name), .v_type = AA_FS_TYPE_DIR, .v.files = (_value) } + +extern void __init aa_destroy_aafs(void); + +struct aa_profile; +struct aa_namespace; + +enum aafs_ns_type { + AAFS_NS_DIR, + AAFS_NS_PROFS, + AAFS_NS_NS, + AAFS_NS_COUNT, + AAFS_NS_MAX_COUNT, + AAFS_NS_SIZE, + AAFS_NS_MAX_SIZE, + AAFS_NS_OWNER, + AAFS_NS_SIZEOF, +}; + +enum aafs_prof_type { + AAFS_PROF_DIR, + AAFS_PROF_PROFS, + AAFS_PROF_NAME, + AAFS_PROF_MODE, + AAFS_PROF_ATTACH, + AAFS_PROF_HASH, + AAFS_PROF_SIZEOF, +}; + +#define ns_dir(X) ((X)->dents[AAFS_NS_DIR]) +#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS]) +#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS]) + +#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, + struct aa_profile *new); +int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); +void __aa_fs_namespace_rmdir(struct aa_namespace *ns); +int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, + const char *name); + +#endif /* __AA_APPARMORFS_H */ diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h new file mode 100644 index 000000000..ba3dfd17f --- /dev/null +++ b/security/apparmor/include/audit.h @@ -0,0 +1,147 @@ +/* + * AppArmor security module + * + * This file contains AppArmor auditing function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_AUDIT_H +#define __AA_AUDIT_H + +#include <linux/audit.h> +#include <linux/fs.h> +#include <linux/lsm_audit.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include "file.h" + +struct aa_profile; + +extern const char *const audit_mode_names[]; +#define AUDIT_MAX_INDEX 5 +enum audit_mode { + AUDIT_NORMAL, /* follow normal auditing of accesses */ + AUDIT_QUIET_DENIED, /* quiet all denied access messages */ + AUDIT_QUIET, /* quiet all messages */ + AUDIT_NOQUIET, /* do not quiet audit messages */ + AUDIT_ALL /* audit all accesses */ +}; + +enum audit_type { + AUDIT_APPARMOR_AUDIT, + AUDIT_APPARMOR_ALLOWED, + AUDIT_APPARMOR_DENIED, + AUDIT_APPARMOR_HINT, + AUDIT_APPARMOR_STATUS, + AUDIT_APPARMOR_ERROR, + AUDIT_APPARMOR_KILL, + AUDIT_APPARMOR_AUTO +}; + +extern const char *const op_table[]; +enum aa_ops { + OP_NULL, + + OP_SYSCTL, + OP_CAPABLE, + + OP_UNLINK, + OP_MKDIR, + OP_RMDIR, + OP_MKNOD, + OP_TRUNC, + OP_LINK, + OP_SYMLINK, + OP_RENAME_SRC, + OP_RENAME_DEST, + OP_CHMOD, + OP_CHOWN, + OP_GETATTR, + OP_OPEN, + + OP_FPERM, + OP_FLOCK, + OP_FMMAP, + OP_FMPROT, + + OP_CREATE, + OP_POST_CREATE, + OP_BIND, + OP_CONNECT, + OP_LISTEN, + OP_ACCEPT, + OP_SENDMSG, + OP_RECVMSG, + OP_GETSOCKNAME, + OP_GETPEERNAME, + OP_GETSOCKOPT, + OP_SETSOCKOPT, + OP_SOCK_SHUTDOWN, + + OP_PTRACE, + + OP_EXEC, + OP_CHANGE_HAT, + OP_CHANGE_PROFILE, + OP_CHANGE_ONEXEC, + + OP_SETPROCATTR, + OP_SETRLIMIT, + + OP_PROF_REPL, + OP_PROF_LOAD, + OP_PROF_RM, +}; + + +struct apparmor_audit_data { + int error; + int op; + int type; + void *profile; + const char *name; + const char *info; + union { + void *target; + struct { + long pos; + void *target; + } iface; + struct { + int rlim; + unsigned long max; + } rlim; + struct { + const char *target; + u32 request; + u32 denied; + kuid_t ouid; + } fs; + }; +}; + +/* define a short hand for apparmor_audit_data structure */ +#define aad apparmor_audit_data + +void aa_audit_msg(int type, struct common_audit_data *sa, + void (*cb) (struct audit_buffer *, void *)); +int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, + struct common_audit_data *sa, + void (*cb) (struct audit_buffer *, void *)); + +static inline int complain_error(int error) +{ + if (error == -EPERM || error == -EACCES) + return 0; + return error; +} + +#endif /* __AA_AUDIT_H */ diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h new file mode 100644 index 000000000..fc3fa381d --- /dev/null +++ b/security/apparmor/include/capability.h @@ -0,0 +1,48 @@ +/* + * AppArmor security module + * + * This file contains AppArmor capability mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2013 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_CAPABILITY_H +#define __AA_CAPABILITY_H + +#include <linux/sched.h> + +#include "apparmorfs.h" + +struct aa_profile; + +/* aa_caps - confinement data for capabilities + * @allowed: capabilities mask + * @audit: caps that are to be audited + * @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 + */ +struct aa_caps { + kernel_cap_t allow; + kernel_cap_t audit; + kernel_cap_t quiet; + kernel_cap_t kill; + kernel_cap_t extended; +}; + +extern struct aa_fs_entry aa_fs_entry_caps[]; + +int aa_capable(struct aa_profile *profile, int cap, int audit); + +static inline void aa_free_cap_rules(struct aa_caps *caps) +{ + /* NOP */ +} + +#endif /* __AA_CAPBILITY_H */ diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h new file mode 100644 index 000000000..6bf65798e --- /dev/null +++ b/security/apparmor/include/context.h @@ -0,0 +1,178 @@ +/* + * AppArmor security module + * + * This file contains AppArmor contexts used to associate "labels" to objects. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_CONTEXT_H +#define __AA_CONTEXT_H + +#include <linux/cred.h> +#include <linux/slab.h> +#include <linux/sched.h> + +#include "policy.h" + +#define cred_cxt(X) (X)->security +#define current_cxt() cred_cxt(current_cred()) + +/* struct aa_file_cxt - the AppArmor context the file was opened in + * @perms: the permission the file was opened with + * + * The file_cxt could currently be directly stored in file->f_security + * as the profile reference is now stored in the f_cred. However the + * cxt struct will expand in the future so we keep the struct. + */ +struct aa_file_cxt { + u16 allow; +}; + +/** + * aa_alloc_file_context - allocate file_cxt + * @gfp: gfp flags for allocation + * + * Returns: file_cxt or NULL on failure + */ +static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp) +{ + return kzalloc(sizeof(struct aa_file_cxt), gfp); +} + +/** + * aa_free_file_context - free a file_cxt + * @cxt: file_cxt to free (MAYBE_NULL) + */ +static inline void aa_free_file_context(struct aa_file_cxt *cxt) +{ + if (cxt) + kzfree(cxt); +} + +/** + * struct aa_task_cxt - 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 + * + * Contains the task's current profile (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_cxt { + struct aa_profile *profile; + struct aa_profile *onexec; + struct aa_profile *previous; + u64 token; +}; + +struct aa_task_cxt *aa_alloc_task_context(gfp_t flags); +void aa_free_task_context(struct aa_task_cxt *cxt); +void aa_dup_task_context(struct aa_task_cxt *new, + const struct aa_task_cxt *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); + + +/** + * aa_cred_profile - obtain cred's profiles + * @cred: cred to obtain profiles from (NOT NULL) + * + * Returns: confining profile + * + * does NOT increment reference count + */ +static inline struct aa_profile *aa_cred_profile(const struct cred *cred) +{ + struct aa_task_cxt *cxt = cred_cxt(cred); + BUG_ON(!cxt || !cxt->profile); + return cxt->profile; +} + +/** + * __aa_task_profile - retrieve another task's profile + * @task: task to query (NOT NULL) + * + * Returns: @task's profile 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) +{ + return aa_cred_profile(__task_cred(task)); +} + +/** + * __aa_task_is_confined - determine if @task has any confinement + * @task: task to check confinement of (NOT NULL) + * + * If @task != current needs to be called in RCU safe critical section + */ +static inline bool __aa_task_is_confined(struct task_struct *task) +{ + return !unconfined(__aa_task_profile(task)); +} + +/** + * __aa_current_profile - find the current tasks confining profile + * + * Returns: up to date confining profile or the ns unconfined profile (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. + */ +static inline struct aa_profile *__aa_current_profile(void) +{ + return aa_cred_profile(current_cred()); +} + +/** + * aa_current_profile - find the current tasks confining profile and do updates + * + * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * + * This fn will update the tasks cred structure if the profile has been + * replaced. Not safe to call inside locks + */ +static inline struct aa_profile *aa_current_profile(void) +{ + const struct aa_task_cxt *cxt = current_cxt(); + struct aa_profile *profile; + BUG_ON(!cxt || !cxt->profile); + + if (PROFILE_INVALID(cxt->profile)) { + profile = aa_get_newest_profile(cxt->profile); + aa_replace_current_profile(profile); + aa_put_profile(profile); + cxt = current_cxt(); + } + + return cxt->profile; +} + +/** + * aa_clear_task_cxt_trans - clear transition tracking info from the cxt + * @cxt: task context to clear (NOT NULL) + */ +static inline void aa_clear_task_cxt_trans(struct aa_task_cxt *cxt) +{ + aa_put_profile(cxt->previous); + aa_put_profile(cxt->onexec); + cxt->previous = NULL; + cxt->onexec = NULL; + cxt->token = 0; +} + +#endif /* __AA_CONTEXT_H */ diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h new file mode 100644 index 000000000..dc418e502 --- /dev/null +++ b/security/apparmor/include/crypto.h @@ -0,0 +1,36 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy loading interface function definitions. + * + * Copyright 2013 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 __APPARMOR_CRYPTO_H +#define __APPARMOR_CRYPTO_H + +#include "policy.h" + +#ifdef CONFIG_SECURITY_APPARMOR_HASH +unsigned int aa_hash_size(void); +int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, + size_t len); +#else +static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version, + void *start, size_t len) +{ + return 0; +} + +static inline unsigned int aa_hash_size(void) +{ + return 0; +} +#endif + +#endif /* __APPARMOR_CRYPTO_H */ diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h new file mode 100644 index 000000000..de04464f0 --- /dev/null +++ b/security/apparmor/include/domain.h @@ -0,0 +1,36 @@ +/* + * AppArmor security module + * + * This file contains AppArmor security domain transition function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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/binfmts.h> +#include <linux/types.h> + +#ifndef __AA_DOMAIN_H +#define __AA_DOMAIN_H + +struct aa_domain { + int size; + char **table; +}; + +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 *ns_name, const char *name, bool onexec, + bool permtest); + +#endif /* __AA_DOMAIN_H */ diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h new file mode 100644 index 000000000..2c922b86b --- /dev/null +++ b/security/apparmor/include/file.h @@ -0,0 +1,216 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_FILE_H +#define __AA_FILE_H + +#include "domain.h" +#include "match.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 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_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ + AA_EXEC_MMAP | AA_MAY_LINK) + +/* + * The xindex is broken into 3 parts + * - index - an index into either the exec name table or the variable table + * - exec type - which determines how the executable name and index are used + * - flags - which modify how the destination name is applied + */ +#define AA_X_INDEX_MASK 0x03ff + +#define AA_X_TYPE_MASK 0x0c00 +#define AA_X_TYPE_SHIFT 10 +#define AA_X_NONE 0x0000 +#define AA_X_NAME 0x0400 /* use executable name px */ +#define AA_X_TABLE 0x0800 /* use a specified name ->n# */ + +#define AA_X_UNSAFE 0x1000 +#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */ +#define AA_X_INHERIT 0x4000 +#define AA_X_UNCONFINED 0x8000 + +/* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */ +#define AA_SECURE_X_NEEDED 0x8000 + +/* need to make conditional which ones are being set */ +struct path_cond { + kuid_t uid; + 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 + * also add delegation info. + */ +static inline u16 dfa_map_xindex(u16 mask) +{ + u16 old_index = (mask >> 10) & 0xf; + u16 index = 0; + + if (mask & 0x100) + index |= AA_X_UNSAFE; + if (mask & 0x200) + index |= AA_X_INHERIT; + if (mask & 0x80) + index |= AA_X_UNCONFINED; + + if (old_index == 1) { + index |= AA_X_UNCONFINED; + } else if (old_index == 2) { + index |= AA_X_NAME; + } else if (old_index == 3) { + index |= AA_X_NAME | AA_X_CHILD; + } else if (old_index) { + index |= AA_X_TABLE; + index |= old_index - 4; + } + + return index; +} + +/* + * map old dfa inline permissions to new format + */ +#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \ + ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) +#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f) +#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f) +#define dfa_user_xindex(dfa, state) \ + (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff)) + +#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \ + 0x7f) | \ + ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) +#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f) +#define dfa_other_quiet(dfa, state) \ + ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f) +#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, + gfp_t gfp, int op, u32 request, const char *name, + const char *target, kuid_t ouid, const char *info, int error); + +/** + * struct aa_file_rules - components used for file rule permissions + * @dfa: dfa to match path names and conditionals against + * @perms: permission table indexed by the matched state accept entry of @dfa + * @trans: transition table for indexed by named x transitions + * + * File permission are determined by matching a path against @dfa and then + * then using the value of the accept entry for the matching state as + * an index into @perms. If a named exec transition is required it is + * looked up in the transition table. + */ +struct aa_file_rules { + unsigned int start; + struct aa_dfa *dfa; + /* struct perms perms; */ + struct aa_domain trans; + /* TODO: add delegate table */ +}; + +unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, + const char *name, struct path_cond *cond, + struct file_perms *perms); + +int aa_path_perm(int op, struct aa_profile *profile, struct path *path, + int flags, u32 request, struct path_cond *cond); + +int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, + struct path *new_dir, struct dentry *new_dentry); + +int aa_file_perm(int op, struct aa_profile *profile, struct file *file, + u32 request); + +static inline void aa_free_file_rules(struct aa_file_rules *rules) +{ + aa_put_dfa(rules->dfa); + aa_free_domain_entries(&rules->trans); +} + +/** + * aa_map_file_perms - map file flags to AppArmor permissions + * @file: open file to map flags to AppArmor permissions + * + * Returns: apparmor permission set for the file + */ +static inline u32 aa_map_file_to_perms(struct file *file) +{ + int flags = file->f_flags; + u32 perms = 0; + + if (file->f_mode & FMODE_WRITE) + perms |= MAY_WRITE; + if (file->f_mode & FMODE_READ) + perms |= MAY_READ; + + if ((flags & O_APPEND) && (perms & MAY_WRITE)) + perms = (perms & ~MAY_WRITE) | MAY_APPEND; + /* trunc implies write permission */ + if (flags & O_TRUNC) + perms |= MAY_WRITE; + if (flags & O_CREAT) + perms |= AA_MAY_CREATE; + + return perms; +} + +#endif /* __AA_FILE_H */ diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h new file mode 100644 index 000000000..288ca76e2 --- /dev/null +++ b/security/apparmor/include/ipc.h @@ -0,0 +1,28 @@ +/* + * AppArmor security module + * + * This file contains AppArmor ipc mediation function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_IPC_H +#define __AA_IPC_H + +#include <linux/sched.h> + +struct aa_profile; + +int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, + unsigned int mode); + +int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, + unsigned int mode); + +#endif /* __AA_IPC_H */ diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h new file mode 100644 index 000000000..001c43aa0 --- /dev/null +++ b/security/apparmor/include/match.h @@ -0,0 +1,141 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy dfa matching engine definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 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_MATCH_H +#define __AA_MATCH_H + +#include <linux/kref.h> + +#define DFA_NOMATCH 0 +#define DFA_START 1 + + +/** + * The format used for transition tables is based on the GNU flex table + * file format (--tables-file option; see Table File Format in the flex + * info pages and the flex sources for documentation). The magic number + * used in the header is 0x1B5E783D instead of 0xF13C57B1 though, because + * new tables have been defined and others YY_ID_CHK (check) and YY_ID_DEF + * (default) tables are used slightly differently (see the apparmor-parser + * package). + * + * + * The data in the packed dfa is stored in network byte order, and the tables + * are arranged for flexibility. We convert the table data to host native + * byte order. + * + * The dfa begins with a table set header, and is followed by the actual + * tables. + */ + +#define YYTH_MAGIC 0x1B5E783D + +struct table_set_header { + u32 th_magic; /* YYTH_MAGIC */ + u32 th_hsize; + u32 th_ssize; + u16 th_flags; + char th_version[]; +}; + +/* The YYTD_ID are one less than flex table mappings. The flex id + * has 1 subtracted at table load time, this allows us to directly use the + * ID's as indexes. + */ +#define YYTD_ID_ACCEPT 0 +#define YYTD_ID_BASE 1 +#define YYTD_ID_CHK 2 +#define YYTD_ID_DEF 3 +#define YYTD_ID_EC 4 +#define YYTD_ID_META 5 +#define YYTD_ID_ACCEPT2 6 +#define YYTD_ID_NXT 7 +#define YYTD_ID_TSIZE 8 + +#define YYTD_DATA8 1 +#define YYTD_DATA16 2 +#define YYTD_DATA32 4 +#define YYTD_DATA64 8 + +/* ACCEPT & ACCEPT2 tables gets 6 dedicated flags, YYTD_DATAX define the + * first flags + */ +#define ACCEPT1_FLAGS(X) ((X) & 0x3f) +#define ACCEPT2_FLAGS(X) ACCEPT1_FLAGS((X) >> YYTD_ID_ACCEPT2) +#define TO_ACCEPT1_FLAG(X) ACCEPT1_FLAGS(X) +#define TO_ACCEPT2_FLAG(X) (ACCEPT1_FLAGS(X) << YYTD_ID_ACCEPT2) +#define DFA_FLAG_VERIFY_STATES 0x1000 + +struct table_header { + u16 td_id; + u16 td_flags; + u32 td_hilen; + u32 td_lolen; + char td_data[]; +}; + +#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF]->td_data)) +#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data)) +#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT]->td_data)) +#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK]->td_data)) +#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data)) +#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data)) +#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data)) + +struct aa_dfa { + struct kref count; + u16 flags; + struct table_header *tables[YYTD_ID_TSIZE]; +}; + +#define byte_to_byte(X) (X) + +#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \ + do { \ + typeof(LEN) __i; \ + TYPE *__t = (TYPE *) TABLE; \ + TYPE *__b = (TYPE *) BLOB; \ + for (__i = 0; __i < LEN; __i++) { \ + __t[__i] = NTOHX(__b[__i]); \ + } \ + } while (0) + +static inline size_t table_size(size_t len, size_t el_size) +{ + return ALIGN(sizeof(struct table_header) + len * el_size, 8); +} + +struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); +unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, + const char *str, int len); +unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, + const char *str); +unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, + const char c); + +void aa_dfa_free_kref(struct kref *kref); + +/** + * aa_put_dfa - put a dfa refcount + * @dfa: dfa to put refcount (MAYBE NULL) + * + * Requires: if @dfa != NULL that a valid refcount be held + */ +static inline void aa_put_dfa(struct aa_dfa *dfa) +{ + if (dfa) + kref_put(&dfa->count, aa_dfa_free_kref); +} + +#endif /* __AA_MATCH_H */ diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h new file mode 100644 index 000000000..286ac75dc --- /dev/null +++ b/security/apparmor/include/path.h @@ -0,0 +1,32 @@ +/* + * AppArmor security module + * + * This file contains AppArmor basic path manipulation function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_PATH_H +#define __AA_PATH_H + + +enum path_flags { + PATH_IS_DIR = 0x1, /* path is a directory */ + PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */ + PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */ + 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 */ +}; + +int aa_path_name(struct path *path, int flags, char **buffer, + const char **name, const char **info); + +#endif /* __AA_PATH_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h new file mode 100644 index 000000000..c28b0f20a --- /dev/null +++ b/security/apparmor/include/policy.h @@ -0,0 +1,408 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_POLICY_H +#define __AA_POLICY_H + +#include <linux/capability.h> +#include <linux/cred.h> +#include <linux/kref.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/socket.h> + +#include "apparmor.h" +#include "audit.h" +#include "capability.h" +#include "domain.h" +#include "file.h" +#include "resource.h" + +extern const char *const aa_profile_mode_names[]; +#define APPARMOR_MODE_NAMES_MAX_INDEX 4 + +#define PROFILE_MODE(_profile, _mode) \ + ((aa_g_profile_mode == (_mode)) || \ + ((_profile)->mode == (_mode))) + +#define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN) + +#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL) + +#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT) + +#define PROFILE_INVALID(_profile) ((_profile)->flags & PFLAG_INVALID) + +#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) + +/* + * FIXME: currently need a clean way to replace and remove profiles as a + * set. It should be done at the namespace level. + * Either, with a set of profiles loaded at the namespace level or via + * a mark and remove marked interface. + */ +enum profile_mode { + APPARMOR_ENFORCE, /* enforce access rules */ + APPARMOR_COMPLAIN, /* allow and log access violations */ + APPARMOR_KILL, /* kill task on access violation */ + 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_INVALID = 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_policy - common part of both namespaces and profiles + * @name: name of the object + * @hname - The hierarchical name + * @list: list policy object is on + * @profiles: head of the profiles list contained in the object + */ +struct aa_policy { + char *name; + char *hname; + struct list_head list; + struct list_head profiles; +}; + +/* struct aa_ns_acct - accounting of profiles in namespace + * @max_size: maximum space allowed for all profiles in namespace + * @max_count: maximum number of profiles that can be in this namespace + * @size: current size of profiles + * @count: current count of profiles (includes null profiles) + */ +struct aa_ns_acct { + int max_size; + int max_count; + int size; + int count; +}; + +/* struct aa_namespace - namespace for a set of profiles + * @base: common policy + * @parent: parent of namespace + * @lock: lock for modifying the object + * @acct: accounting for the namespace + * @unconfined: special unconfined profile for the namespace + * @sub_ns: list of namespaces under the current namespace. + * @uniq_null: uniq value used for null learning profiles + * @uniq_id: a unique id count for the profiles in the namespace + * @dents: dentries for the namespaces file entries in apparmorfs + * + * An aa_namespace defines the set profiles that are searched to determine + * which profile to attach to a task. Profiles can not be shared between + * aa_namespaces and profile names within a namespace are guaranteed to be + * unique. When profiles in separate namespaces have the same name they + * are NOT considered to be equivalent. + * + * Namespaces are hierarchical and only namespaces and profiles below the + * current namespace are visible. + * + * Namespace names must be unique and can not contain the characters :/\0 + * + * FIXME TODO: add vserver support of namespaces (can it all be done in + * userspace?) + */ +struct aa_namespace { + struct aa_policy base; + struct aa_namespace *parent; + struct mutex lock; + struct aa_ns_acct acct; + struct aa_profile *unconfined; + struct list_head sub_ns; + atomic_t uniq_null; + long uniq_id; + + struct dentry *dents[AAFS_NS_SIZEOF]; +}; + +/* struct aa_policydb - match engine for a policy + * dfa: dfa pattern match + * start: set of start states for the different classes of data + */ +struct aa_policydb { + /* Generic policy DFA specific rule types will be subsections of it */ + struct aa_dfa *dfa; + unsigned int start[AA_CLASS_LAST + 1]; + +}; + +struct aa_replacedby { + struct kref count; + struct aa_profile __rcu *profile; +}; + + +/* 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 + * @parent: parent of profile + * @ns: namespace the profile is in + * @replacedby: 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 + * @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 + * @caps: capabilities for the profile + * @rlimits: rlimits for the profile + * + * @dents: dentries for the profiles file entries in apparmorfs + * @dirname: name of the profile dir in apparmorfs + * + * The AppArmor profile contains the basic confinement data. Each profile + * has a name, and exists in a namespace. The @name and @exec_match are + * used to determine profile attachment against unconfined tasks. All other + * attachments are determined by profile X transition rules. + * + * The @replacedby struct is write protected by the profile lock. + * + * Profiles have a hierarchy where hats and children profiles keep + * a reference to their parent. + * + * Profile names can not begin with a : and can not contain the \0 + * character. If a profile name begins with / it will be considered when + * determining profile attachment on "unconfined" tasks. + */ +struct aa_profile { + struct aa_policy base; + struct kref count; + struct rcu_head rcu; + struct aa_profile __rcu *parent; + + struct aa_namespace *ns; + struct aa_replacedby *replacedby; + const char *rename; + + const char *attach; + struct aa_dfa *xmatch; + int xmatch_len; + enum audit_mode audit; + long mode; + long flags; + u32 path_flags; + int size; + + struct aa_policydb policy; + struct aa_file_rules file; + struct aa_caps caps; + struct aa_rlimit rlimits; + + unsigned char *hash; + char *dirname; + struct dentry *dents[AAFS_PROF_SIZEOF]; +}; + +extern struct aa_namespace *root_ns; +extern enum profile_mode aa_g_profile_mode; + +void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); + +bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view); +const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child); +int aa_alloc_root_ns(void); +void aa_free_root_ns(void); +void aa_free_namespace_kref(struct kref *kref); + +struct aa_namespace *aa_find_namespace(struct aa_namespace *root, + const char *name); + + +void aa_free_replacedby_kref(struct kref *kref); +struct aa_profile *aa_alloc_profile(const char *name); +struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat); +void aa_free_profile(struct aa_profile *profile); +void aa_free_profile_kref(struct kref *kref); +struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); +struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name); +struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name); + +ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace); +ssize_t aa_remove_profiles(char *name, size_t size); + +#define PROF_ADD 1 +#define PROF_REPLACE 0 + +#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) + + +static inline struct aa_profile *aa_deref_parent(struct aa_profile *p) +{ + return rcu_dereference_protected(p->parent, + mutex_is_locked(&p->ns->lock)); +} + +/** + * aa_get_profile - increment refcount on profile @p + * @p: profile (MAYBE NULL) + * + * Returns: pointer to @p if @p is NULL will return NULL + * Requires: @p must be held with valid refcount when called + */ +static inline struct aa_profile *aa_get_profile(struct aa_profile *p) +{ + if (p) + kref_get(&(p->count)); + + return p; +} + +/** + * aa_get_profile_not0 - increment refcount on profile @p found via lookup + * @p: profile (MAYBE NULL) + * + * Returns: pointer to @p if @p is NULL will return NULL + * Requires: @p must be held with valid refcount when called + */ +static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p) +{ + if (p && kref_get_not0(&p->count)) + return p; + + return NULL; +} + +/** + * aa_get_profile_rcu - increment a refcount profile that can be replaced + * @p: pointer to profile that can be replaced (NOT NULL) + * + * Returns: pointer to a refcounted profile. + * else NULL if no profile + */ +static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) +{ + struct aa_profile *c; + + rcu_read_lock(); + do { + c = rcu_dereference(*p); + } while (c && !kref_get_not0(&c->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_INVALID(p)) + return aa_get_profile_rcu(&p->replacedby->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_replacedby *aa_get_replacedby(struct aa_replacedby *p) +{ + if (p) + kref_get(&(p->count)); + + return p; +} + +static inline void aa_put_replacedby(struct aa_replacedby *p) +{ + if (p) + kref_put(&p->count, aa_free_replacedby_kref); +} + +/* requires profile list write lock held */ +static inline void __aa_update_replacedby(struct aa_profile *orig, + struct aa_profile *new) +{ + struct aa_profile *tmp; + tmp = rcu_dereference_protected(orig->replacedby->profile, + mutex_is_locked(&orig->ns->lock)); + rcu_assign_pointer(orig->replacedby->profile, aa_get_profile(new)); + orig->flags |= PFLAG_INVALID; + aa_put_profile(tmp); +} + +/** + * aa_get_namespace - increment references count on @ns + * @ns: namespace to increment reference count of (MAYBE NULL) + * + * Returns: pointer to @ns, if @ns is NULL returns NULL + * Requires: @ns must be held with valid refcount when called + */ +static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns) +{ + if (ns) + aa_get_profile(ns->unconfined); + + return ns; +} + +/** + * aa_put_namespace - decrement refcount on @ns + * @ns: namespace to put reference of + * + * Decrement reference count of @ns and if no longer in use free it + */ +static inline void aa_put_namespace(struct aa_namespace *ns) +{ + if (ns) + aa_put_profile(ns->unconfined); +} + +static inline int AUDIT_MODE(struct aa_profile *profile) +{ + if (aa_g_audit != AUDIT_NORMAL) + return aa_g_audit; + + return profile->audit; +} + +bool aa_may_manage_policy(int op); + +#endif /* __AA_POLICY_H */ diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h new file mode 100644 index 000000000..c214fb88b --- /dev/null +++ b/security/apparmor/include/policy_unpack.h @@ -0,0 +1,39 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy loading interface function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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 __POLICY_INTERFACE_H +#define __POLICY_INTERFACE_H + +#include <linux/list.h> + +struct aa_load_ent { + struct list_head list; + struct aa_profile *new; + struct aa_profile *old; + struct aa_profile *rename; +}; + +void aa_load_ent_free(struct aa_load_ent *ent); +struct aa_load_ent *aa_load_ent_alloc(void); + +#define PACKED_FLAG_HAT 1 + +#define PACKED_MODE_ENFORCE 0 +#define PACKED_MODE_COMPLAIN 1 +#define PACKED_MODE_KILL 2 +#define PACKED_MODE_UNCONFINED 3 + +int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns); + +#endif /* __POLICY_INTERFACE_H */ diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h new file mode 100644 index 000000000..6bd5f33d9 --- /dev/null +++ b/security/apparmor/include/procattr.h @@ -0,0 +1,25 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /proc/<pid>/attr/ interface function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_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); + +#endif /* __AA_PROCATTR_H */ diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h new file mode 100644 index 000000000..d3f4cf027 --- /dev/null +++ b/security/apparmor/include/resource.h @@ -0,0 +1,50 @@ +/* + * AppArmor security module + * + * This file contains AppArmor resource limits function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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_RESOURCE_H +#define __AA_RESOURCE_H + +#include <linux/resource.h> +#include <linux/sched.h> + +#include "apparmorfs.h" + +struct aa_profile; + +/* struct aa_rlimit - rlimit settings for the profile + * @mask: which hard limits to set + * @limits: rlimit values that override task limits + * + * AppArmor rlimits are used to set confined task rlimits. Only the + * limits specified in @mask will be controlled by apparmor. + */ +struct aa_rlimit { + unsigned int mask; + struct rlimit limits[RLIM_NLIMITS]; +}; + +extern struct aa_fs_entry aa_fs_entry_rlimit[]; + +int aa_map_resource(int resource); +int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *, + unsigned int resource, struct rlimit *new_rlim); + +void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new); + +static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims) +{ + /* NOP */ +} + +#endif /* __AA_RESOURCE_H */ diff --git a/security/apparmor/include/sid.h b/security/apparmor/include/sid.h new file mode 100644 index 000000000..513ca0e48 --- /dev/null +++ b/security/apparmor/include/sid.h @@ -0,0 +1,26 @@ +/* + * AppArmor security module + * + * This file contains AppArmor security identifier (sid) definitions + * + * Copyright 2009-2010 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_SID_H +#define __AA_SID_H + +#include <linux/types.h> + +/* sid value that will not be allocated */ +#define AA_SID_INVALID 0 +#define AA_SID_ALLOC AA_SID_INVALID + +u32 aa_alloc_sid(void); +void aa_free_sid(u32 sid); + +#endif /* __AA_SID_H */ diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c new file mode 100644 index 000000000..777ac1c47 --- /dev/null +++ b/security/apparmor/ipc.c @@ -0,0 +1,111 @@ +/* + * AppArmor security module + * + * This file contains AppArmor ipc mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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/gfp.h> +#include <linux/ptrace.h> + +#include "include/audit.h" +#include "include/capability.h" +#include "include/context.h" +#include "include/policy.h" +#include "include/ipc.h" + +/* call back to audit ptrace fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + audit_log_format(ab, " target="); + audit_log_untrustedstring(ab, sa->aad->target); +} + +/** + * 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) +{ + struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = OP_PTRACE; + aad.target = target; + aad.error = error; + + return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa, + audit_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) +{ + /* TODO: currently only based on capability, not extended ptrace + * rules, + * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH + */ + + if (unconfined(tracer) || tracer == tracee) + return 0; + /* log this capability request */ + return aa_capable(tracer, CAP_SYS_PTRACE, 1); +} + +/** + * 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 + * + * Returns: %0 else error code if permission denied or error + */ +int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, + unsigned int mode) +{ + /* + * 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); + + error = aa_may_ptrace(tracer_p, tracee_p, mode); + error = aa_audit_ptrace(tracer_p, tracee_p, error); + + aa_put_profile(tracee_p); + } + aa_put_profile(tracer_p); + + return error; +} diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c new file mode 100644 index 000000000..c1827e068 --- /dev/null +++ b/security/apparmor/lib.c @@ -0,0 +1,106 @@ +/* + * AppArmor security module + * + * This file contains basic common functions used in AppArmor + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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/mm.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/vmalloc.h> + +#include "include/audit.h" +#include "include/apparmor.h" + + +/** + * aa_split_fqname - split a fqname into a profile and namespace name + * @fqname: a full qualified name in namespace profile format (NOT NULL) + * @ns_name: pointer to portion of the string containing the ns name (NOT NULL) + * + * Returns: profile name or NULL if one is not specified + * + * Split a namespace name from a profile name (see policy.c for naming + * description). If a portion of the name is missing it returns NULL for + * that portion. + * + * NOTE: may modify the @fqname string. The pointers returned point + * into the @fqname string. + */ +char *aa_split_fqname(char *fqname, char **ns_name) +{ + char *name = strim(fqname); + + *ns_name = NULL; + if (name[0] == ':') { + char *split = strchr(&name[1], ':'); + *ns_name = skip_spaces(&name[1]); + if (split) { + /* overwrite ':' with \0 */ + *split++ = 0; + if (strncmp(split, "//", 2) == 0) + split += 2; + name = skip_spaces(split); + } else + /* a ns name without a following profile is allowed */ + name = NULL; + } + if (name && *name == 0) + name = NULL; + + return name; +} + +/** + * aa_info_message - log a none profile related status message + * @str: message to log + */ +void aa_info_message(const char *str) +{ + if (audit_enabled) { + struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.info = str; + aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); + } + printk(KERN_INFO "AppArmor: %s\n", str); +} + +/** + * __aa_kvmalloc - do allocation preferring kmalloc but falling back to vmalloc + * @size: how many bytes of memory are required + * @flags: the type of memory to allocate (see kmalloc). + * + * Return: allocated buffer or NULL if failed + * + * It is possible that policy being loaded from the user is larger than + * what can be allocated by kmalloc, in those cases fall back to vmalloc. + */ +void *__aa_kvmalloc(size_t size, gfp_t flags) +{ + void *buffer = NULL; + + if (size == 0) + return NULL; + + /* do not attempt kmalloc if we need more than 16 pages at once */ + if (size <= (16*PAGE_SIZE)) + buffer = kmalloc(size, flags | GFP_NOIO | __GFP_NOWARN); + if (!buffer) { + if (flags & __GFP_ZERO) + buffer = vzalloc(size); + else + buffer = vmalloc(size); + } + return buffer; +} diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c new file mode 100644 index 000000000..e5f156143 --- /dev/null +++ b/security/apparmor/lsm.c @@ -0,0 +1,949 @@ +/* + * AppArmor security module + * + * This file contains AppArmor LSM hooks. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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/security.h> +#include <linux/moduleparam.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <linux/ptrace.h> +#include <linux/ctype.h> +#include <linux/sysctl.h> +#include <linux/audit.h> +#include <linux/user_namespace.h> +#include <net/sock.h> + +#include "include/apparmor.h" +#include "include/apparmorfs.h" +#include "include/audit.h" +#include "include/capability.h" +#include "include/context.h" +#include "include/file.h" +#include "include/ipc.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/procattr.h" + +/* Flag indicating whether initialization completed */ +int apparmor_initialized __initdata; + +/* + * LSM hook functions + */ + +/* + * free the associated aa_task_cxt and put its profiles + */ +static void apparmor_cred_free(struct cred *cred) +{ + aa_free_task_context(cred_cxt(cred)); + cred_cxt(cred) = NULL; +} + +/* + * allocate the apparmor part of blank credentials + */ +static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp) +{ + /* freed by apparmor_cred_free */ + struct aa_task_cxt *cxt = aa_alloc_task_context(gfp); + if (!cxt) + return -ENOMEM; + + cred_cxt(cred) = cxt; + return 0; +} + +/* + * prepare new aa_task_cxt for modification by prepare_cred block + */ +static int apparmor_cred_prepare(struct cred *new, const struct cred *old, + gfp_t gfp) +{ + /* freed by apparmor_cred_free */ + struct aa_task_cxt *cxt = aa_alloc_task_context(gfp); + if (!cxt) + return -ENOMEM; + + aa_dup_task_context(cxt, cred_cxt(old)); + cred_cxt(new) = cxt; + return 0; +} + +/* + * transfer the apparmor data to a blank set of creds + */ +static void apparmor_cred_transfer(struct cred *new, const struct cred *old) +{ + const struct aa_task_cxt *old_cxt = cred_cxt(old); + struct aa_task_cxt *new_cxt = cred_cxt(new); + + aa_dup_task_context(new_cxt, old_cxt); +} + +static int apparmor_ptrace_access_check(struct task_struct *child, + unsigned int mode) +{ + int error = cap_ptrace_access_check(child, mode); + if (error) + return error; + + return aa_ptrace(current, child, mode); +} + +static int apparmor_ptrace_traceme(struct task_struct *parent) +{ + int error = cap_ptrace_traceme(parent); + if (error) + return error; + + return aa_ptrace(parent, current, PTRACE_MODE_ATTACH); +} + +/* 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; + const struct cred *cred; + + rcu_read_lock(); + cred = __task_cred(target); + profile = aa_cred_profile(cred); + + *effective = cred->cap_effective; + *inheritable = cred->cap_inheritable; + *permitted = cred->cap_permitted; + + if (!unconfined(profile) && !COMPLAIN_MODE(profile)) { + *effective = cap_intersect(*effective, profile->caps.allow); + *permitted = cap_intersect(*permitted, profile->caps.allow); + } + rcu_read_unlock(); + + return 0; +} + +static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, + int cap, int audit) +{ + struct aa_profile *profile; + /* cap_capable returns 0 on success, else -EPERM */ + int error = cap_capable(cred, ns, cap, audit); + if (!error) { + profile = aa_cred_profile(cred); + if (!unconfined(profile)) + error = aa_capable(profile, cap, audit); + } + return error; +} + +/** + * common_perm - basic common permission check wrapper fn for paths + * @op: operation being checked + * @path: path to check permission of (NOT NULL) + * @mask: requested permissions mask + * @cond: conditional info for the permission request (NOT NULL) + * + * Returns: %0 else error code if error or permission denied + */ +static int common_perm(int op, struct path *path, u32 mask, + struct path_cond *cond) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_path_perm(op, profile, path, 0, mask, cond); + + return error; +} + +/** + * common_perm_dir_dentry - common permission wrapper when path is dir, dentry + * @op: operation being checked + * @dir: directory of the dentry (NOT NULL) + * @dentry: dentry to check (NOT NULL) + * @mask: requested permissions mask + * @cond: conditional info for the permission request (NOT NULL) + * + * Returns: %0 else error code if error or permission denied + */ +static int common_perm_dir_dentry(int op, struct path *dir, + struct dentry *dentry, u32 mask, + struct path_cond *cond) +{ + struct path path = { dir->mnt, dentry }; + + return common_perm(op, &path, mask, cond); +} + +/** + * common_perm_mnt_dentry - common permission wrapper when mnt, dentry + * @op: operation being checked + * @mnt: mount point of dentry (NOT NULL) + * @dentry: dentry to check (NOT NULL) + * @mask: requested permissions mask + * + * Returns: %0 else error code if error or permission denied + */ +static int common_perm_mnt_dentry(int op, struct vfsmount *mnt, + struct dentry *dentry, u32 mask) +{ + struct path path = { mnt, dentry }; + struct path_cond cond = { d_backing_inode(dentry)->i_uid, + d_backing_inode(dentry)->i_mode + }; + + return common_perm(op, &path, mask, &cond); +} + +/** + * common_perm_rm - common permission wrapper for operations doing rm + * @op: operation being checked + * @dir: directory that the dentry is in (NOT NULL) + * @dentry: dentry being rm'd (NOT NULL) + * @mask: requested permission mask + * + * Returns: %0 else error code if error or permission denied + */ +static int common_perm_rm(int op, struct path *dir, + struct dentry *dentry, u32 mask) +{ + struct inode *inode = d_backing_inode(dentry); + struct path_cond cond = { }; + + if (!inode || !dir->mnt || !mediated_filesystem(dentry)) + return 0; + + cond.uid = inode->i_uid; + cond.mode = inode->i_mode; + + return common_perm_dir_dentry(op, dir, dentry, mask, &cond); +} + +/** + * common_perm_create - common permission wrapper for operations doing create + * @op: operation being checked + * @dir: directory that dentry will be created in (NOT NULL) + * @dentry: dentry to create (NOT NULL) + * @mask: request permission mask + * @mode: created file mode + * + * Returns: %0 else error code if error or permission denied + */ +static int common_perm_create(int op, struct path *dir, struct dentry *dentry, + u32 mask, umode_t mode) +{ + struct path_cond cond = { current_fsuid(), mode }; + + if (!dir->mnt || !mediated_filesystem(dir->dentry)) + return 0; + + return common_perm_dir_dentry(op, dir, dentry, mask, &cond); +} + +static int apparmor_path_unlink(struct path *dir, struct dentry *dentry) +{ + return common_perm_rm(OP_UNLINK, dir, dentry, AA_MAY_DELETE); +} + +static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry, + umode_t mode) +{ + return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE, + S_IFDIR); +} + +static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry) +{ + return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE); +} + +static int apparmor_path_mknod(struct path *dir, struct dentry *dentry, + umode_t mode, unsigned int dev) +{ + return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode); +} + +static int apparmor_path_truncate(struct path *path) +{ + struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, + d_backing_inode(path->dentry)->i_mode + }; + + if (!path->mnt || !mediated_filesystem(path->dentry)) + return 0; + + return common_perm(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE, + &cond); +} + +static int apparmor_path_symlink(struct path *dir, struct dentry *dentry, + const char *old_name) +{ + return common_perm_create(OP_SYMLINK, dir, dentry, AA_MAY_CREATE, + S_IFLNK); +} + +static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir, + struct dentry *new_dentry) +{ + struct aa_profile *profile; + int error = 0; + + if (!mediated_filesystem(old_dentry)) + return 0; + + profile = aa_current_profile(); + if (!unconfined(profile)) + error = aa_path_link(profile, old_dentry, new_dir, new_dentry); + return error; +} + +static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry, + struct path *new_dir, struct dentry *new_dentry) +{ + struct aa_profile *profile; + int error = 0; + + if (!mediated_filesystem(old_dentry)) + return 0; + + profile = aa_current_profile(); + if (!unconfined(profile)) { + struct path old_path = { old_dir->mnt, old_dentry }; + struct path new_path = { new_dir->mnt, new_dentry }; + struct path_cond cond = { d_backing_inode(old_dentry)->i_uid, + 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, + &cond); + if (!error) + error = aa_path_perm(OP_RENAME_DEST, profile, &new_path, + 0, MAY_WRITE | AA_MAY_META_WRITE | + AA_MAY_CREATE, &cond); + + } + return error; +} + +static int apparmor_path_chmod(struct path *path, umode_t mode) +{ + if (!mediated_filesystem(path->dentry)) + return 0; + + return common_perm_mnt_dentry(OP_CHMOD, path->mnt, path->dentry, AA_MAY_CHMOD); +} + +static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid) +{ + struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, + d_backing_inode(path->dentry)->i_mode + }; + + if (!mediated_filesystem(path->dentry)) + return 0; + + return common_perm(OP_CHOWN, path, AA_MAY_CHOWN, &cond); +} + +static int apparmor_inode_getattr(const struct path *path) +{ + if (!mediated_filesystem(path->dentry)) + return 0; + + return common_perm_mnt_dentry(OP_GETATTR, path->mnt, path->dentry, + AA_MAY_META_READ); +} + +static int apparmor_file_open(struct file *file, const struct cred *cred) +{ + struct aa_file_cxt *fcxt = file->f_security; + struct aa_profile *profile; + int error = 0; + + if (!mediated_filesystem(file->f_path.dentry)) + return 0; + + /* If in exec, permission is handled by bprm hooks. + * Cache permissions granted by the previous exec check, with + * implicit read and executable mmap which are required to + * actually execute the image. + */ + if (current->in_execve) { + fcxt->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP; + return 0; + } + + profile = aa_cred_profile(cred); + if (!unconfined(profile)) { + 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, + aa_map_file_to_perms(file), &cond); + /* todo cache full allowed permissions set and state */ + fcxt->allow = aa_map_file_to_perms(file); + } + + return error; +} + +static int apparmor_file_alloc_security(struct file *file) +{ + /* freed by apparmor_file_free_security */ + file->f_security = aa_alloc_file_context(GFP_KERNEL); + if (!file->f_security) + return -ENOMEM; + return 0; + +} + +static void apparmor_file_free_security(struct file *file) +{ + struct aa_file_cxt *cxt = file->f_security; + + aa_free_file_context(cxt); +} + +static int common_file_perm(int op, struct file *file, u32 mask) +{ + struct aa_file_cxt *fcxt = file->f_security; + struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred); + int error = 0; + + BUG_ON(!fprofile); + + if (!file->f_path.mnt || + !mediated_filesystem(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 & ~fcxt->allow))) + error = aa_file_perm(op, profile, file, mask); + + return error; +} + +static int apparmor_file_permission(struct file *file, int mask) +{ + return common_file_perm(OP_FPERM, file, mask); +} + +static int apparmor_file_lock(struct file *file, unsigned int cmd) +{ + u32 mask = AA_MAY_LOCK; + + if (cmd == F_WRLCK) + mask |= MAY_WRITE; + + return common_file_perm(OP_FLOCK, file, mask); +} + +static int common_mmap(int op, struct file *file, unsigned long prot, + unsigned long flags) +{ + int mask = 0; + + if (!file || !file->f_security) + return 0; + + if (prot & PROT_READ) + mask |= MAY_READ; + /* + * Private mappings don't require write perms since they don't + * write back to the files + */ + if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE)) + mask |= MAY_WRITE; + if (prot & PROT_EXEC) + mask |= AA_EXEC_MMAP; + + return common_file_perm(op, file, mask); +} + +static int apparmor_mmap_file(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + return common_mmap(OP_FMMAP, file, prot, flags); +} + +static int apparmor_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) +{ + return common_mmap(OP_FMPROT, vma->vm_file, prot, + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); +} + +static int apparmor_getprocattr(struct task_struct *task, char *name, + char **value) +{ + int error = -ENOENT; + /* released below */ + const struct cred *cred = get_task_cred(task); + struct aa_task_cxt *cxt = cred_cxt(cred); + struct aa_profile *profile = NULL; + + if (strcmp(name, "current") == 0) + profile = aa_get_newest_profile(cxt->profile); + else if (strcmp(name, "prev") == 0 && cxt->previous) + profile = aa_get_newest_profile(cxt->previous); + else if (strcmp(name, "exec") == 0 && cxt->onexec) + profile = aa_get_newest_profile(cxt->onexec); + else + error = -EINVAL; + + if (profile) + error = aa_getprocattr(profile, value); + + aa_put_profile(profile); + put_cred(cred); + + return error; +} + +static int apparmor_setprocattr(struct task_struct *task, char *name, + void *value, size_t size) +{ + struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; + char *command, *args = value; + size_t arg_size; + int error; + + if (size == 0) + return -EINVAL; + /* args points to a PAGE_SIZE buffer, AppArmor requires that + * the buffer must be null terminated or have size <= PAGE_SIZE -1 + * so that AppArmor can null terminate them + */ + if (args[size - 1] != '\0') { + if (size == PAGE_SIZE) + return -EINVAL; + args[size] = '\0'; + } + + /* task can only write its own attributes */ + if (current != task) + return -EACCES; + + args = value; + args = strim(args); + command = strsep(&args, " "); + if (!args) + return -EINVAL; + args = skip_spaces(args); + if (!*args) + return -EINVAL; + + arg_size = size - (args - (char *) value); + if (strcmp(name, "current") == 0) { + if (strcmp(command, "changehat") == 0) { + error = aa_setprocattr_changehat(args, arg_size, + !AA_DO_TEST); + } else if (strcmp(command, "permhat") == 0) { + error = aa_setprocattr_changehat(args, arg_size, + AA_DO_TEST); + } else if (strcmp(command, "changeprofile") == 0) { + error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, + !AA_DO_TEST); + } else if (strcmp(command, "permprofile") == 0) { + error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, + AA_DO_TEST); + } else + goto fail; + } else if (strcmp(name, "exec") == 0) { + if (strcmp(command, "exec") == 0) + error = aa_setprocattr_changeprofile(args, AA_ONEXEC, + !AA_DO_TEST); + else + goto fail; + } else + /* only support the "current" and "exec" process attributes */ + return -EINVAL; + + if (!error) + error = size; + return error; + +fail: + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.profile = aa_current_profile(); + aad.op = OP_SETPROCATTR; + aad.info = name; + aad.error = -EINVAL; + aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); + return -EINVAL; +} + +static int apparmor_task_setrlimit(struct task_struct *task, + unsigned int resource, struct rlimit *new_rlim) +{ + struct aa_profile *profile = __aa_current_profile(); + int error = 0; + + if (!unconfined(profile)) + error = aa_task_setrlimit(profile, task, resource, new_rlim); + + return error; +} + +static struct security_operations apparmor_ops = { + .name = "apparmor", + + .ptrace_access_check = apparmor_ptrace_access_check, + .ptrace_traceme = apparmor_ptrace_traceme, + .capget = apparmor_capget, + .capable = apparmor_capable, + + .path_link = apparmor_path_link, + .path_unlink = apparmor_path_unlink, + .path_symlink = apparmor_path_symlink, + .path_mkdir = apparmor_path_mkdir, + .path_rmdir = apparmor_path_rmdir, + .path_mknod = apparmor_path_mknod, + .path_rename = apparmor_path_rename, + .path_chmod = apparmor_path_chmod, + .path_chown = apparmor_path_chown, + .path_truncate = apparmor_path_truncate, + .inode_getattr = apparmor_inode_getattr, + + .file_open = apparmor_file_open, + .file_permission = apparmor_file_permission, + .file_alloc_security = apparmor_file_alloc_security, + .file_free_security = apparmor_file_free_security, + .mmap_file = apparmor_mmap_file, + .mmap_addr = cap_mmap_addr, + .file_mprotect = apparmor_file_mprotect, + .file_lock = apparmor_file_lock, + + .getprocattr = apparmor_getprocattr, + .setprocattr = apparmor_setprocattr, + + .cred_alloc_blank = apparmor_cred_alloc_blank, + .cred_free = apparmor_cred_free, + .cred_prepare = apparmor_cred_prepare, + .cred_transfer = apparmor_cred_transfer, + + .bprm_set_creds = apparmor_bprm_set_creds, + .bprm_committing_creds = apparmor_bprm_committing_creds, + .bprm_committed_creds = apparmor_bprm_committed_creds, + .bprm_secureexec = apparmor_bprm_secureexec, + + .task_setrlimit = apparmor_task_setrlimit, +}; + +/* + * AppArmor sysfs module parameters + */ + +static int param_set_aabool(const char *val, const struct kernel_param *kp); +static int param_get_aabool(char *buffer, const struct kernel_param *kp); +#define param_check_aabool param_check_bool +static struct kernel_param_ops param_ops_aabool = { + .flags = KERNEL_PARAM_OPS_FL_NOARG, + .set = param_set_aabool, + .get = param_get_aabool +}; + +static int param_set_aauint(const char *val, const struct kernel_param *kp); +static int param_get_aauint(char *buffer, const struct kernel_param *kp); +#define param_check_aauint param_check_uint +static struct kernel_param_ops param_ops_aauint = { + .set = param_set_aauint, + .get = param_get_aauint +}; + +static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp); +static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp); +#define param_check_aalockpolicy param_check_bool +static struct kernel_param_ops param_ops_aalockpolicy = { + .flags = KERNEL_PARAM_OPS_FL_NOARG, + .set = param_set_aalockpolicy, + .get = param_get_aalockpolicy +}; + +static int param_set_audit(const char *val, struct kernel_param *kp); +static int param_get_audit(char *buffer, struct kernel_param *kp); + +static int param_set_mode(const char *val, struct kernel_param *kp); +static int param_get_mode(char *buffer, struct kernel_param *kp); + +/* Flag values, also controllable via /sys/module/apparmor/parameters + * We define special types as we want to do additional mediation. + */ + +/* AppArmor global enforcement switch - complain, enforce, kill */ +enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE; +module_param_call(mode, param_set_mode, param_get_mode, + &aa_g_profile_mode, S_IRUSR | S_IWUSR); + +/* Debug mode */ +bool aa_g_debug; +module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR); + +/* Audit mode */ +enum audit_mode aa_g_audit; +module_param_call(audit, param_set_audit, param_get_audit, + &aa_g_audit, S_IRUSR | S_IWUSR); + +/* Determines if audit header is included in audited messages. This + * provides more context if the audit daemon is not running + */ +bool aa_g_audit_header = 1; +module_param_named(audit_header, aa_g_audit_header, aabool, + S_IRUSR | S_IWUSR); + +/* lock out loading/removal of policy + * TODO: add in at boot loading of policy, which is the only way to + * load policy, if lock_policy is set + */ +bool aa_g_lock_policy; +module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy, + S_IRUSR | S_IWUSR); + +/* Syscall logging mode */ +bool aa_g_logsyscall; +module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUSR | S_IWUSR); + +/* Maximum pathname length before accesses will start getting rejected */ +unsigned int aa_g_path_max = 2 * PATH_MAX; +module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR); + +/* Determines how paranoid loading of policy is and how much verification + * on the loaded policy is done. + */ +bool aa_g_paranoid_load = 1; +module_param_named(paranoid_load, aa_g_paranoid_load, aabool, + S_IRUSR | S_IWUSR); + +/* Boot time disable flag */ +static bool apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE; +module_param_named(enabled, apparmor_enabled, bool, S_IRUGO); + +static int __init apparmor_enabled_setup(char *str) +{ + unsigned long enabled; + int error = kstrtoul(str, 0, &enabled); + if (!error) + apparmor_enabled = enabled ? 1 : 0; + return 1; +} + +__setup("apparmor=", apparmor_enabled_setup); + +/* set global flag turning off the ability to load policy */ +static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + if (aa_g_lock_policy) + return -EACCES; + return param_set_bool(val, kp); +} + +static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + return param_get_bool(buffer, kp); +} + +static int param_set_aabool(const char *val, const struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + return param_set_bool(val, kp); +} + +static int param_get_aabool(char *buffer, const struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + return param_get_bool(buffer, kp); +} + +static int param_set_aauint(const char *val, const struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + return param_set_uint(val, kp); +} + +static int param_get_aauint(char *buffer, const struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + return param_get_uint(buffer, kp); +} + +static int param_get_audit(char *buffer, struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]); +} + +static int param_set_audit(const char *val, struct kernel_param *kp) +{ + int i; + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + if (!val) + return -EINVAL; + + for (i = 0; i < AUDIT_MAX_INDEX; i++) { + if (strcmp(val, audit_mode_names[i]) == 0) { + aa_g_audit = i; + return 0; + } + } + + return -EINVAL; +} + +static int param_get_mode(char *buffer, struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + return sprintf(buffer, "%s", aa_profile_mode_names[aa_g_profile_mode]); +} + +static int param_set_mode(const char *val, struct kernel_param *kp) +{ + int i; + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + if (!val) + return -EINVAL; + + for (i = 0; i < APPARMOR_MODE_NAMES_MAX_INDEX; i++) { + if (strcmp(val, aa_profile_mode_names[i]) == 0) { + aa_g_profile_mode = i; + return 0; + } + } + + return -EINVAL; +} + +/* + * AppArmor init functions + */ + +/** + * set_init_cxt - set a task context and profile on the first task. + * + * TODO: allow setting an alternate profile than unconfined + */ +static int __init set_init_cxt(void) +{ + struct cred *cred = (struct cred *)current->real_cred; + struct aa_task_cxt *cxt; + + cxt = aa_alloc_task_context(GFP_KERNEL); + if (!cxt) + return -ENOMEM; + + cxt->profile = aa_get_profile(root_ns->unconfined); + cred_cxt(cred) = cxt; + + return 0; +} + +static int __init apparmor_init(void) +{ + int error; + + if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) { + aa_info_message("AppArmor disabled by boot time parameter"); + apparmor_enabled = 0; + return 0; + } + + error = aa_alloc_root_ns(); + if (error) { + AA_ERROR("Unable to allocate default profile namespace\n"); + goto alloc_out; + } + + error = set_init_cxt(); + if (error) { + AA_ERROR("Failed to set context on init task\n"); + goto register_security_out; + } + + error = register_security(&apparmor_ops); + if (error) { + struct cred *cred = (struct cred *)current->real_cred; + aa_free_task_context(cred_cxt(cred)); + cred_cxt(cred) = NULL; + AA_ERROR("Unable to register AppArmor\n"); + goto register_security_out; + } + + /* Report that AppArmor successfully initialized */ + apparmor_initialized = 1; + if (aa_g_profile_mode == APPARMOR_COMPLAIN) + aa_info_message("AppArmor initialized: complain mode enabled"); + else if (aa_g_profile_mode == APPARMOR_KILL) + aa_info_message("AppArmor initialized: kill mode enabled"); + else + aa_info_message("AppArmor initialized"); + + return error; + +register_security_out: + aa_free_root_ns(); + +alloc_out: + aa_destroy_aafs(); + + apparmor_enabled = 0; + return error; +} + +security_initcall(apparmor_init); diff --git a/security/apparmor/match.c b/security/apparmor/match.c new file mode 100644 index 000000000..727eb4200 --- /dev/null +++ b/security/apparmor/match.c @@ -0,0 +1,428 @@ +/* + * AppArmor security module + * + * This file contains AppArmor dfa based regular expression matching engine + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 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/errno.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/err.h> +#include <linux/kref.h> + +#include "include/apparmor.h" +#include "include/match.h" + +#define base_idx(X) ((X) & 0xffffff) + +/** + * unpack_table - unpack a dfa table (one of accept, default, base, next check) + * @blob: data to unpack (NOT NULL) + * @bsize: size of blob + * + * Returns: pointer to table else NULL on failure + * + * NOTE: must be freed by kvfree (not kfree) + */ +static struct table_header *unpack_table(char *blob, size_t bsize) +{ + struct table_header *table = NULL; + struct table_header th; + size_t tsize; + + if (bsize < sizeof(struct table_header)) + goto out; + + /* loaded td_id's start at 1, subtract 1 now to avoid doing + * it every time we use td_id as an index + */ + th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1; + th.td_flags = be16_to_cpu(*(u16 *) (blob + 2)); + th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8)); + blob += sizeof(struct table_header); + + if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 || + th.td_flags == YYTD_DATA8)) + goto out; + + tsize = table_size(th.td_lolen, th.td_flags); + if (bsize < tsize) + goto out; + + table = kvzalloc(tsize); + if (table) { + *table = th; + if (th.td_flags == YYTD_DATA8) + UNPACK_ARRAY(table->td_data, blob, th.td_lolen, + u8, byte_to_byte); + else if (th.td_flags == YYTD_DATA16) + UNPACK_ARRAY(table->td_data, blob, th.td_lolen, + u16, be16_to_cpu); + else if (th.td_flags == YYTD_DATA32) + UNPACK_ARRAY(table->td_data, blob, th.td_lolen, + u32, be32_to_cpu); + else + goto fail; + } + +out: + /* if table was vmalloced make sure the page tables are synced + * before it is used, as it goes live to all cpus. + */ + if (is_vmalloc_addr(table)) + vm_unmap_aliases(); + return table; +fail: + kvfree(table); + return NULL; +} + +/** + * verify_dfa - verify that transitions and states in the tables are in bounds. + * @dfa: dfa to test (NOT NULL) + * @flags: flags controlling what type of accept table are acceptable + * + * Assumes dfa has gone through the first pass verification done by unpacking + * NOTE: this does not valid accept table values + * + * Returns: %0 else error code on failure to verify + */ +static int verify_dfa(struct aa_dfa *dfa, int flags) +{ + size_t i, state_count, trans_count; + int error = -EPROTO; + + /* check that required tables exist */ + if (!(dfa->tables[YYTD_ID_DEF] && + dfa->tables[YYTD_ID_BASE] && + dfa->tables[YYTD_ID_NXT] && dfa->tables[YYTD_ID_CHK])) + goto out; + + /* accept.size == default.size == base.size */ + state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + if (ACCEPT1_FLAGS(flags)) { + if (!dfa->tables[YYTD_ID_ACCEPT]) + goto out; + if (state_count != dfa->tables[YYTD_ID_ACCEPT]->td_lolen) + goto out; + } + if (ACCEPT2_FLAGS(flags)) { + if (!dfa->tables[YYTD_ID_ACCEPT2]) + goto out; + if (state_count != dfa->tables[YYTD_ID_ACCEPT2]->td_lolen) + goto out; + } + if (state_count != dfa->tables[YYTD_ID_DEF]->td_lolen) + goto out; + + /* next.size == chk.size */ + trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen; + if (trans_count != dfa->tables[YYTD_ID_CHK]->td_lolen) + goto out; + + /* if equivalence classes then its table size must be 256 */ + if (dfa->tables[YYTD_ID_EC] && + dfa->tables[YYTD_ID_EC]->td_lolen != 256) + goto out; + + if (flags & DFA_FLAG_VERIFY_STATES) { + for (i = 0; i < state_count; i++) { + if (DEFAULT_TABLE(dfa)[i] >= state_count) + goto out; + if (base_idx(BASE_TABLE(dfa)[i]) + 255 >= trans_count) { + printk(KERN_ERR "AppArmor DFA next/check upper " + "bounds error\n"); + goto out; + } + } + + for (i = 0; i < trans_count; i++) { + if (NEXT_TABLE(dfa)[i] >= state_count) + goto out; + if (CHECK_TABLE(dfa)[i] >= state_count) + goto out; + } + } + + error = 0; +out: + return error; +} + +/** + * dfa_free - free a dfa allocated by aa_dfa_unpack + * @dfa: the dfa to free (MAYBE NULL) + * + * Requires: reference count to dfa == 0 + */ +static void dfa_free(struct aa_dfa *dfa) +{ + if (dfa) { + int i; + + for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) { + kvfree(dfa->tables[i]); + dfa->tables[i] = NULL; + } + kfree(dfa); + } +} + +/** + * aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa) + * @kr: kref callback for freeing of a dfa (NOT NULL) + */ +void aa_dfa_free_kref(struct kref *kref) +{ + struct aa_dfa *dfa = container_of(kref, struct aa_dfa, count); + dfa_free(dfa); +} + +/** + * aa_dfa_unpack - unpack the binary tables of a serialized dfa + * @blob: aligned serialized stream of data to unpack (NOT NULL) + * @size: size of data to unpack + * @flags: flags controlling what type of accept tables are acceptable + * + * Unpack a dfa that has been serialized. To find information on the dfa + * format look in Documentation/security/apparmor.txt + * Assumes the dfa @blob stream has been aligned on a 8 byte boundary + * + * Returns: an unpacked dfa ready for matching or ERR_PTR on failure + */ +struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) +{ + int hsize; + int error = -ENOMEM; + char *data = blob; + struct table_header *table = NULL; + struct aa_dfa *dfa = kzalloc(sizeof(struct aa_dfa), GFP_KERNEL); + if (!dfa) + goto fail; + + kref_init(&dfa->count); + + error = -EPROTO; + + /* get dfa table set header */ + if (size < sizeof(struct table_set_header)) + goto fail; + + if (ntohl(*(u32 *) data) != YYTH_MAGIC) + goto fail; + + hsize = ntohl(*(u32 *) (data + 4)); + if (size < hsize) + goto fail; + + dfa->flags = ntohs(*(u16 *) (data + 12)); + data += hsize; + size -= hsize; + + while (size > 0) { + table = unpack_table(data, size); + if (!table) + goto fail; + + switch (table->td_id) { + case YYTD_ID_ACCEPT: + if (!(table->td_flags & ACCEPT1_FLAGS(flags))) + goto fail; + break; + case YYTD_ID_ACCEPT2: + if (!(table->td_flags & ACCEPT2_FLAGS(flags))) + goto fail; + break; + case YYTD_ID_BASE: + if (table->td_flags != YYTD_DATA32) + goto fail; + break; + case YYTD_ID_DEF: + case YYTD_ID_NXT: + case YYTD_ID_CHK: + if (table->td_flags != YYTD_DATA16) + goto fail; + break; + case YYTD_ID_EC: + if (table->td_flags != YYTD_DATA8) + goto fail; + break; + default: + goto fail; + } + /* check for duplicate table entry */ + if (dfa->tables[table->td_id]) + goto fail; + dfa->tables[table->td_id] = table; + data += table_size(table->td_lolen, table->td_flags); + size -= table_size(table->td_lolen, table->td_flags); + table = NULL; + } + + error = verify_dfa(dfa, flags); + if (error) + goto fail; + + return dfa; + +fail: + kvfree(table); + dfa_free(dfa); + return ERR_PTR(error); +} + +/** + * aa_dfa_match_len - traverse @dfa to find state @str stops at + * @dfa: the dfa to match @str against (NOT NULL) + * @start: the state of the dfa to start matching in + * @str: the string of bytes to match against the dfa (NOT NULL) + * @len: length of the string of bytes to match + * + * aa_dfa_match_len will match @str against the dfa and return the state it + * finished matching in. The final state can be used to look up the accepting + * label, or as the start state of a continuing match. + * + * This function will happily match again the 0 byte and only finishes + * when @len input is consumed. + * + * Returns: final state reached after input is consumed + */ +unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, + const char *str, int len) +{ + u16 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u16 *next = NEXT_TABLE(dfa); + u16 *check = CHECK_TABLE(dfa); + unsigned int state = start, pos; + + if (state == 0) + return 0; + + /* current state is <state>, matching character *str */ + if (dfa->tables[YYTD_ID_EC]) { + /* Equivalence class table defined */ + u8 *equiv = EQUIV_TABLE(dfa); + /* default is direct to next state */ + for (; len; len--) { + pos = base_idx(base[state]) + equiv[(u8) *str++]; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } + } else { + /* default is direct to next state */ + for (; len; len--) { + pos = base_idx(base[state]) + (u8) *str++; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } + } + + return state; +} + +/** + * aa_dfa_match - traverse @dfa to find state @str stops at + * @dfa: the dfa to match @str against (NOT NULL) + * @start: the state of the dfa to start matching in + * @str: the null terminated string of bytes to match against the dfa (NOT NULL) + * + * aa_dfa_match will match @str against the dfa and return the state it + * finished matching in. The final state can be used to look up the accepting + * label, or as the start state of a continuing match. + * + * Returns: final state reached after input is consumed + */ +unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, + const char *str) +{ + u16 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u16 *next = NEXT_TABLE(dfa); + u16 *check = CHECK_TABLE(dfa); + unsigned int state = start, pos; + + if (state == 0) + return 0; + + /* current state is <state>, matching character *str */ + if (dfa->tables[YYTD_ID_EC]) { + /* Equivalence class table defined */ + u8 *equiv = EQUIV_TABLE(dfa); + /* default is direct to next state */ + while (*str) { + pos = base_idx(base[state]) + equiv[(u8) *str++]; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } + } else { + /* default is direct to next state */ + while (*str) { + pos = base_idx(base[state]) + (u8) *str++; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } + } + + return state; +} + +/** + * aa_dfa_next - step one character to the next state in the dfa + * @dfa: the dfa to tranverse (NOT NULL) + * @state: the state to start in + * @c: the input character to transition on + * + * aa_dfa_match will step through the dfa by one input character @c + * + * Returns: state reach after input @c + */ +unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, + const char c) +{ + u16 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u16 *next = NEXT_TABLE(dfa); + u16 *check = CHECK_TABLE(dfa); + unsigned int pos; + + /* current state is <state>, matching character *str */ + if (dfa->tables[YYTD_ID_EC]) { + /* Equivalence class table defined */ + u8 *equiv = EQUIV_TABLE(dfa); + /* default is direct to next state */ + + pos = base_idx(base[state]) + equiv[(u8) c]; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } else { + /* default is direct to next state */ + pos = base_idx(base[state]) + (u8) c; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } + + return state; +} diff --git a/security/apparmor/path.c b/security/apparmor/path.c new file mode 100644 index 000000000..71e0e3a15 --- /dev/null +++ b/security/apparmor/path.c @@ -0,0 +1,236 @@ +/* + * AppArmor security module + * + * This file contains AppArmor function for pathnames + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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/magic.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <linux/nsproxy.h> +#include <linux/path.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/fs_struct.h> + +#include "include/apparmor.h" +#include "include/path.h" +#include "include/policy.h" + + +/* modified from dcache.c */ +static int prepend(char **buffer, int buflen, const char *str, int namelen) +{ + buflen -= namelen; + if (buflen < 0) + return -ENAMETOOLONG; + *buffer -= namelen; + memcpy(*buffer, str, namelen); + return 0; +} + +#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT) + +/** + * 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 + * + * Handle path name lookup. + * + * Returns: %0 else error code if path lookup fails + * When no error the path name is returned in @name which points to + * to a position in @buf + */ +static int d_namespace_path(struct path *path, char *buf, int buflen, + char **name, int flags) +{ + char *res; + int error = 0; + int connected = 1; + + if (path->mnt->mnt_flags & MNT_INTERNAL) { + /* it's not mounted anywhere */ + res = dentry_path(path->dentry, buf, buflen); + *name = res; + if (IS_ERR(res)) { + *name = buf; + return PTR_ERR(res); + } + if (path->dentry->d_sb->s_magic == PROC_SUPER_MAGIC && + strncmp(*name, "/sys/", 5) == 0) { + /* TODO: convert over to using a per namespace + * control instead of hard coded /proc + */ + return prepend(name, *name - buf, "/proc", 5); + } + return 0; + } + + /* resolve paths relative to chroot?*/ + if (flags & PATH_CHROOT_REL) { + struct path root; + get_fs_root(current->fs, &root); + res = __d_path(path, &root, buf, buflen); + path_put(&root); + } else { + res = d_absolute_path(path, buf, buflen); + if (!our_mnt(path->mnt)) + connected = 0; + } + + /* handle error conditions - and still allow a partial path to + * be returned. + */ + if (!res || IS_ERR(res)) { + if (PTR_ERR(res) == -ENAMETOOLONG) + return -ENAMETOOLONG; + connected = 0; + res = dentry_path_raw(path->dentry, buf, buflen); + if (IS_ERR(res)) { + error = PTR_ERR(res); + *name = buf; + goto out; + }; + } else if (!our_mnt(path->mnt)) + connected = 0; + + *name = res; + + /* Handle two cases: + * 1. A deleted dentry && profile is not allowing mediation of deleted + * 2. On some filesystems, newly allocated dentries appear to the + * security_path hooks as a deleted dentry except without an inode + * allocated. + */ + if (d_unlinked(path->dentry) && d_is_positive(path->dentry) && + !(flags & PATH_MEDIATE_DELETED)) { + error = -ENOENT; + goto out; + } + + /* If the path is not connected to the expected root, + * check if it is a sysctl and handle specially else remove any + * leading / that __d_path may have returned. + * Unless + * specifically directed to connect the path, + * OR + * if in a chroot and doing chroot relative paths and the path + * resolves to the namespace root (would be connected outside + * of chroot) and specifically directed to connect paths to + * namespace root. + */ + if (!connected) { + if (!(flags & PATH_CONNECT_PATH) && + !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) && + our_mnt(path->mnt))) { + /* disconnected path, don't return pathname starting + * with '/' + */ + error = -EACCES; + if (*res == '/') + *name = res + 1; + } + } + +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(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"; + } + + return error; +} + +/** + * aa_path_name - compute the pathname of a file + * @path: path the file (NOT NULL) + * @flags: flags controlling path name generation + * @buffer: buffer that aa_get_name() allocated (NOT NULL) + * @name: Returns - the generated path name if !error (NOT NULL) + * @info: Returns - information on why the path lookup failed (MAYBE NULL) + * + * @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 + * may contain a partial or invalid name that can be used for audit purposes, + * but it can not be used for mediation. + * + * We need PATH_IS_DIR to indicate whether the file is a directory or not + * because the file may not yet exist, and so we cannot check the inode's + * file type. + * + * Returns: %0 else error code if could retrieve name + */ +int aa_path_name(struct path *path, int flags, char **buffer, const char **name, + const char **info) +{ + 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; + } + *buffer = buf; + *name = str; + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c new file mode 100644 index 000000000..705c2879d --- /dev/null +++ b/security/apparmor/policy.c @@ -0,0 +1,1301 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy manipulation functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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. + * + * + * AppArmor policy is based around profiles, which contain the rules a + * task is confined by. Every task in the system has a profile attached + * to it determined either by matching "unconfined" tasks against the + * visible set of profiles or by following a profiles attachment rules. + * + * Each profile exists in a profile namespace which is a container of + * visible profiles. Each namespace contains a special "unconfined" profile, + * which doesn't enforce any confinement on a task beyond DAC. + * + * Namespace and profile names can be written together in either + * of two syntaxes. + * :namespace:profile - used by kernel interfaces for easy detection + * namespace://profile - used by policy + * + * Profile names can not start with : or @ or ^ and may not contain \0 + * + * Reserved profile names + * unconfined - special automatically generated unconfined profile + * inherit - special name to indicate profile inheritance + * null-XXXX-YYYY - special automatically generated learning profiles + * + * Namespace names may not start with / or @ and may not contain \0 or : + * Reserved namespace names + * user-XXXX - user defined profiles + * + * a // in a profile or namespace name indicates a hierarchical name with the + * name before the // being the parent and the name after the child. + * + * Profile and namespace hierarchies serve two different but similar purposes. + * The namespace contains the set of visible profiles that are considered + * for attachment. The hierarchy of namespaces allows for virtualizing + * the namespace so that for example a chroot can have its own set of profiles + * which may define some local user namespaces. + * The profile hierarchy severs two distinct purposes, + * - it allows for sub profiles or hats, which allows an application to run + * subprograms under its own profile with different restriction than it + * self, and not have it use the system profile. + * eg. if a mail program starts an editor, the policy might make the + * restrictions tighter on the editor tighter than the mail program, + * and definitely different than general editor restrictions + * - it allows for binary hierarchy of profiles, so that execution history + * is preserved. This feature isn't exploited by AppArmor reference policy + * but is allowed. NOTE: this is currently suboptimal because profile + * aliasing is not currently implemented so that a profile for each + * level must be defined. + * eg. /bin/bash///bin/ls as a name would indicate /bin/ls was started + * from /bin/bash + * + * A profile or namespace name that can contain one or more // separators + * is referred to as an hname (hierarchical). + * eg. /bin/bash//bin/ls + * + * An fqname is a name that may contain both namespace and profile hnames. + * eg. :ns:/bin/bash//bin/ls + * + * NOTES: + * - locking of profile lists is currently fairly coarse. All profile + * lists within a namespace use the namespace lock. + * FIXME: move profile lists to using rcu_lists + */ + +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> + +#include "include/apparmor.h" +#include "include/capability.h" +#include "include/context.h" +#include "include/file.h" +#include "include/ipc.h" +#include "include/match.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/policy_unpack.h" +#include "include/resource.h" + + +/* root profile namespace */ +struct aa_namespace *root_ns; + +const char *const aa_profile_mode_names[] = { + "enforce", + "complain", + "kill", + "unconfined", +}; + +/** + * hname_tail - find the last component of an hname + * @name: hname to find the base profile name component of (NOT NULL) + * + * Returns: the tail (base profile name) name component of an hname + */ +static const char *hname_tail(const char *hname) +{ + char *split; + hname = strim((char *)hname); + for (split = strstr(hname, "//"); split; split = strstr(hname, "//")) + hname = split + 2; + + return hname; +} + +/** + * 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) + * + * Note: this fn creates a copy of strings passed in + * + * Returns: true if policy init successful + */ +static bool policy_init(struct aa_policy *policy, const char *prefix, + const char *name) +{ + /* freed by policy_free */ + if (prefix) { + policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3, + GFP_KERNEL); + if (policy->hname) + sprintf(policy->hname, "%s//%s", prefix, name); + } else + policy->hname = kstrdup(name, GFP_KERNEL); + if (!policy->hname) + return 0; + /* base.name is a substring of fqname */ + policy->name = (char *)hname_tail(policy->hname); + INIT_LIST_HEAD(&policy->list); + INIT_LIST_HEAD(&policy->profiles); + + return 1; +} + +/** + * policy_destroy - free the elements referenced by @policy + * @policy: policy that is to have its elements freed (NOT NULL) + */ +static void policy_destroy(struct aa_policy *policy) +{ + /* still contains profiles -- invalid */ + if (on_list_rcu(&policy->profiles)) { + AA_ERROR("%s: internal error, " + "policy '%s' still contains profiles\n", + __func__, policy->name); + BUG(); + } + if (on_list_rcu(&policy->list)) { + AA_ERROR("%s: internal error, policy '%s' still on list\n", + __func__, policy->name); + BUG(); + } + + /* don't free name as its a subset of hname */ + kzfree(policy->hname); +} + +/** + * __policy_find - find a policy by @name on a policy list + * @head: list to search (NOT NULL) + * @name: name to search for (NOT NULL) + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted policy that match @name or NULL if not found + */ +static struct aa_policy *__policy_find(struct list_head *head, const char *name) +{ + struct aa_policy *policy; + + list_for_each_entry_rcu(policy, head, list) { + if (!strcmp(policy->name, name)) + return policy; + } + return NULL; +} + +/** + * __policy_strn_find - find a policy that's name matches @len chars of @str + * @head: list to search (NOT NULL) + * @str: string to search for (NOT NULL) + * @len: length of match required + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted policy that match @str or NULL if not found + * + * if @len == strlen(@strlen) then this is equiv to __policy_find + * other wise it allows searching for policy by a partial match of name + */ +static struct aa_policy *__policy_strn_find(struct list_head *head, + const char *str, int len) +{ + struct aa_policy *policy; + + list_for_each_entry_rcu(policy, head, list) { + if (aa_strneq(policy->name, str, len)) + return policy; + } + + return NULL; +} + +/* + * Routines for AppArmor namespaces + */ + +static const char *hidden_ns_name = "---"; +/** + * aa_ns_visible - test if @view is visible from @curr + * @curr: namespace to treat as the parent (NOT NULL) + * @view: namespace to test if visible from @curr (NOT NULL) + * + * Returns: true if @view is visible from @curr else false + */ +bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view) +{ + if (curr == view) + return true; + + for ( ; view; view = view->parent) { + if (view->parent == curr) + return true; + } + return false; +} + +/** + * aa_na_name - Find the ns name to display for @view from @curr + * @curr - current namespace (NOT NULL) + * @view - namespace attempting to view (NOT NULL) + * + * Returns: name of @view visible from @curr + */ +const char *aa_ns_name(struct aa_namespace *curr, struct aa_namespace *view) +{ + /* if view == curr then the namespace name isn't displayed */ + if (curr == view) + return ""; + + if (aa_ns_visible(curr, view)) { + /* at this point if a ns is visible it is in a view ns + * thus the curr ns.hname is a prefix of its name. + * Only output the virtualized portion of the name + * Add + 2 to skip over // separating curr hname prefix + * from the visible tail of the views hname + */ + return view->base.hname + strlen(curr->base.hname) + 2; + } else + return hidden_ns_name; +} + +/** + * alloc_namespace - allocate, initialize and return a new namespace + * @prefix: parent namespace name (MAYBE NULL) + * @name: a preallocated name (NOT NULL) + * + * Returns: refcounted namespace or NULL on failure. + */ +static struct aa_namespace *alloc_namespace(const char *prefix, + const char *name) +{ + struct aa_namespace *ns; + + ns = kzalloc(sizeof(*ns), GFP_KERNEL); + AA_DEBUG("%s(%p)\n", __func__, ns); + if (!ns) + return NULL; + if (!policy_init(&ns->base, prefix, name)) + goto fail_ns; + + INIT_LIST_HEAD(&ns->sub_ns); + mutex_init(&ns->lock); + + /* released by free_namespace */ + ns->unconfined = aa_alloc_profile("unconfined"); + if (!ns->unconfined) + goto fail_unconfined; + + ns->unconfined->flags = PFLAG_IX_ON_NAME_ERROR | + PFLAG_IMMUTABLE | PFLAG_NS_COUNT; + ns->unconfined->mode = APPARMOR_UNCONFINED; + + /* ns and ns->unconfined share ns->unconfined refcount */ + ns->unconfined->ns = ns; + + atomic_set(&ns->uniq_null, 0); + + return ns; + +fail_unconfined: + kzfree(ns->base.hname); +fail_ns: + kzfree(ns); + return NULL; +} + +/** + * free_namespace - free a profile namespace + * @ns: the namespace to free (MAYBE NULL) + * + * Requires: All references to the namespace must have been put, if the + * namespace was referenced by a profile confining a task, + */ +static void free_namespace(struct aa_namespace *ns) +{ + if (!ns) + return; + + policy_destroy(&ns->base); + aa_put_namespace(ns->parent); + + ns->unconfined->ns = NULL; + aa_free_profile(ns->unconfined); + kzfree(ns); +} + +/** + * __aa_find_namespace - find a namespace on a list by @name + * @head: list to search for namespace on (NOT NULL) + * @name: name of namespace to look for (NOT NULL) + * + * Returns: unrefcounted namespace + * + * Requires: rcu_read_lock be held + */ +static struct aa_namespace *__aa_find_namespace(struct list_head *head, + const char *name) +{ + return (struct aa_namespace *)__policy_find(head, name); +} + +/** + * aa_find_namespace - look up a profile namespace on the namespace list + * @root: namespace to search in (NOT NULL) + * @name: name of namespace to find (NOT NULL) + * + * Returns: a refcounted namespace on the list, or NULL if no namespace + * called @name exists. + * + * refcount released by caller + */ +struct aa_namespace *aa_find_namespace(struct aa_namespace *root, + const char *name) +{ + struct aa_namespace *ns = NULL; + + rcu_read_lock(); + ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); + rcu_read_unlock(); + + return ns; +} + +/** + * aa_prepare_namespace - find an existing or create a new namespace of @name + * @name: the namespace to find or add (MAYBE NULL) + * + * Returns: refcounted namespace or NULL if failed to create one + */ +static struct aa_namespace *aa_prepare_namespace(const char *name) +{ + struct aa_namespace *ns, *root; + + root = aa_current_profile()->ns; + + mutex_lock(&root->lock); + + /* if name isn't specified the profile is loaded to the current ns */ + if (!name) { + /* released by caller */ + ns = aa_get_namespace(root); + goto out; + } + + /* try and find the specified ns and if it doesn't exist create it */ + /* released by caller */ + ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); + if (!ns) { + ns = alloc_namespace(root->base.hname, name); + if (!ns) + goto out; + if (__aa_fs_namespace_mkdir(ns, ns_subns_dir(root), name)) { + AA_ERROR("Failed to create interface for ns %s\n", + ns->base.name); + free_namespace(ns); + ns = NULL; + goto out; + } + ns->parent = aa_get_namespace(root); + list_add_rcu(&ns->base.list, &root->sub_ns); + /* add list ref */ + aa_get_namespace(ns); + } +out: + mutex_unlock(&root->lock); + + /* return ref */ + return ns; +} + +/** + * __list_add_profile - add a profile to a list + * @list: list to add it to (NOT NULL) + * @profile: the profile to add (NOT NULL) + * + * refcount @profile, should be put by __list_remove_profile + * + * Requires: namespace lock be held, or list not be shared + */ +static void __list_add_profile(struct list_head *list, + struct aa_profile *profile) +{ + list_add_rcu(&profile->base.list, list); + /* get list reference */ + aa_get_profile(profile); +} + +/** + * __list_remove_profile - remove a profile from the list it is on + * @profile: the profile to remove (NOT NULL) + * + * remove a profile from the list, warning generally removal should + * be done with __replace_profile as most profile removals are + * replacements to the unconfined profile. + * + * put @profile list refcount + * + * Requires: namespace lock be held, or list not have been live + */ +static void __list_remove_profile(struct aa_profile *profile) +{ + list_del_rcu(&profile->base.list); + aa_put_profile(profile); +} + +static void __profile_list_release(struct list_head *head); + +/** + * __remove_profile - remove old profile, and children + * @profile: profile to be replaced (NOT NULL) + * + * Requires: namespace list lock be held, or list not be shared + */ +static void __remove_profile(struct aa_profile *profile) +{ + /* release any children lists first */ + __profile_list_release(&profile->base.profiles); + /* released by free_profile */ + __aa_update_replacedby(profile, profile->ns->unconfined); + __aa_fs_profile_rmdir(profile); + __list_remove_profile(profile); +} + +/** + * __profile_list_release - remove all profiles on the list and put refs + * @head: list of profiles (NOT NULL) + * + * Requires: namespace lock be held + */ +static void __profile_list_release(struct list_head *head) +{ + struct aa_profile *profile, *tmp; + list_for_each_entry_safe(profile, tmp, head, base.list) + __remove_profile(profile); +} + +static void __ns_list_release(struct list_head *head); + +/** + * destroy_namespace - remove everything contained by @ns + * @ns: namespace to have it contents removed (NOT NULL) + */ +static void destroy_namespace(struct aa_namespace *ns) +{ + if (!ns) + return; + + mutex_lock(&ns->lock); + /* release all profiles in this namespace */ + __profile_list_release(&ns->base.profiles); + + /* release all sub namespaces */ + __ns_list_release(&ns->sub_ns); + + if (ns->parent) + __aa_update_replacedby(ns->unconfined, ns->parent->unconfined); + __aa_fs_namespace_rmdir(ns); + mutex_unlock(&ns->lock); +} + +/** + * __remove_namespace - remove a namespace and all its children + * @ns: namespace to be removed (NOT NULL) + * + * Requires: ns->parent->lock be held and ns removed from parent. + */ +static void __remove_namespace(struct aa_namespace *ns) +{ + /* remove ns from namespace list */ + list_del_rcu(&ns->base.list); + destroy_namespace(ns); + aa_put_namespace(ns); +} + +/** + * __ns_list_release - remove all profile namespaces on the list put refs + * @head: list of profile namespaces (NOT NULL) + * + * Requires: namespace lock be held + */ +static void __ns_list_release(struct list_head *head) +{ + struct aa_namespace *ns, *tmp; + list_for_each_entry_safe(ns, tmp, head, base.list) + __remove_namespace(ns); + +} + +/** + * aa_alloc_root_ns - allocate the root profile namespace + * + * Returns: %0 on success else error + * + */ +int __init aa_alloc_root_ns(void) +{ + /* released by aa_free_root_ns - used as list ref*/ + root_ns = alloc_namespace(NULL, "root"); + if (!root_ns) + return -ENOMEM; + + return 0; +} + + /** + * aa_free_root_ns - free the root profile namespace + */ +void __init aa_free_root_ns(void) + { + struct aa_namespace *ns = root_ns; + root_ns = NULL; + + destroy_namespace(ns); + aa_put_namespace(ns); +} + + +static void free_replacedby(struct aa_replacedby *r) +{ + if (r) { + /* r->profile will not be updated any more as r is dead */ + aa_put_profile(rcu_dereference_protected(r->profile, true)); + kzfree(r); + } +} + + +void aa_free_replacedby_kref(struct kref *kref) +{ + struct aa_replacedby *r = container_of(kref, struct aa_replacedby, + count); + free_replacedby(r); +} + +/** + * aa_free_profile - free a profile + * @profile: the profile to free (MAYBE NULL) + * + * Free a profile, its hats and null_profile. All references to the profile, + * its hats and null_profile must have been put. + * + * If the profile was referenced from a task context, free_profile() will + * be called from an rcu callback routine, so we must not sleep here. + */ +void aa_free_profile(struct aa_profile *profile) +{ + AA_DEBUG("%s(%p)\n", __func__, profile); + + if (!profile) + return; + + /* free children profiles */ + policy_destroy(&profile->base); + aa_put_profile(rcu_access_pointer(profile->parent)); + + aa_put_namespace(profile->ns); + kzfree(profile->rename); + + aa_free_file_rules(&profile->file); + aa_free_cap_rules(&profile->caps); + aa_free_rlimit_rules(&profile->rlimits); + + kzfree(profile->dirname); + aa_put_dfa(profile->xmatch); + aa_put_dfa(profile->policy.dfa); + aa_put_replacedby(profile->replacedby); + + kzfree(profile->hash); + 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) + free_namespace(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); +} + +/** + * aa_alloc_profile - allocate, initialize and return a new profile + * @hname: name of the profile (NOT NULL) + * + * Returns: refcount profile or NULL on failure + */ +struct aa_profile *aa_alloc_profile(const char *hname) +{ + struct aa_profile *profile; + + /* freed by free_profile - usually through aa_put_profile */ + profile = kzalloc(sizeof(*profile), GFP_KERNEL); + if (!profile) + return NULL; + + profile->replacedby = kzalloc(sizeof(struct aa_replacedby), GFP_KERNEL); + if (!profile->replacedby) + goto fail; + kref_init(&profile->replacedby->count); + + if (!policy_init(&profile->base, NULL, hname)) + goto fail; + kref_init(&profile->count); + + /* refcount released by caller */ + return profile; + +fail: + kzfree(profile->replacedby); + kzfree(profile); + + return NULL; +} + +/** + * aa_new_null_profile - create a new null-X learning profile + * @parent: profile that caused this profile to be created (NOT NULL) + * @hat: true if the null- learning profile is a hat + * + * Create a null- complain mode profile used in learning mode. The name of + * the profile is unique and follows the format of parent//null-<uniq>. + * + * null profiles are added to the profile list but the list does not + * hold a count on them so that they are automatically released when + * not in use. + * + * Returns: new refcounted profile else NULL on failure + */ +struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat) +{ + struct aa_profile *profile = NULL; + char *name; + int uniq = atomic_inc_return(&parent->ns->uniq_null); + + /* freed below */ + name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, GFP_KERNEL); + if (!name) + goto fail; + sprintf(name, "%s//null-%x", parent->base.hname, uniq); + + profile = aa_alloc_profile(name); + kfree(name); + if (!profile) + goto fail; + + profile->mode = APPARMOR_COMPLAIN; + profile->flags = PFLAG_NULL; + if (hat) + profile->flags |= PFLAG_HAT; + + /* released on free_profile */ + rcu_assign_pointer(profile->parent, aa_get_profile(parent)); + profile->ns = aa_get_namespace(parent->ns); + + mutex_lock(&profile->ns->lock); + __list_add_profile(&parent->base.profiles, profile); + mutex_unlock(&profile->ns->lock); + + /* refcount released by caller */ + return profile; + +fail: + return NULL; +} + +/* TODO: profile accounting - setup in remove */ + +/** + * __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) + * + * 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) +{ + return (struct aa_profile *)__policy_find(head, 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 *__strn_find_child(struct list_head *head, + const char *name, int len) +{ + return (struct aa_profile *)__policy_strn_find(head, name, len); +} + +/** + * aa_find_child - find a profile by @name in @parent + * @parent: profile to search (NOT NULL) + * @name: profile name to search for (NOT NULL) + * + * Returns: a refcounted profile or NULL if not found + */ +struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) +{ + struct aa_profile *profile; + + rcu_read_lock(); + profile = aa_get_profile(__find_child(&parent->base.profiles, name)); + rcu_read_unlock(); + + /* refcount released by caller */ + return profile; +} + +/** + * __lookup_parent - lookup the parent of a profile of name @hname + * @ns: namespace to lookup profile in (NOT NULL) + * @hname: hierarchical profile name to find parent of (NOT NULL) + * + * Lookups up the parent of a fully qualified profile name, the profile + * that matches hname does not need to exist, in general this + * is used to load a new profile. + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted policy or NULL if not found + */ +static struct aa_policy *__lookup_parent(struct aa_namespace *ns, + const char *hname) +{ + struct aa_policy *policy; + struct aa_profile *profile = NULL; + char *split; + + policy = &ns->base; + + for (split = strstr(hname, "//"); split;) { + profile = __strn_find_child(&policy->profiles, hname, + split - hname); + if (!profile) + return NULL; + policy = &profile->base; + hname = split + 2; + split = strstr(hname, "//"); + } + if (!profile) + return &ns->base; + return &profile->base; +} + +/** + * __lookup_profile - lookup the profile matching @hname + * @base: base list to start looking up profile name from (NOT NULL) + * @hname: hierarchical profile name (NOT NULL) + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted profile pointer or NULL if not found + * + * Do a relative name lookup, recursing through profile tree. + */ +static struct aa_profile *__lookup_profile(struct aa_policy *base, + const char *hname) +{ + struct aa_profile *profile = NULL; + char *split; + + for (split = strstr(hname, "//"); split;) { + profile = __strn_find_child(&base->profiles, hname, + split - hname); + if (!profile) + return NULL; + + base = &profile->base; + hname = split + 2; + split = strstr(hname, "//"); + } + + profile = __find_child(&base->profiles, hname); + + return profile; +} + +/** + * aa_lookup_profile - find a profile by its full or partial name + * @ns: the namespace to start from (NOT NULL) + * @hname: name to do lookup on. Does not contain namespace prefix (NOT NULL) + * + * Returns: refcounted profile or NULL if not found + */ +struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname) +{ + struct aa_profile *profile; + + rcu_read_lock(); + do { + profile = __lookup_profile(&ns->base, hname); + } while (profile && !aa_get_profile_not0(profile)); + rcu_read_unlock(); + + /* the unconfined profile is not in the regular profile list */ + if (!profile && strcmp(hname, "unconfined") == 0) + profile = aa_get_newest_profile(ns->unconfined); + + /* refcount released by caller */ + return profile; +} + +/** + * replacement_allowed - test to see if replacement is allowed + * @profile: profile to test if it can be replaced (MAYBE NULL) + * @noreplace: true if replacement shouldn't be allowed but addition is okay + * @info: Returns - info about why replacement failed (NOT NULL) + * + * Returns: %0 if replacement allowed else error code + */ +static int replacement_allowed(struct aa_profile *profile, int noreplace, + const char **info) +{ + if (profile) { + if (profile->flags & PFLAG_IMMUTABLE) { + *info = "cannot replace immutible profile"; + return -EPERM; + } else if (noreplace) { + *info = "profile already exists"; + return -EEXIST; + } + } + return 0; +} + +/** + * aa_audit_policy - Do auditing of policy changes + * @op: policy operation being performed + * @gfp: memory allocation flags + * @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(int op, gfp_t gfp, const char *name, const char *info, + int error) +{ + struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = op; + aad.name = name; + aad.info = info; + aad.error = error; + + return aa_audit(AUDIT_APPARMOR_STATUS, __aa_current_profile(), gfp, + &sa, NULL); +} + +/** + * aa_may_manage_policy - can the current task manage policy + * @op: the policy manipulation operation being done + * + * Returns: true if the task is allowed to manipulate policy + */ +bool aa_may_manage_policy(int op) +{ + /* check if loading policy is locked out */ + if (aa_g_lock_policy) { + audit_policy(op, GFP_KERNEL, NULL, "policy_locked", -EACCES); + return 0; + } + + if (!capable(CAP_MAC_ADMIN)) { + audit_policy(op, GFP_KERNEL, NULL, "not policy admin", -EACCES); + return 0; + } + + return 1; +} + +static struct aa_profile *__list_lookup_parent(struct list_head *lh, + struct aa_profile *profile) +{ + const char *base = hname_tail(profile->base.hname); + long len = base - profile->base.hname; + struct aa_load_ent *ent; + + /* parent won't have trailing // so remove from len */ + if (len <= 2) + return NULL; + len -= 2; + + list_for_each_entry(ent, lh, list) { + if (ent->new == profile) + continue; + if (strncmp(ent->new->base.hname, profile->base.hname, len) == + 0 && ent->new->base.hname[len] == 0) + return ent->new; + } + + return NULL; +} + +/** + * __replace_profile - replace @old with @new on a list + * @old: profile to be replaced (NOT NULL) + * @new: profile to replace @old with (NOT NULL) + * @share_replacedby: transfer @old->replacedby to @new + * + * Will duplicate and refcount elements that @new inherits from @old + * and will inherit @old children. + * + * refcount @new for list, put @old list refcount + * + * 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_replacedby) +{ + struct aa_profile *child, *tmp; + + if (!list_empty(&old->base.profiles)) { + LIST_HEAD(lh); + list_splice_init_rcu(&old->base.profiles, &lh, synchronize_rcu); + + list_for_each_entry_safe(child, tmp, &lh, base.list) { + struct aa_profile *p; + + list_del_init(&child->base.list); + p = __find_child(&new->base.profiles, child->base.name); + if (p) { + /* @p replaces @child */ + __replace_profile(child, p, share_replacedby); + continue; + } + + /* inherit @child and its children */ + /* TODO: update hname of inherited children */ + /* list refcount transferred to @new */ + p = aa_deref_parent(child); + rcu_assign_pointer(child->parent, aa_get_profile(new)); + list_add_rcu(&child->base.list, &new->base.profiles); + aa_put_profile(p); + } + } + + if (!rcu_access_pointer(new->parent)) { + struct aa_profile *parent = aa_deref_parent(old); + rcu_assign_pointer(new->parent, aa_get_profile(parent)); + } + __aa_update_replacedby(old, new); + if (share_replacedby) { + aa_put_replacedby(new->replacedby); + new->replacedby = aa_get_replacedby(old->replacedby); + } else if (!rcu_access_pointer(new->replacedby->profile)) + /* aafs interface uses replacedby */ + rcu_assign_pointer(new->replacedby->profile, + aa_get_profile(new)); + __aa_fs_profile_migrate_dents(old, new); + + if (list_empty(&new->base.list)) { + /* new is not on a list already */ + list_replace_rcu(&old->base.list, &new->base.list); + aa_get_profile(new); + aa_put_profile(old); + } else + __list_remove_profile(old); +} + +/** + * __lookup_replace - lookup replacement information for a profile + * @ns - namespace the lookup occurs in + * @hname - name of profile to lookup + * @noreplace - true if not replacing an existing profile + * @p - Returns: profile to be replaced + * @info - Returns: info string on why lookup failed + * + * Returns: profile to replace (no ref) on success else ptr error + */ +static int __lookup_replace(struct aa_namespace *ns, const char *hname, + bool noreplace, struct aa_profile **p, + const char **info) +{ + *p = aa_get_profile(__lookup_profile(&ns->base, hname)); + if (*p) { + int error = replacement_allowed(*p, noreplace, info); + if (error) { + *info = "profile can not be replaced"; + return error; + } + } + + return 0; +} + +/** + * aa_replace_profiles - replace profile(s) on the profile list + * @udata: serialized data stream (NOT NULL) + * @size: size of the serialized data stream + * @noreplace: true if only doing addition, no replacement allowed + * + * unpack and replace a profile on the profile list and uses of that profile + * by any aa_task_cxt. If the profile does not exist on the profile list + * it is added. + * + * Returns: size of data consumed else error code on failure. + */ +ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) +{ + const char *ns_name, *name = NULL, *info = NULL; + struct aa_namespace *ns = NULL; + struct aa_load_ent *ent, *tmp; + int op = OP_PROF_REPL; + ssize_t error; + LIST_HEAD(lh); + + /* released below */ + error = aa_unpack(udata, size, &lh, &ns_name); + if (error) + goto out; + + /* released below */ + ns = aa_prepare_namespace(ns_name); + if (!ns) { + info = "failed to prepare namespace"; + error = -ENOMEM; + name = ns_name; + goto fail; + } + + mutex_lock(&ns->lock); + /* setup parent and ns info */ + list_for_each_entry(ent, &lh, list) { + struct aa_policy *policy; + + name = ent->new->base.hname; + error = __lookup_replace(ns, ent->new->base.hname, noreplace, + &ent->old, &info); + if (error) + goto fail_lock; + + if (ent->new->rename) { + error = __lookup_replace(ns, ent->new->rename, + noreplace, &ent->rename, + &info); + if (error) + goto fail_lock; + } + + /* released when @new is freed */ + ent->new->ns = aa_get_namespace(ns); + + if (ent->old || ent->rename) + continue; + + /* no ref on policy only use inside lock */ + policy = __lookup_parent(ns, ent->new->base.hname); + if (!policy) { + struct aa_profile *p; + p = __list_lookup_parent(&lh, ent->new); + if (!p) { + error = -ENOENT; + info = "parent does not exist"; + name = ent->new->base.hname; + goto fail_lock; + } + rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); + } else if (policy != &ns->base) { + /* released on profile replacement or free_profile */ + struct aa_profile *p = (struct aa_profile *) policy; + rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); + } + } + + /* create new fs entries for introspection if needed */ + 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 { + struct dentry *parent; + if (rcu_access_pointer(ent->new->parent)) { + struct aa_profile *p; + p = aa_deref_parent(ent->new); + parent = prof_child_dir(p); + } else + parent = ns_subprofs_dir(ent->new->ns); + error = __aa_fs_profile_mkdir(ent->new, parent); + } + + if (error) { + info = "failed to create "; + goto fail_lock; + } + } + + /* Done with checks that may fail - do actual replacement */ + 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(op, GFP_ATOMIC, ent->new->base.name, NULL, error); + + if (ent->old) { + __replace_profile(ent->old, ent->new, 1); + if (ent->rename) { + /* aafs interface uses replacedby */ + struct aa_replacedby *r = ent->new->replacedby; + rcu_assign_pointer(r->profile, + aa_get_profile(ent->new)); + __replace_profile(ent->rename, ent->new, 0); + } + } else if (ent->rename) { + /* aafs interface uses replacedby */ + rcu_assign_pointer(ent->new->replacedby->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); + aa_put_profile(parent); + rcu_assign_pointer(ent->new->parent, newest); + } else + aa_put_profile(newest); + /* aafs interface uses replacedby */ + rcu_assign_pointer(ent->new->replacedby->profile, + aa_get_profile(ent->new)); + __list_add_profile(&parent->base.profiles, ent->new); + } else { + /* aafs interface uses replacedby */ + rcu_assign_pointer(ent->new->replacedby->profile, + aa_get_profile(ent->new)); + __list_add_profile(&ns->base.profiles, ent->new); + } + aa_load_ent_free(ent); + } + mutex_unlock(&ns->lock); + +out: + aa_put_namespace(ns); + + if (error) + return error; + return size; + +fail_lock: + mutex_unlock(&ns->lock); +fail: + error = audit_policy(op, GFP_KERNEL, name, info, error); + + list_for_each_entry_safe(ent, tmp, &lh, list) { + list_del_init(&ent->list); + aa_load_ent_free(ent); + } + + goto out; +} + +/** + * aa_remove_profiles - remove profile(s) from the system + * @fqname: name of the profile or namespace to remove (NOT NULL) + * @size: size of the name + * + * Remove a profile or sub namespace from the current namespace, so that + * they can not be found anymore and mark them as replaced by unconfined + * + * NOTE: removing confinement does not restore rlimits to preconfinemnet values + * + * Returns: size of data consume else error code if fails + */ +ssize_t aa_remove_profiles(char *fqname, size_t size) +{ + struct aa_namespace *root, *ns = NULL; + struct aa_profile *profile = NULL; + const char *name = fqname, *info = NULL; + ssize_t error = 0; + + if (*fqname == 0) { + info = "no profile specified"; + error = -ENOENT; + goto fail; + } + + root = aa_current_profile()->ns; + + if (fqname[0] == ':') { + char *ns_name; + name = aa_split_fqname(fqname, &ns_name); + /* released below */ + ns = aa_find_namespace(root, ns_name); + if (!ns) { + info = "namespace does not exist"; + error = -ENOENT; + goto fail; + } + } else + /* released below */ + ns = aa_get_namespace(root); + + if (!name) { + /* remove namespace - can only happen if fqname[0] == ':' */ + mutex_lock(&ns->parent->lock); + __remove_namespace(ns); + mutex_unlock(&ns->parent->lock); + } else { + /* remove profile */ + mutex_lock(&ns->lock); + profile = aa_get_profile(__lookup_profile(&ns->base, name)); + if (!profile) { + error = -ENOENT; + info = "profile does not exist"; + goto fail_ns_lock; + } + name = profile->base.hname; + __remove_profile(profile); + mutex_unlock(&ns->lock); + } + + /* don't fail removal if audit fails */ + (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); + aa_put_namespace(ns); + aa_put_profile(profile); + return size; + +fail_ns_lock: + mutex_unlock(&ns->lock); + aa_put_namespace(ns); + +fail: + (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); + return error; +} diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c new file mode 100644 index 000000000..a689f1093 --- /dev/null +++ b/security/apparmor/policy_unpack.c @@ -0,0 +1,805 @@ +/* + * AppArmor security module + * + * This file contains AppArmor functions for unpacking policy loaded from + * userspace. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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. + * + * AppArmor uses a serialized binary format for loading policy. To find + * policy format documentation look in Documentation/security/apparmor.txt + * All policy is validated before it is used. + */ + +#include <asm/unaligned.h> +#include <linux/ctype.h> +#include <linux/errno.h> + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/crypto.h" +#include "include/match.h" +#include "include/policy.h" +#include "include/policy_unpack.h" + +/* + * The AppArmor interface treats data as a type byte followed by the + * actual data. The interface has the notion of a a named entry + * which has a name (AA_NAME typecode followed by name string) followed by + * the entries typecode and data. Named types allow for optional + * elements and extensions to be added and tested for without breaking + * backwards compatibility. + */ + +enum aa_code { + AA_U8, + AA_U16, + AA_U32, + AA_U64, + AA_NAME, /* same as string except it is items name */ + AA_STRING, + AA_BLOB, + AA_STRUCT, + AA_STRUCTEND, + AA_LIST, + AA_LISTEND, + AA_ARRAY, + AA_ARRAYEND, +}; + +/* + * aa_ext is the read of the buffer containing the serialized profile. The + * data is copied into a kernel buffer in apparmorfs and then handed off to + * the unpack routines. + */ +struct aa_ext { + void *start; + void *end; + void *pos; /* pointer to current position in the buffer */ + u32 version; +}; + +/* audit callback for unpack fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + if (sa->aad->iface.target) { + struct aa_profile *name = sa->aad->iface.target; + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, name->base.hname); + } + if (sa->aad->iface.pos) + audit_log_format(ab, " offset=%ld", sa->aad->iface.pos); +} + +/** + * audit_iface - do audit message for policy unpacking/load/replace/remove + * @new: profile if it has been allocated (MAYBE NULL) + * @name: name of the profile being manipulated (MAYBE NULL) + * @info: any extra info about the failure (MAYBE NULL) + * @e: buffer position info + * @error: error code + * + * Returns: %0 or error + */ +static int audit_iface(struct aa_profile *new, const char *name, + const char *info, struct aa_ext *e, int error) +{ + struct aa_profile *profile = __aa_current_profile(); + struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + if (e) + aad.iface.pos = e->pos - e->start; + aad.iface.target = new; + aad.name = name; + aad.info = info; + aad.error = error; + + return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa, + audit_cb); +} + +/* test if read will be in packed data bounds */ +static bool inbounds(struct aa_ext *e, size_t size) +{ + return (size <= e->end - e->pos); +} + +/** + * aa_u16_chunck - test and do bounds checking for a u16 size based chunk + * @e: serialized data read head (NOT NULL) + * @chunk: start address for chunk of data (NOT NULL) + * + * Returns: the size of chunk found with the read head at the end of the chunk. + */ +static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk) +{ + size_t size = 0; + + if (!inbounds(e, sizeof(u16))) + return 0; + size = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + if (!inbounds(e, size)) + return 0; + *chunk = e->pos; + e->pos += size; + return size; +} + +/* unpack control byte */ +static bool unpack_X(struct aa_ext *e, enum aa_code code) +{ + if (!inbounds(e, 1)) + return 0; + if (*(u8 *) e->pos != code) + return 0; + e->pos++; + return 1; +} + +/** + * unpack_nameX - check is the next element is of type X with a name of @name + * @e: serialized data extent information (NOT NULL) + * @code: type code + * @name: name to match to the serialized element. (MAYBE NULL) + * + * check that the next serialized data element is of type X and has a tag + * name @name. If @name is specified then there must be a matching + * name element in the stream. If @name is NULL any name element will be + * skipped and only the typecode will be tested. + * + * Returns 1 on success (both type code and name tests match) and the read + * head is advanced past the headers + * + * Returns: 0 if either match fails, the read head does not move + */ +static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) +{ + /* + * May need to reset pos if name or type doesn't match + */ + void *pos = e->pos; + /* + * Check for presence of a tagname, and if present name size + * AA_NAME tag value is a u16. + */ + if (unpack_X(e, AA_NAME)) { + char *tag = NULL; + size_t size = unpack_u16_chunk(e, &tag); + /* if a name is specified it must match. otherwise skip tag */ + if (name && (!size || strcmp(name, tag))) + goto fail; + } else if (name) { + /* if a name is specified and there is no name tag fail */ + goto fail; + } + + /* now check if type code matches */ + if (unpack_X(e, code)) + return 1; + +fail: + e->pos = pos; + return 0; +} + +static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) +{ + if (unpack_nameX(e, AA_U32, name)) { + if (!inbounds(e, sizeof(u32))) + return 0; + if (data) + *data = le32_to_cpu(get_unaligned((u32 *) e->pos)); + e->pos += sizeof(u32); + return 1; + } + return 0; +} + +static bool unpack_u64(struct aa_ext *e, u64 *data, const char *name) +{ + if (unpack_nameX(e, AA_U64, name)) { + if (!inbounds(e, sizeof(u64))) + return 0; + if (data) + *data = le64_to_cpu(get_unaligned((u64 *) e->pos)); + e->pos += sizeof(u64); + return 1; + } + return 0; +} + +static size_t unpack_array(struct aa_ext *e, const char *name) +{ + if (unpack_nameX(e, AA_ARRAY, name)) { + int size; + if (!inbounds(e, sizeof(u16))) + return 0; + size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return size; + } + return 0; +} + +static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name) +{ + if (unpack_nameX(e, AA_BLOB, name)) { + u32 size; + if (!inbounds(e, sizeof(u32))) + return 0; + size = le32_to_cpu(get_unaligned((u32 *) e->pos)); + e->pos += sizeof(u32); + if (inbounds(e, (size_t) size)) { + *blob = e->pos; + e->pos += size; + return size; + } + } + return 0; +} + +static int unpack_str(struct aa_ext *e, const char **string, const char *name) +{ + char *src_str; + size_t size = 0; + void *pos = e->pos; + *string = NULL; + if (unpack_nameX(e, AA_STRING, name)) { + size = unpack_u16_chunk(e, &src_str); + if (size) { + /* strings are null terminated, length is size - 1 */ + if (src_str[size - 1] != 0) + goto fail; + *string = src_str; + } + } + return size; + +fail: + e->pos = pos; + return 0; +} + +static int unpack_strdup(struct aa_ext *e, char **string, const char *name) +{ + const char *tmp; + void *pos = e->pos; + int res = unpack_str(e, &tmp, name); + *string = NULL; + + if (!res) + return 0; + + *string = kmemdup(tmp, res, GFP_KERNEL); + if (!*string) { + e->pos = pos; + return 0; + } + + return res; +} + +#define DFA_VALID_PERM_MASK 0xffffffff +#define DFA_VALID_PERM2_MASK 0xffffffff + +/** + * verify_accept - verify the accept tables of a dfa + * @dfa: dfa to verify accept tables of (NOT NULL) + * @flags: flags governing dfa + * + * Returns: 1 if valid accept tables else 0 if error + */ +static bool verify_accept(struct aa_dfa *dfa, int flags) +{ + int i; + + /* verify accept permissions */ + for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { + int mode = ACCEPT_TABLE(dfa)[i]; + + if (mode & ~DFA_VALID_PERM_MASK) + return 0; + + if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK) + return 0; + } + return 1; +} + +/** + * unpack_dfa - unpack a file rule dfa + * @e: serialized data extent information (NOT NULL) + * + * returns dfa or ERR_PTR or NULL if no dfa + */ +static struct aa_dfa *unpack_dfa(struct aa_ext *e) +{ + char *blob = NULL; + size_t size; + struct aa_dfa *dfa = NULL; + + size = unpack_blob(e, &blob, "aadfa"); + if (size) { + /* + * The dfa is aligned with in the blob to 8 bytes + * from the beginning of the stream. + * alignment adjust needed by dfa unpack + */ + size_t sz = blob - (char *) e->start - + ((e->pos - e->start) & 7); + size_t pad = ALIGN(sz, 8) - sz; + int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | + TO_ACCEPT2_FLAG(YYTD_DATA32); + + + if (aa_g_paranoid_load) + flags |= DFA_FLAG_VERIFY_STATES; + + dfa = aa_dfa_unpack(blob + pad, size - pad, flags); + + if (IS_ERR(dfa)) + return dfa; + + if (!verify_accept(dfa, flags)) + goto fail; + } + + return dfa; + +fail: + aa_put_dfa(dfa); + return ERR_PTR(-EPROTO); +} + +/** + * unpack_trans_table - unpack a profile transition table + * @e: serialized data extent information (NOT NULL) + * @profile: profile to add the accept table to (NOT NULL) + * + * Returns: 1 if table successfully unpacked + */ +static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) +{ + void *pos = e->pos; + + /* exec table is optional */ + if (unpack_nameX(e, AA_STRUCT, "xtable")) { + int i, size; + + size = unpack_array(e, NULL); + /* currently 4 exec bits and entries 0-3 are reserved iupcx */ + if (size > 16 - 4) + goto fail; + profile->file.trans.table = kzalloc(sizeof(char *) * size, + GFP_KERNEL); + if (!profile->file.trans.table) + goto fail; + + profile->file.trans.size = size; + for (i = 0; i < size; i++) { + char *str; + int c, j, size2 = unpack_strdup(e, &str, NULL); + /* unpack_strdup verifies that the last character is + * null termination byte. + */ + if (!size2) + goto fail; + profile->file.trans.table[i] = str; + /* verify that name doesn't start with space */ + if (isspace(*str)) + goto fail; + + /* count internal # of internal \0 */ + for (c = j = 0; j < size2 - 2; j++) { + if (!str[j]) + c++; + } + if (*str == ':') { + /* beginning with : requires an embedded \0, + * verify that exactly 1 internal \0 exists + * trailing \0 already verified by unpack_strdup + */ + if (c != 1) + goto fail; + /* first character after : must be valid */ + if (!str[1]) + goto fail; + } else if (c) + /* fail - all other cases with embedded \0 */ + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + return 1; + +fail: + aa_free_domain_entries(&profile->file.trans); + e->pos = pos; + return 0; +} + +static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) +{ + void *pos = e->pos; + + /* rlimits are optional */ + if (unpack_nameX(e, AA_STRUCT, "rlimits")) { + int i, size; + u32 tmp = 0; + if (!unpack_u32(e, &tmp, NULL)) + goto fail; + profile->rlimits.mask = tmp; + + size = unpack_array(e, NULL); + if (size > RLIM_NLIMITS) + goto fail; + for (i = 0; i < size; i++) { + u64 tmp2 = 0; + int a = aa_map_resource(i); + if (!unpack_u64(e, &tmp2, NULL)) + goto fail; + profile->rlimits.limits[a].rlim_max = tmp2; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + return 1; + +fail: + e->pos = pos; + return 0; +} + +/** + * unpack_profile - unpack a serialized profile + * @e: serialized data extent information (NOT NULL) + * + * NOTE: unpack profile sets audit struct if there is a failure + */ +static struct aa_profile *unpack_profile(struct aa_ext *e) +{ + struct aa_profile *profile = NULL; + const char *name = NULL; + int i, error = -EPROTO; + kernel_cap_t tmpcap; + u32 tmp; + + /* check that we have the right struct being passed */ + if (!unpack_nameX(e, AA_STRUCT, "profile")) + goto fail; + if (!unpack_str(e, &name, NULL)) + goto fail; + + profile = aa_alloc_profile(name); + if (!profile) + return ERR_PTR(-ENOMEM); + + /* profile renaming is optional */ + (void) unpack_str(e, &profile->rename, "rename"); + + /* attachment string is optional */ + (void) unpack_str(e, &profile->attach, "attach"); + + /* xmatch is optional and may be NULL */ + profile->xmatch = unpack_dfa(e); + if (IS_ERR(profile->xmatch)) { + error = PTR_ERR(profile->xmatch); + profile->xmatch = NULL; + goto fail; + } + /* xmatch_len is not optional if xmatch is set */ + if (profile->xmatch) { + if (!unpack_u32(e, &tmp, NULL)) + goto fail; + profile->xmatch_len = tmp; + } + + /* 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; + if (!unpack_u32(e, &tmp, NULL)) + goto fail; + if (tmp == PACKED_MODE_COMPLAIN) + profile->mode = APPARMOR_COMPLAIN; + else if (tmp == PACKED_MODE_KILL) + profile->mode = APPARMOR_KILL; + else if (tmp == PACKED_MODE_UNCONFINED) + profile->mode = APPARMOR_UNCONFINED; + if (!unpack_u32(e, &tmp, NULL)) + goto fail; + if (tmp) + profile->audit = AUDIT_ALL; + + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + + /* path_flags is optional */ + if (unpack_u32(e, &profile->path_flags, "path_flags")) + profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED; + else + /* set a default value if path_flags field is not present */ + profile->path_flags = PFLAG_MEDIATE_DELETED; + + if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) + goto fail; + if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL)) + goto fail; + if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL)) + goto fail; + if (!unpack_u32(e, &tmpcap.cap[0], NULL)) + goto fail; + + if (unpack_nameX(e, AA_STRUCT, "caps64")) { + /* optional upper half of 64 bit caps */ + if (!unpack_u32(e, &(profile->caps.allow.cap[1]), NULL)) + goto fail; + if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL)) + goto fail; + if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL)) + goto fail; + if (!unpack_u32(e, &(tmpcap.cap[1]), NULL)) + goto fail; + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + + if (unpack_nameX(e, AA_STRUCT, "capsx")) { + /* optional extended caps mediation mask */ + if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL)) + goto fail; + if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL)) + goto fail; + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + + if (!unpack_rlimits(e, profile)) + goto fail; + + if (unpack_nameX(e, AA_STRUCT, "policydb")) { + /* generic policy dfa - optional and may be NULL */ + profile->policy.dfa = unpack_dfa(e); + if (IS_ERR(profile->policy.dfa)) { + error = PTR_ERR(profile->policy.dfa); + profile->policy.dfa = NULL; + goto fail; + } + if (!unpack_u32(e, &profile->policy.start[0], "start")) + /* default start state */ + profile->policy.start[0] = DFA_START; + /* setup class index */ + for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) { + profile->policy.start[i] = + aa_dfa_next(profile->policy.dfa, + profile->policy.start[0], + i); + } + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + + /* get file rules */ + profile->file.dfa = unpack_dfa(e); + if (IS_ERR(profile->file.dfa)) { + error = PTR_ERR(profile->file.dfa); + profile->file.dfa = NULL; + goto fail; + } + + if (!unpack_u32(e, &profile->file.start, "dfa_start")) + /* default start state */ + profile->file.start = DFA_START; + + if (!unpack_trans_table(e, profile)) + goto fail; + + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + + return profile; + +fail: + if (profile) + name = NULL; + else if (!name) + name = "unknown"; + audit_iface(profile, name, "failed to unpack profile", e, error); + aa_free_profile(profile); + + return ERR_PTR(error); +} + +/** + * verify_head - unpack serialized stream header + * @e: serialized data read head (NOT NULL) + * @required: whether the header is required or optional + * @ns: Returns - namespace if one is specified else NULL (NOT NULL) + * + * Returns: error or 0 if header is good + */ +static int verify_header(struct aa_ext *e, int required, const char **ns) +{ + int error = -EPROTONOSUPPORT; + const char *name = NULL; + *ns = NULL; + + /* get the interface version */ + if (!unpack_u32(e, &e->version, "version")) { + if (required) { + audit_iface(NULL, NULL, "invalid profile format", e, + error); + return error; + } + + /* check that the interface version is currently supported */ + if (e->version != 5) { + audit_iface(NULL, NULL, "unsupported interface version", + e, error); + return error; + } + } + + + /* read the namespace if present */ + if (unpack_str(e, &name, "namespace")) { + if (*ns && strcmp(*ns, name)) + audit_iface(NULL, NULL, "invalid ns change", e, error); + else if (!*ns) + *ns = name; + } + + return 0; +} + +static bool verify_xindex(int xindex, int table_size) +{ + int index, xtype; + xtype = xindex & AA_X_TYPE_MASK; + index = xindex & AA_X_INDEX_MASK; + if (xtype == AA_X_TABLE && index > table_size) + return 0; + return 1; +} + +/* verify dfa xindexes are in range of transition tables */ +static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) +{ + int i; + for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { + if (!verify_xindex(dfa_user_xindex(dfa, i), table_size)) + return 0; + if (!verify_xindex(dfa_other_xindex(dfa, i), table_size)) + return 0; + } + return 1; +} + +/** + * verify_profile - Do post unpack analysis to verify profile consistency + * @profile: profile to verify (NOT NULL) + * + * Returns: 0 if passes verification else error + */ +static int verify_profile(struct aa_profile *profile) +{ + if (aa_g_paranoid_load) { + if (profile->file.dfa && + !verify_dfa_xindex(profile->file.dfa, + profile->file.trans.size)) { + audit_iface(profile, NULL, "Invalid named transition", + NULL, -EPROTO); + return -EPROTO; + } + } + + return 0; +} + +void aa_load_ent_free(struct aa_load_ent *ent) +{ + if (ent) { + aa_put_profile(ent->rename); + aa_put_profile(ent->old); + aa_put_profile(ent->new); + kzfree(ent); + } +} + +struct aa_load_ent *aa_load_ent_alloc(void) +{ + struct aa_load_ent *ent = kzalloc(sizeof(*ent), GFP_KERNEL); + if (ent) + INIT_LIST_HEAD(&ent->list); + return ent; +} + +/** + * aa_unpack - unpack packed binary profile(s) data loaded from user space + * @udata: user data copied to kmem (NOT NULL) + * @size: the size of the user data + * @lh: list to place unpacked profiles in a aa_repl_ws + * @ns: Returns namespace profile is in if specified else NULL (NOT NULL) + * + * Unpack user data and return refcounted allocated profile(s) stored in + * @lh in order of discovery, with the list chain stored in base.list + * or error + * + * Returns: profile(s) on @lh else error pointer if fails to unpack + */ +int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) +{ + struct aa_load_ent *tmp, *ent; + struct aa_profile *profile = NULL; + int error; + struct aa_ext e = { + .start = udata, + .end = udata + size, + .pos = udata, + }; + + *ns = NULL; + while (e.pos < e.end) { + void *start; + error = verify_header(&e, e.pos == e.start, ns); + if (error) + goto fail; + + start = e.pos; + profile = unpack_profile(&e); + if (IS_ERR(profile)) { + error = PTR_ERR(profile); + goto fail; + } + + error = verify_profile(profile); + if (error) + goto fail_profile; + + error = aa_calc_profile_hash(profile, e.version, start, + e.pos - start); + if (error) + goto fail_profile; + + ent = aa_load_ent_alloc(); + if (!ent) { + error = -ENOMEM; + goto fail_profile; + } + + ent->new = profile; + list_add_tail(&ent->list, lh); + } + + return 0; + +fail_profile: + aa_put_profile(profile); + +fail: + list_for_each_entry_safe(ent, tmp, lh, list) { + list_del_init(&ent->list); + aa_load_ent_free(ent); + } + + return error; +} diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c new file mode 100644 index 000000000..b125acc9a --- /dev/null +++ b/security/apparmor/procattr.c @@ -0,0 +1,165 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /proc/<pid>/attr/ interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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 "include/apparmor.h" +#include "include/context.h" +#include "include/policy.h" +#include "include/domain.h" +#include "include/procattr.h" + + +/** + * aa_getprocattr - Return the profile information for @profile + * @profile: the profile to print profile info about (NOT NULL) + * @string: Returns - string containing the profile info (NOT NULL) + * + * Returns: length of @string on success else error on failure + * + * Requires: profile != NULL + * + * Creates a string containing the namespace_name://profile_name for + * @profile. + * + * Returns: size of string placed in @string else error code on failure + */ +int aa_getprocattr(struct aa_profile *profile, 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_namespace *ns = profile->ns; + struct aa_namespace *current_ns = __aa_current_profile()->ns; + char *s; + + if (!aa_ns_visible(current_ns, ns)) + return -EACCES; + + ns_name = aa_ns_name(current_ns, ns); + ns_len = strlen(ns_name); + + /* if the visible ns_name is > 0 increase size for : :// seperator */ + if (ns_len) + ns_len += 4; + + /* unconfined profiles don't have a mode string appended */ + if (!unconfined(profile)) + mode_len = strlen(mode_str) + 3; /* + 3 for _() */ + + 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) + return -ENOMEM; + + if (ns_len) { + /* skip over prefix current_ns->base.hname and separating // */ + sprintf(s, ":%s://", ns_name); + s += ns_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; +} + +/** + * split_token_from_name - separate a string of form <token>^<name> + * @op: operation being checked + * @args: string to parse (NOT NULL) + * @token: stores returned parsed token value (NOT NULL) + * + * Returns: start position of name after token else NULL on failure + */ +static char *split_token_from_name(int op, char *args, u64 * token) +{ + char *name; + + *token = simple_strtoull(args, &name, 16); + if ((name == args) || *name != '^') { + AA_ERROR("%s: Invalid input '%s'", op_table[op], args); + return ERR_PTR(-EINVAL); + } + + name++; /* skip ^ */ + if (!*name) + name = NULL; + return name; +} + +/** + * 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 + * + * Returns: %0 or error code if change_hat fails + */ +int aa_setprocattr_changehat(char *args, size_t size, int test) +{ + char *hat; + u64 token; + const char *hats[16]; /* current hard limit on # of names */ + int count = 0; + + hat = split_token_from_name(OP_CHANGE_HAT, args, &token); + if (IS_ERR(hat)) + return PTR_ERR(hat); + + if (!hat && !token) { + AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic"); + return -EINVAL; + } + + if (hat) { + /* set up hat name vector, args guaranteed null terminated + * at args[size] by setprocattr. + * + * If there are multiple hat names in the buffer each is + * separated by a \0. Ie. userspace writes them pre tokenized + */ + char *end = args + size; + for (count = 0; (hat < end) && count < 16; ++count) { + char *next = hat + strlen(hat) + 1; + hats[count] = hat; + hat = next; + } + } + + AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n", + __func__, token, hat ? hat : NULL); + + return aa_change_hat(hats, count, token, test); +} + +/** + * aa_setprocattr_changeprofile - handle procattr interface to changeprofile + * @fqname: args received from writting to /proc/<pid>/attr/current (NOT NULL) + * @onexec: true if change_profile should be delayed until exec + * @test: true if this is a test of change_profile permissions + * + * Returns: %0 or error code if change_profile fails + */ +int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test) +{ + char *name, *ns_name; + + name = aa_split_fqname(fqname, &ns_name); + return aa_change_profile(ns_name, name, onexec, test); +} diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c new file mode 100644 index 000000000..748bf0ca6 --- /dev/null +++ b/security/apparmor/resource.c @@ -0,0 +1,154 @@ +/* + * AppArmor security module + * + * This file contains AppArmor resource mediation and attachment + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 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 "include/audit.h" +#include "include/context.h" +#include "include/resource.h" +#include "include/policy.h" + +/* + * Table of rlimit names: we generate it from resource.h. + */ +#include "rlim_names.h" + +struct aa_fs_entry aa_fs_entry_rlimit[] = { + AA_FS_FILE_STRING("mask", AA_FS_RLIMIT_MASK), + { } +}; + +/* audit callback for resource specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " rlimit=%s value=%lu", + rlim_names[sa->aad->rlim.rlim], sa->aad->rlim.max); +} + +/** + * audit_resource - audit setting resource limit + * @profile: profile being enforced (NOT NULL) + * @resoure: rlimit being auditing + * @value: value being set + * @error: error value + * + * 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) +{ + struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = OP_SETRLIMIT, + aad.rlim.rlim = resource; + aad.rlim.max = value; + aad.error = error; + return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa, + audit_cb); +} + +/** + * aa_map_resouce - map compiled policy resource to internal # + * @resource: flattened policy resource number + * + * Returns: resource # for the current architecture. + * + * rlimit resource can vary based on architecture, map the compiled policy + * resource # to the internal representation for the architecture. + */ +int aa_map_resource(int resource) +{ + return rlim_map[resource]; +} + +/** + * aa_task_setrlimit - test permission to set an rlimit + * @profile - profile 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) + * + * Control raising the processes hard limit. + * + * Returns: 0 or error code if setting resource failed + */ +int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, + unsigned int resource, struct rlimit *new_rlim) +{ + struct aa_profile *task_profile; + int error = 0; + + rcu_read_lock(); + task_profile = aa_get_profile(aa_cred_profile(__task_cred(task))); + rcu_read_unlock(); + + /* TODO: extend resource control to handle other (non current) + * profiles. AppArmor rules currently have the implicit assumption + * that the task is setting the resource of a task confined with + * the same profile. + */ + if (profile != task_profile || + (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); +} + +/** + * __aa_transition_rlimits - apply new profile rlimits + * @old: old profile on task (NOT NULL) + * @new: new profile with rlimits to apply (NOT NULL) + */ +void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new) +{ + unsigned int mask = 0; + struct rlimit *rlim, *initrlim; + int i; + + /* for any rlimits the profile controlled reset the soft limit + * to the less 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); + } + } + } + + /* 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; + + 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); + } +} diff --git a/security/apparmor/sid.c b/security/apparmor/sid.c new file mode 100644 index 000000000..f0b34f76e --- /dev/null +++ b/security/apparmor/sid.c @@ -0,0 +1,55 @@ +/* + * AppArmor security module + * + * This file contains AppArmor security identifier (sid) manipulation fns + * + * Copyright 2009-2010 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. + * + * + * AppArmor allocates a unique sid for every profile loaded. If a profile + * is replaced it receives the sid of the profile it is replacing. + * + * The sid value of 0 is invalid. + */ + +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/err.h> + +#include "include/sid.h" + +/* global counter from which sids are allocated */ +static u32 global_sid; +static DEFINE_SPINLOCK(sid_lock); + +/* TODO FIXME: add sid to profile mapping, and sid recycling */ + +/** + * aa_alloc_sid - allocate a new sid for a profile + */ +u32 aa_alloc_sid(void) +{ + u32 sid; + + /* + * TODO FIXME: sid recycling - part of profile mapping table + */ + spin_lock(&sid_lock); + sid = (++global_sid); + spin_unlock(&sid_lock); + return sid; +} + +/** + * aa_free_sid - free a sid + * @sid: sid to free + */ +void aa_free_sid(u32 sid) +{ + ; /* NOP ATM */ +} |