diff options
Diffstat (limited to 'security/apparmor')
34 files changed, 2459 insertions, 919 deletions
diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 4d995aeaebc..9cdec70d72b 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,6 +1,5 @@  #  # Generated include files  # -af_names.h  capability_names.h  rlim_names.h diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b2e32..d49c53960b6 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,15 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE  	  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 index f204869399e..d693df87481 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -5,20 +5,66 @@ 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 af_names.h +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 *capability_names[] = {" > $@ ; sed -n -e "/CAP_FS_MASK/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\$$/[\\2]  = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ +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 *rlim_names[] = {" > $@ ; sed -n --e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+RLIMIT_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2]  = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ ; echo "static const int rlim_map[] = {" >> $@ ; sed -n -e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+\\(RLIMIT_[A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/\\1,/p" $< >> $@ ; echo "};" >> $@ +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/linux/capability.h +$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ +			    $(src)/Makefile  	$(call cmd,make-caps) -$(obj)/af_names.h : $(srctree)/include/linux/socket.h -	$(call cmd,make-af) -$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h +$(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 index 0848292982a..7db9954f1af 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -12,18 +12,62 @@   * 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 @@ -142,38 +186,733 @@ static const struct file_operations aa_fs_profile_remove = {  	.llseek = default_llseek,  }; -/** Base file system setup **/ +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 = profile->dents[i]->d_inode->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; -static struct dentry *aa_fs_dentry __initdata; +fail: +	error = PTR_ERR(dent); -static void __init aafs_remove(const char *name) +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 dentry *dentry; +	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; +} -	dentry = lookup_one_len(name, aa_fs_dentry, strlen(name)); -	if (!IS_ERR(dentry)) { -		securityfs_remove(dentry); -		dput(dentry); +/** + * __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;  }  /** - * aafs_create - create an entry in the apparmor filesystem - * @name: name of the entry (NOT NULL) - * @mask: file permission mask of the file - * @fops: file operations for the file (NOT NULL) + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL)   * - * Used aafs_remove to remove entries created with this fn. + * 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 int __init aafs_create(const char *name, int mask, -			      const struct file_operations *fops) +static struct aa_profile *__next_profile(struct aa_profile *p)  { -	struct dentry *dentry; +	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)); +	} -	dentry = securityfs_create_file(name, S_IFREG | mask, aa_fs_dentry, -					NULL, fops); +	/* 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 IS_ERR(dentry) ? PTR_ERR(dentry) : 0; +	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);  }  /** @@ -183,14 +922,7 @@ static int __init aafs_create(const char *name, int mask,   */  void __init aa_destroy_aafs(void)  { -	if (aa_fs_dentry) { -		aafs_remove(".remove"); -		aafs_remove(".replace"); -		aafs_remove(".load"); - -		securityfs_remove(aa_fs_dentry); -		aa_fs_dentry = NULL; -	} +	aafs_remove_dir(&aa_fs_entry);  }  /** @@ -200,32 +932,25 @@ void __init aa_destroy_aafs(void)   *   * Returns: error on failure   */ -int __init aa_create_aafs(void) +static int __init aa_create_aafs(void)  {  	int error;  	if (!apparmor_initialized)  		return 0; -	if (aa_fs_dentry) { +	if (aa_fs_entry.dentry) {  		AA_ERROR("%s: AppArmor securityfs already exists\n", __func__);  		return -EEXIST;  	} -	aa_fs_dentry = securityfs_create_dir("apparmor", NULL); -	if (IS_ERR(aa_fs_dentry)) { -		error = PTR_ERR(aa_fs_dentry); -		aa_fs_dentry = NULL; -		goto error; -	} - -	error = aafs_create(".load", 0640, &aa_fs_profile_load); -	if (error) -		goto error; -	error = aafs_create(".replace", 0640, &aa_fs_profile_replace); +	/* Populate fs tree. */ +	error = aafs_create_dir(&aa_fs_entry, NULL);  	if (error)  		goto error; -	error = aafs_create(".remove", 0640, &aa_fs_profile_remove); + +	error = __aa_fs_namespace_mkdir(root_ns, aa_fs_entry.dentry, +					"policy");  	if (error)  		goto error; diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 96502b22b26..89c78658031 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -19,7 +19,7 @@  #include "include/audit.h"  #include "include/policy.h" -const char *op_table[] = { +const char *const op_table[] = {  	"null",  	"sysctl", @@ -73,7 +73,7 @@ const char *op_table[] = {  	"profile_remove"  }; -const char *audit_mode_names[] = { +const char *const audit_mode_names[] = {  	"normal",  	"quiet_denied",  	"quiet", @@ -81,14 +81,15 @@ const char *audit_mode_names[] = {  	"all"  }; -static char *aa_audit_type[] = { +static const char *const aa_audit_type[] = {  	"AUDIT",  	"ALLOWED",  	"DENIED",  	"HINT",  	"STATUS",  	"ERROR", -	"KILLED" +	"KILLED", +	"AUTO"  };  /* @@ -110,32 +111,26 @@ static char *aa_audit_type[] = {  static void audit_pre(struct audit_buffer *ab, void *ca)  {  	struct common_audit_data *sa = ca; -	struct task_struct *tsk = sa->tsk ? sa->tsk : current;  	if (aa_g_audit_header) {  		audit_log_format(ab, "apparmor="); -		audit_log_string(ab, aa_audit_type[sa->aad.type]); +		audit_log_string(ab, aa_audit_type[sa->aad->type]);  	} -	if (sa->aad.op) { +	if (sa->aad->op) {  		audit_log_format(ab, " operation="); -		audit_log_string(ab, op_table[sa->aad.op]); +		audit_log_string(ab, op_table[sa->aad->op]);  	} -	if (sa->aad.info) { +	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); +		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; -		pid_t pid; -		rcu_read_lock(); -		pid = tsk->real_parent->pid; -		rcu_read_unlock(); -		audit_log_format(ab, " parent=%d", pid); +	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); @@ -144,9 +139,9 @@ static void audit_pre(struct audit_buffer *ab, void *ca)  		audit_log_untrustedstring(ab, profile->base.hname);  	} -	if (sa->aad.name) { +	if (sa->aad->name) {  		audit_log_format(ab, " name="); -		audit_log_untrustedstring(ab, sa->aad.name); +		audit_log_untrustedstring(ab, sa->aad->name);  	}  } @@ -158,10 +153,8 @@ static void audit_pre(struct audit_buffer *ab, void *ca)  void aa_audit_msg(int type, struct common_audit_data *sa,  		  void (*cb) (struct audit_buffer *, void *))  { -	sa->aad.type = type; -	sa->lsm_pre_audit = audit_pre; -	sa->lsm_post_audit = cb; -	common_lsm_audit(sa); +	sa->aad->type = type; +	common_lsm_audit(sa, audit_pre, cb);  }  /** @@ -183,7 +176,7 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,  	BUG_ON(!profile);  	if (type == AUDIT_APPARMOR_AUTO) { -		if (likely(!sa->aad.error)) { +		if (likely(!sa->aad->error)) {  			if (AUDIT_MODE(profile) != AUDIT_ALL)  				return 0;  			type = AUDIT_APPARMOR_AUDIT; @@ -195,21 +188,22 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,  	if (AUDIT_MODE(profile) == AUDIT_QUIET ||  	    (type == AUDIT_APPARMOR_DENIED &&  	     AUDIT_MODE(profile) == AUDIT_QUIET)) -		return sa->aad.error; +		return sa->aad->error;  	if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)  		type = AUDIT_APPARMOR_KILL;  	if (!unconfined(profile)) -		sa->aad.profile = 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->tsk ? sa->tsk : current); +	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); +	if (sa->aad->type == AUDIT_APPARMOR_ALLOWED) +		return complain_error(sa->aad->error); -	return sa->aad.error; +	return sa->aad->error;  } diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 9982c48def4..1101c6f64bb 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -27,6 +27,11 @@   */  #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; @@ -48,8 +53,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)  /**   * audit_caps - audit a capability - * @profile: profile confining task (NOT NULL) - * @task: task capability test was performed against (NOT NULL) + * @profile: profile being tested for confinement (NOT NULL)   * @cap: capability tested   * @error: error code returned by test   * @@ -58,17 +62,17 @@ static void audit_cb(struct audit_buffer *ab, void *va)   *   * Returns: 0 or sa->error on success,  error code on failure   */ -static int audit_caps(struct aa_profile *profile, struct task_struct *task, -		      int cap, int error) +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; -	COMMON_AUDIT_DATA_INIT(&sa, CAP); -	sa.tsk = task; +	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; +	sa.aad->op = OP_CAPABLE; +	sa.aad->error = error;  	if (likely(!error)) {  		/* test if auditing is being forced */ @@ -117,8 +121,7 @@ static int profile_capable(struct aa_profile *profile, int cap)  /**   * aa_capable - test permission to use capability - * @task: task doing capability test against (NOT NULL) - * @profile: profile confining @task (NOT NULL) + * @profile: profile being tested against (NOT NULL)   * @cap: capability to be tested   * @audit: whether an audit record should be generated   * @@ -126,8 +129,7 @@ static int profile_capable(struct aa_profile *profile, int cap)   *   * Returns: 0 on success, or else an error code.   */ -int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, -	       int audit) +int aa_capable(struct aa_profile *profile, int cap, int audit)  {  	int error = profile_capable(profile, cap); @@ -137,5 +139,5 @@ int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,  		return error;  	} -	return audit_caps(profile, task, cap, error); +	return audit_caps(profile, cap, error);  } diff --git a/security/apparmor/context.c b/security/apparmor/context.c index 8a9b5027c81..3064c6ced87 100644 --- a/security/apparmor/context.c +++ b/security/apparmor/context.c @@ -69,6 +69,23 @@ void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old)  }  /** + * 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)   * @@ -76,7 +93,7 @@ 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)  { -	struct aa_task_cxt *cxt = current_cred()->security; +	struct aa_task_cxt *cxt = current_cxt();  	struct cred *new;  	BUG_ON(!profile); @@ -87,21 +104,17 @@ int aa_replace_current_profile(struct aa_profile *profile)  	if (!new)  		return -ENOMEM; -	cxt = new->security; -	if (unconfined(profile) || (cxt->profile->ns != profile->ns)) { +	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_put_profile(cxt->previous); -		aa_put_profile(cxt->onexec); -		cxt->previous = NULL; -		cxt->onexec = NULL; -		cxt->token = 0; -	} +		aa_clear_task_cxt_trans(cxt); +  	/* be careful switching cxt->profile, when racing replacement it -	 * is possible that cxt->profile->replacedby is the reference keeping -	 * @profile valid, so make sure to get its reference before dropping -	 * the reference on cxt->profile */ +	 * 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; @@ -123,7 +136,7 @@ int aa_set_current_onexec(struct aa_profile *profile)  	if (!new)  		return -ENOMEM; -	cxt = new->security; +	cxt = cred_cxt(new);  	aa_get_profile(profile);  	aa_put_profile(cxt->onexec);  	cxt->onexec = profile; @@ -150,7 +163,7 @@ int aa_set_current_hat(struct aa_profile *profile, u64 token)  		return -ENOMEM;  	BUG_ON(!profile); -	cxt = new->security; +	cxt = cred_cxt(new);  	if (!cxt->previous) {  		/* transfer refcount */  		cxt->previous = cxt->profile; @@ -162,7 +175,7 @@ int aa_set_current_hat(struct aa_profile *profile, u64 token)  		abort_creds(new);  		return -EACCES;  	} -	cxt->profile = aa_get_profile(aa_newest_version(profile)); +	cxt->profile = aa_get_newest_profile(profile);  	/* clear exec on switching context */  	aa_put_profile(cxt->onexec);  	cxt->onexec = NULL; @@ -187,7 +200,7 @@ int aa_restore_previous_profile(u64 token)  	if (!new)  		return -ENOMEM; -	cxt = new->security; +	cxt = cred_cxt(new);  	if (cxt->token != token) {  		abort_creds(new);  		return -EACCES; @@ -199,17 +212,10 @@ int aa_restore_previous_profile(u64 token)  	}  	aa_put_profile(cxt->profile); -	cxt->profile = aa_newest_version(cxt->previous); +	cxt->profile = aa_get_newest_profile(cxt->previous);  	BUG_ON(!cxt->profile); -	if (unlikely(cxt->profile != cxt->previous)) { -		aa_get_profile(cxt->profile); -		aa_put_profile(cxt->previous); -	}  	/* clear exec && prev information when restoring to previous context */ -	cxt->previous = NULL; -	cxt->token = 0; -	aa_put_profile(cxt->onexec); -	cxt->onexec = NULL; +	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 00000000000..532471d0b3a --- /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 index c825c6e0b63..452567d3a08 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -50,40 +50,34 @@ void aa_free_domain_entries(struct aa_domain *domain)  /**   * may_change_ptraced_domain - check if can change profile on ptraced task - * @task: task we want to change profile of   (NOT NULL)   * @to_profile: profile to change to  (NOT NULL)   * - * Check if the task is ptraced and if so if the tracing task is allowed + * 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 task_struct *task, -				     struct aa_profile *to_profile) +static int may_change_ptraced_domain(struct aa_profile *to_profile)  {  	struct task_struct *tracer; -	const struct cred *cred = NULL;  	struct aa_profile *tracerp = NULL;  	int error = 0;  	rcu_read_lock(); -	tracer = tracehook_tracer_task(task); -	if (tracer) { +	tracer = ptrace_parent(current); +	if (tracer)  		/* released below */ -		cred = get_task_cred(tracer); -		tracerp = aa_cred_profile(cred); -	} -	rcu_read_unlock(); +		tracerp = aa_get_task_profile(tracer);  	/* not ptraced */  	if (!tracer || unconfined(tracerp))  		goto out; -	error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH); +	error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH);  out: -	if (cred) -		put_cred(cred); +	rcu_read_unlock(); +	aa_put_profile(tracerp);  	return error;  } @@ -148,7 +142,7 @@ static struct aa_profile *__attach_match(const char *name,  	int len = 0;  	struct aa_profile *profile, *candidate = NULL; -	list_for_each_entry(profile, head, base.list) { +	list_for_each_entry_rcu(profile, head, base.list) {  		if (profile->flags & PFLAG_NULL)  			continue;  		if (profile->xmatch && profile->xmatch_len > len) { @@ -181,9 +175,9 @@ static struct aa_profile *find_attach(struct aa_namespace *ns,  {  	struct aa_profile *profile; -	read_lock(&ns->lock); +	rcu_read_lock();  	profile = aa_get_profile(__attach_match(name, list)); -	read_unlock(&ns->lock); +	rcu_read_unlock();  	return profile;  } @@ -349,8 +343,8 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)  	unsigned int state;  	struct file_perms perms = {};  	struct path_cond cond = { -		bprm->file->f_path.dentry->d_inode->i_uid, -		bprm->file->f_path.dentry->d_inode->i_mode +		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); @@ -360,10 +354,10 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)  	if (bprm->cred_prepared)  		return 0; -	cxt = bprm->cred->security; +	cxt = cred_cxt(bprm->cred);  	BUG_ON(!cxt); -	profile = aa_get_profile(aa_newest_version(cxt->profile)); +	profile = aa_get_newest_profile(cxt->profile);  	/*  	 * get the namespace from the replacement profile as replacement  	 * can change the namespace @@ -372,13 +366,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)  	state = profile->file.start;  	/* buffer freed below, name is pointer into buffer */ -	error = aa_get_name(&bprm->file->f_path, profile->path_flags, &buffer, -			    &name); +	error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, +			     &name, &info);  	if (error) { -		if (profile->flags & -		    (PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED)) +		if (unconfined(profile) || +		    (profile->flags & PFLAG_IX_ON_NAME_ERROR))  			error = 0; -		info = "Exec failed name resolution";  		name = bprm->filename;  		goto audit;  	} @@ -395,6 +388,11 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)  			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;  	} @@ -411,12 +409,13 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)  		 * exec\0change_profile  		 */  		state = aa_dfa_null_transition(profile->file.dfa, state); -		cp = change_profile_perms(profile, cxt->onexec->ns, name, +		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_profile(aa_newest_version(cxt->onexec)); +		new_profile = aa_get_newest_profile(cxt->onexec);  		goto apply;  	} @@ -433,11 +432,13 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)  				new_profile = aa_get_profile(profile);  				goto x_clear;  			} else if (perms.xindex & AA_X_UNCONFINED) { -				new_profile = aa_get_profile(ns->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)) { @@ -455,6 +456,16 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)  		/* 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; @@ -464,7 +475,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)  	}  	if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { -		error = may_change_ptraced_domain(current, new_profile); +		error = may_change_ptraced_domain(new_profile);  		if (error) {  			aa_put_profile(new_profile);  			goto audit; @@ -499,11 +510,7 @@ x_clear:  	cxt->profile = new_profile;  	/* clear out all temporary/transitional state from the context */ -	aa_put_profile(cxt->previous); -	aa_put_profile(cxt->onexec); -	cxt->previous = NULL; -	cxt->onexec = NULL; -	cxt->token = 0; +	aa_clear_task_cxt_trans(cxt);  audit:  	error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC, @@ -542,7 +549,7 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm)  void apparmor_bprm_committing_creds(struct linux_binprm *bprm)  {  	struct aa_profile *profile = __aa_current_profile(); -	struct aa_task_cxt *new_cxt = bprm->cred->security; +	struct aa_task_cxt *new_cxt = cred_cxt(bprm->cred);  	/* bail out if unconfined or not changing profile */  	if ((new_cxt->profile == profile) || @@ -609,9 +616,17 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)  	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 (current->no_new_privs) +		return -EPERM; +  	/* released below */  	cred = get_current_cred(); -	cxt = cred->security; +	cxt = cred_cxt(cred);  	profile = aa_cred_profile(cred);  	previous_profile = cxt->previous; @@ -624,7 +639,10 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)  	if (count) {  		/* attempting to change into a new hat or switch to a sibling */  		struct aa_profile *root; -		root = PROFILE_IS_HAT(profile) ? profile->parent : profile; +		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++) @@ -636,6 +654,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)  					error = -ECHILD;  				else  					error = -ENOENT; +				aa_put_profile(root);  				goto out;  			} @@ -650,6 +669,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)  			/* 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); @@ -659,6 +679,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)  				goto audit;  			}  		} else { +			aa_put_profile(root);  			target = hat->base.hname;  			if (!PROFILE_IS_HAT(hat)) {  				info = "target not hat"; @@ -667,7 +688,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)  			}  		} -		error = may_change_ptraced_domain(current, hat); +		error = may_change_ptraced_domain(hat);  		if (error) {  			info = "ptraced";  			error = -EPERM; @@ -698,7 +719,7 @@ audit:  	if (!permtest)  		error = aa_audit_file(profile, &perms, GFP_KERNEL,  				      OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, -				      target, 0, info, error); +				      target, GLOBAL_ROOT_UID, info, error);  out:  	aa_put_profile(hat); @@ -727,7 +748,6 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,  		      bool permtest)  {  	const struct cred *cred; -	struct aa_task_cxt *cxt;  	struct aa_profile *profile, *target = NULL;  	struct aa_namespace *ns = NULL;  	struct file_perms perms = {}; @@ -747,9 +767,20 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,  	}  	cred = get_current_cred(); -	cxt = cred->security;  	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 (current->no_new_privs && !unconfined(profile)) { +		put_cred(cred); +		return -EPERM; +	} +  	if (ns_name) {  		/* released below */  		ns = aa_find_namespace(profile->ns, ns_name); @@ -796,7 +827,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,  	}  	/* check if tracing task is allowed to trace target domain */ -	error = may_change_ptraced_domain(current, target); +	error = may_change_ptraced_domain(target);  	if (error) {  		info = "ptrace prevents transition";  		goto audit; @@ -813,7 +844,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,  audit:  	if (!permtest)  		error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, -				      name, hname, 0, info, error); +				      name, hname, GLOBAL_ROOT_UID, info, error);  	aa_put_namespace(ns);  	aa_put_profile(target); diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 7312db74121..fdaa50cb187 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -65,24 +65,26 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask)  static void file_audit_cb(struct audit_buffer *ab, void *va)  {  	struct common_audit_data *sa = va; -	uid_t fsuid = current_fsuid(); +	kuid_t fsuid = current_fsuid(); -	if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) { +	if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) {  		audit_log_format(ab, " requested_mask="); -		audit_file_mask(ab, sa->aad.fs.request); +		audit_file_mask(ab, sa->aad->fs.request);  	} -	if (sa->aad.fs.denied & AA_AUDIT_FILE_MASK) { +	if (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) {  		audit_log_format(ab, " denied_mask="); -		audit_file_mask(ab, sa->aad.fs.denied); +		audit_file_mask(ab, sa->aad->fs.denied);  	} -	if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) { -		audit_log_format(ab, " fsuid=%d", fsuid); -		audit_log_format(ab, " ouid=%d", sa->aad.fs.ouid); +	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) { +	if (sa->aad->fs.target) {  		audit_log_format(ab, " target="); -		audit_log_untrustedstring(ab, sa->aad.fs.target); +		audit_log_untrustedstring(ab, sa->aad->fs.target);  	}  } @@ -103,49 +105,51 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)   */  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, uid_t ouid, const char *info, int error) +		  const char *target, kuid_t ouid, const char *info, int error)  {  	int type = AUDIT_APPARMOR_AUTO;  	struct common_audit_data sa; -	COMMON_AUDIT_DATA_INIT(&sa, NONE); -	sa.aad.op = op, -	sa.aad.fs.request = request; -	sa.aad.name = name; -	sa.aad.fs.target = target; -	sa.aad.fs.ouid = ouid; -	sa.aad.info = info; -	sa.aad.error = error; - -	if (likely(!sa.aad.error)) { +	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; +		sa.aad->fs.request &= mask; -		if (likely(!sa.aad.fs.request)) +		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; +		sa.aad->fs.request = sa.aad->fs.request & ~perms->allow; -		if (sa.aad.fs.request & perms->kill) +		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) && +		if ((sa.aad->fs.request & perms->quiet) &&  		    AUDIT_MODE(profile) != AUDIT_NOQUIET &&  		    AUDIT_MODE(profile) != AUDIT_ALL) -			sa.aad.fs.request &= ~perms->quiet; +			sa.aad->fs.request &= ~perms->quiet; -		if (!sa.aad.fs.request) -			return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; +		if (!sa.aad->fs.request) +			return COMPLAIN_MODE(profile) ? 0 : sa.aad->error;  	} -	sa.aad.fs.denied = sa.aad.fs.request & ~perms->allow; +	sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow;  	return aa_audit(type, profile, gfp, &sa, file_audit_cb);  } @@ -173,8 +177,6 @@ static u32 map_old_perms(u32 old)  	if (old & 0x40)	/* AA_EXEC_MMAP */  		new |= AA_EXEC_MMAP; -	new |= AA_MAY_META_READ; -  	return new;  } @@ -201,7 +203,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,  	 */  	perms.kill = 0; -	if (current_fsuid() == cond->uid) { +	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)); @@ -212,10 +214,13 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,  		perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));  		perms.xindex = dfa_other_xindex(dfa, state);  	} +	perms.allow |= AA_MAY_META_READ;  	/* 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;  } @@ -279,22 +284,16 @@ int aa_path_perm(int op, struct aa_profile *profile, struct path *path,  	int error;  	flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); -	error = aa_get_name(path, flags, &buffer, &name); +	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 if (error == -ENOENT) -			info = "Failed name lookup - deleted entry"; -		else if (error == -ESTALE) -			info = "Failed name lookup - disconnected path"; -		else if (error == -ENAMETOOLONG) -			info = "Failed name lookup - name too long"; -		else -			info = "Failed name lookup"; +		}  	} else {  		aa_str_perms(profile->file.dfa, profile->file.start, name, cond,  			     &perms); @@ -365,12 +364,14 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,  	lperms = nullperms;  	/* buffer freed below, lname is pointer in buffer */ -	error = aa_get_name(&link, profile->path_flags, &buffer, &lname); +	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_get_name(&target, profile->path_flags, &buffer2, &tname); +	error = aa_path_name(&target, profile->path_flags, &buffer2, &tname, +			     &info);  	if (error)  		goto audit; @@ -448,8 +449,8 @@ int aa_file_perm(int op, struct aa_profile *profile, struct file *file,  		 u32 request)  {  	struct path_cond cond = { -		.uid = file->f_path.dentry->d_inode->i_uid, -		.mode = file->f_path.dentry->d_inode->i_mode +		.uid = file_inode(file)->i_uid, +		.mode = file_inode(file)->i_mode  	};  	return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 38ccaea0820..97130f88838 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -15,17 +15,31 @@  #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 int aa_g_audit_header; -extern int aa_g_debug; -extern int aa_g_lock_policy; -extern int aa_g_logsyscall; -extern int aa_g_paranoid_load; +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;  /* @@ -51,9 +65,23 @@ 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 *kvmalloc(size_t size); -void kvfree(void *buffer); +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 @@ -81,7 +109,7 @@ 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_match_len(dfa, start, "", 1); +	return aa_dfa_next(dfa, start, 0);  }  static inline bool mediated_filesystem(struct inode *inode) diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a114d..414e56878dd 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -15,6 +15,90 @@  #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 index 1951786d32e..ba3dfd17f23 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -25,11 +25,8 @@  struct aa_profile; -extern const char *audit_mode_names[]; +extern const char *const audit_mode_names[];  #define AUDIT_MAX_INDEX 5 - -#define AUDIT_APPARMOR_AUTO 0	/* auto choose audit message type */ -  enum audit_mode {  	AUDIT_NORMAL,		/* follow normal auditing of accesses */  	AUDIT_QUIET_DENIED,	/* quiet all denied access messages */ @@ -45,10 +42,11 @@ enum audit_type {  	AUDIT_APPARMOR_HINT,  	AUDIT_APPARMOR_STATUS,  	AUDIT_APPARMOR_ERROR, -	AUDIT_APPARMOR_KILL +	AUDIT_APPARMOR_KILL, +	AUDIT_APPARMOR_AUTO  }; -extern const char *op_table[]; +extern const char *const op_table[];  enum aa_ops {  	OP_NULL, @@ -104,7 +102,33 @@ enum aa_ops {  }; -/* define a short hand for apparmor_audit_data portion of common_audit_data */ +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, diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index c24d2959ea0..fc3fa381d85 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -4,7 +4,7 @@   * This file contains AppArmor capability mediation definitions.   *   * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * 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 @@ -17,6 +17,8 @@  #include <linux/sched.h> +#include "apparmorfs.h" +  struct aa_profile;  /* aa_caps - confinement data for capabilities @@ -34,8 +36,9 @@ struct aa_caps {  	kernel_cap_t extended;  }; -int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, -	       int audit); +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)  { diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h index a9cbee4d9e4..6bf65798e5d 100644 --- a/security/apparmor/include/context.h +++ b/security/apparmor/include/context.h @@ -21,6 +21,9 @@  #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   * @@ -80,23 +83,8 @@ 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_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) -{ -	struct aa_task_cxt *cxt = __task_cred(task)->security; - -	BUG_ON(!cxt || !cxt->profile); -	if (unconfined(aa_newest_version(cxt->profile))) -		return 0; - -	return 1; -}  /**   * aa_cred_profile - obtain cred's profiles @@ -108,9 +96,33 @@ static inline bool __aa_task_is_confined(struct task_struct *task)   */  static inline struct aa_profile *aa_cred_profile(const struct cred *cred)  { -	struct aa_task_cxt *cxt = cred->security; +	struct aa_task_cxt *cxt = cred_cxt(cred);  	BUG_ON(!cxt || !cxt->profile); -	return aa_newest_version(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));  }  /** @@ -136,19 +148,31 @@ static inline struct aa_profile *__aa_current_profile(void)   */  static inline struct aa_profile *aa_current_profile(void)  { -	const struct aa_task_cxt *cxt = current_cred()->security; +	const struct aa_task_cxt *cxt = current_cxt();  	struct aa_profile *profile;  	BUG_ON(!cxt || !cxt->profile); -	profile = aa_newest_version(cxt->profile); -	/* -	 * Whether or not replacement succeeds, use newest profile so -	 * there is no need to update it after replacement. -	 */ -	if (unlikely((cxt->profile != 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 profile; +	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 00000000000..dc418e5024d --- /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/file.h b/security/apparmor/include/file.h index be36feabb16..2c922b86bd4 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -15,12 +15,11 @@  #ifndef __AA_FILE_H  #define __AA_FILE_H -#include <linux/path.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 @@ -72,7 +71,7 @@ struct aa_profile;  /* need to make conditional which ones are being set */  struct path_cond { -	uid_t uid; +	kuid_t uid;  	umode_t mode;  }; @@ -118,7 +117,7 @@ static inline u16 dfa_map_xindex(u16 mask)  		index |= AA_X_NAME;  	} else if (old_index == 3) {  		index |= AA_X_NAME | AA_X_CHILD; -	} else { +	} else if (old_index) {  		index |= AA_X_TABLE;  		index |= old_index - 4;  	} @@ -147,7 +146,7 @@ static inline u16 dfa_map_xindex(u16 mask)  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, uid_t ouid, const char *info, int error); +		  const char *target, kuid_t ouid, const char *info, int error);  /**   * struct aa_file_rules - components used for file rule permissions @@ -187,11 +186,6 @@ static inline void aa_free_file_rules(struct aa_file_rules *rules)  	aa_free_domain_entries(&rules->trans);  } -#define ACC_FMODE(x) (("\000\004\002\006"[(x)&O_ACCMODE]) | (((x) << 1) & 0x40)) - -/* from namei.c */ -#define MAP_OPEN_FLAGS(x) ((((x) + 1) & O_ACCMODE) ? (x) + 1 : (x)) -  /**   * aa_map_file_perms - map file flags to AppArmor permissions   * @file: open file to map flags to AppArmor permissions @@ -200,8 +194,13 @@ static inline void aa_free_file_rules(struct aa_file_rules *rules)   */  static inline u32 aa_map_file_to_perms(struct file *file)  { -	int flags = MAP_OPEN_FLAGS(file->f_flags); -	u32 perms = ACC_FMODE(file->f_mode); +	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; diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h index aeda0fbc8b2..288ca76e2fb 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -19,8 +19,8 @@  struct aa_profile; -int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, -		  struct aa_profile *tracee, unsigned int mode); +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); diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 734a6d35112..001c43aa040 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -4,7 +4,7 @@   * This file contains AppArmor policy dfa matching engine definitions.   *   * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * 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 @@ -15,25 +15,31 @@  #ifndef __AA_MATCH_H  #define __AA_MATCH_H -#include <linux/workqueue.h> +#include <linux/kref.h>  #define DFA_NOMATCH			0  #define DFA_START			1 -#define DFA_VALID_PERM_MASK		0xffffffff -#define DFA_VALID_PERM2_MASK		0xffffffff  /**   * 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 insted of 0xF13C57B1 though, because - * the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used - * slightly differently (see the apparmor-parser package). + * 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 -#define YYTH_DEF_RECURSE 0x1			/* DEF Table is recursive */  struct table_set_header {  	u32 th_magic;		/* YYTH_MAGIC */ @@ -62,7 +68,7 @@ struct table_set_header {  #define YYTD_DATA32	4  #define YYTD_DATA64	8 -/* Each ACCEPT2 table gets 6 dedicated flags, YYTD_DATAX define the +/* ACCEPT & ACCEPT2 tables gets 6 dedicated flags, YYTD_DATAX define the   * first flags   */  #define ACCEPT1_FLAGS(X) ((X) & 0x3f) @@ -115,6 +121,9 @@ 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);  /** diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 27b327a7fae..286ac75dc88 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -26,6 +26,7 @@ enum path_flags {  	PATH_MEDIATE_DELETED = 0x10000,	/* mediate deleted paths */  }; -int aa_get_name(struct path *path, int flags, char **buffer, const char **name); +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 index aeda5cf5690..c28b0f20ab5 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -29,19 +29,23 @@  #include "file.h"  #include "resource.h" -extern const char *profile_mode_names[]; -#define APPARMOR_NAMES_MAX_INDEX 3 +extern const char *const aa_profile_mode_names[]; +#define APPARMOR_MODE_NAMES_MAX_INDEX 4 -#define COMPLAIN_MODE(_profile)	\ -	((aa_g_profile_mode == APPARMOR_COMPLAIN) || \ -	 ((_profile)->mode == APPARMOR_COMPLAIN)) +#define PROFILE_MODE(_profile, _mode)		\ +	((aa_g_profile_mode == (_mode)) ||	\ +	 ((_profile)->mode == (_mode))) -#define KILL_MODE(_profile) \ -	((aa_g_profile_mode == APPARMOR_KILL) || \ -	 ((_profile)->mode == APPARMOR_KILL)) +#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. @@ -52,17 +56,19 @@ 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_UNCONFINED = 2,		/* profile is an unconfined profile */  	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 */ @@ -73,14 +79,12 @@ struct aa_profile;  /* struct aa_policy - common part of both namespaces and profiles   * @name: name of the object   * @hname - The hierarchical name - * @count: reference count of the obj   * @list: list policy object is on   * @profiles: head of the profiles list contained in the object   */  struct aa_policy {  	char *name;  	char *hname; -	struct kref count;  	struct list_head list;  	struct list_head profiles;  }; @@ -105,6 +109,9 @@ struct aa_ns_acct {   * @acct: accounting for the namespace   * @unconfined: special unconfined profile for the namespace   * @sub_ns: list of namespaces under the current namespace. + * @uniq_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 @@ -123,37 +130,63 @@ struct aa_ns_acct {  struct aa_namespace {  	struct aa_policy base;  	struct aa_namespace *parent; -	rwlock_t lock; +	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 - * @sid: the unique security id number of this profile   * @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 field is write protected by the profile lock.  Reads - * are assumed to be atomic, and are done without locking. + * 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. @@ -164,24 +197,31 @@ struct aa_namespace {   */  struct aa_profile {  	struct aa_policy base; -	struct aa_profile *parent; +	struct kref count; +	struct rcu_head rcu; +	struct aa_profile __rcu *parent;  	struct aa_namespace *ns; -	struct aa_profile *replacedby; +	struct aa_replacedby *replacedby;  	const char *rename; +	const char *attach;  	struct aa_dfa *xmatch;  	int xmatch_len; -	u32 sid;  	enum audit_mode audit; -	enum profile_mode mode; -	u32 flags; +	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; @@ -198,43 +238,11 @@ void aa_free_namespace_kref(struct kref *kref);  struct aa_namespace *aa_find_namespace(struct aa_namespace *root,  				       const char *name); -static inline struct aa_policy *aa_get_common(struct aa_policy *c) -{ -	if (c) -		kref_get(&c->count); - -	return c; -} - -/** - * 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) -		kref_get(&(ns->base.count)); - -	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) -		kref_put(&ns->base.count, aa_free_namespace_kref); -} +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); @@ -246,25 +254,13 @@ ssize_t aa_remove_profiles(char *name, size_t size);  #define PROF_ADD 1  #define PROF_REPLACE 0 -#define unconfined(X) ((X)->flags & PFLAG_UNCONFINED) +#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) -/** - * aa_newest_version - find the newest version of @profile - * @profile: the profile to check for newer versions of (NOT NULL) - * - * Returns: newest version of @profile, if @profile is the newest version - *          return @profile. - * - * NOTE: the profile returned is not refcounted, The refcount on @profile - * must be held until the caller decides what to do with the returned newest - * version. - */ -static inline struct aa_profile *aa_newest_version(struct aa_profile *profile) -{ -	while (profile->replacedby) -		profile = profile->replacedby; -	return profile; +static inline struct aa_profile *aa_deref_parent(struct aa_profile *p) +{ +	return rcu_dereference_protected(p->parent, +					 mutex_is_locked(&p->ns->lock));  }  /** @@ -277,19 +273,126 @@ static inline struct aa_profile *aa_newest_version(struct aa_profile *profile)  static inline struct aa_profile *aa_get_profile(struct aa_profile *p)  {  	if (p) -		kref_get(&(p->base.count)); +		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->base.count, aa_free_profile_kref); +		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) diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index a2dcccac45a..c214fb88b1b 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -15,6 +15,25 @@  #ifndef __POLICY_INTERFACE_H  #define __POLICY_INTERFACE_H -struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns); +#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 index 544aa6b766a..6bd5f33d953 100644 --- a/security/apparmor/include/procattr.h +++ b/security/apparmor/include/procattr.h @@ -21,6 +21,5 @@  int aa_getprocattr(struct aa_profile *profile, char **string);  int aa_setprocattr_changehat(char *args, size_t size, int test);  int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test); -int aa_setprocattr_permipc(char *fqname);  #endif /* __AA_PROCATTR_H */ diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h index 02baec732bb..d3f4cf02795 100644 --- a/security/apparmor/include/resource.h +++ b/security/apparmor/include/resource.h @@ -18,6 +18,8 @@  #include <linux/resource.h>  #include <linux/sched.h> +#include "apparmorfs.h" +  struct aa_profile;  /* struct aa_rlimit - rlimit settings for the profile @@ -32,6 +34,8 @@ struct aa_rlimit {  	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); diff --git a/security/apparmor/include/sid.h b/security/apparmor/include/sid.h index 020db35c301..513ca0e4896 100644 --- a/security/apparmor/include/sid.h +++ b/security/apparmor/include/sid.h @@ -16,7 +16,9 @@  #include <linux/types.h> -struct aa_profile; +/* 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); diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 649fad88869..777ac1c4725 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -19,13 +19,14 @@  #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); +	audit_log_untrustedstring(ab, sa->aad->target);  }  /** @@ -40,10 +41,12 @@ static int aa_audit_ptrace(struct aa_profile *profile,  			   struct aa_profile *target, int error)  {  	struct common_audit_data sa; -	COMMON_AUDIT_DATA_INIT(&sa, NONE); -	sa.aad.op = OP_PTRACE; -	sa.aad.target = target; -	sa.aad.error = error; +	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); @@ -51,15 +54,14 @@ static int aa_audit_ptrace(struct aa_profile *profile,  /**   * aa_may_ptrace - test if tracer task can trace the tracee - * @tracer_task: task who will do the tracing  (NOT NULL)   * @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 task_struct *tracer_task, struct aa_profile *tracer, -		  struct aa_profile *tracee, unsigned int mode) +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, @@ -69,7 +71,7 @@ int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,  	if (unconfined(tracer) || tracer == tracee)  		return 0;  	/* log this capability request */ -	return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1); +	return aa_capable(tracer, CAP_SYS_PTRACE, 1);  }  /** @@ -92,23 +94,18 @@ int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,  	 *       - tracer profile has CAP_SYS_PTRACE  	 */ -	struct aa_profile *tracer_p; -	/* cred released below */ -	const struct cred *cred = get_task_cred(tracer); +	struct aa_profile *tracer_p = aa_get_task_profile(tracer);  	int error = 0; -	tracer_p = aa_cred_profile(cred);  	if (!unconfined(tracer_p)) { -		/* lcred released below */ -		const struct cred *lcred = get_task_cred(tracee); -		struct aa_profile *tracee_p = aa_cred_profile(lcred); +		struct aa_profile *tracee_p = aa_get_task_profile(tracee); -		error = aa_may_ptrace(tracer, tracer_p, tracee_p, mode); +		error = aa_may_ptrace(tracer_p, tracee_p, mode);  		error = aa_audit_ptrace(tracer_p, tracee_p, error); -		put_cred(lcred); +		aa_put_profile(tracee_p);  	} -	put_cred(cred); +	aa_put_profile(tracer_p);  	return error;  } diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 506d2baf614..c1827e06845 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -12,11 +12,13 @@   * 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"  /** @@ -43,8 +45,10 @@ char *aa_split_fqname(char *fqname, char **ns_name)  		*ns_name = skip_spaces(&name[1]);  		if (split) {  			/* overwrite ':' with \0 */ -			*split = 0; -			name = skip_spaces(split + 1); +			*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; @@ -63,23 +67,26 @@ void aa_info_message(const char *str)  {  	if (audit_enabled) {  		struct common_audit_data sa; -		COMMON_AUDIT_DATA_INIT(&sa, NONE); -		sa.aad.info = str; +		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);  }  /** - * kvmalloc - do allocation preferring kmalloc but falling back to vmalloc - * @size: size of allocation + * __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 *kvmalloc(size_t size) +void *__aa_kvmalloc(size_t size, gfp_t flags)  {  	void *buffer = NULL; @@ -88,46 +95,12 @@ void *kvmalloc(size_t size)  	/* do not attempt kmalloc if we need more than 16 pages at once */  	if (size <= (16*PAGE_SIZE)) -		buffer = kmalloc(size, GFP_NOIO | __GFP_NOWARN); +		buffer = kmalloc(size, flags | GFP_NOIO | __GFP_NOWARN);  	if (!buffer) { -		/* see kvfree for why size must be at least work_struct size -		 * when allocated via vmalloc -		 */ -		if (size < sizeof(struct work_struct)) -			size = sizeof(struct work_struct); -		buffer = vmalloc(size); +		if (flags & __GFP_ZERO) +			buffer = vzalloc(size); +		else +			buffer = vmalloc(size);  	}  	return buffer;  } - -/** - * do_vfree - workqueue routine for freeing vmalloced memory - * @work: data to be freed - * - * The work_struct is overlaid to the data being freed, as at the point - * the work is scheduled the data is no longer valid, be its freeing - * needs to be delayed until safe. - */ -static void do_vfree(struct work_struct *work) -{ -	vfree(work); -} - -/** - * kvfree - free an allocation do by kvmalloc - * @buffer: buffer to free (MAYBE_NULL) - * - * Free a buffer allocated by kvmalloc - */ -void kvfree(void *buffer) -{ -	if (is_vmalloc_addr(buffer)) { -		/* Data is no longer valid so just use the allocated space -		 * as the work_struct -		 */ -		struct work_struct *work = (struct work_struct *) buffer; -		INIT_WORK(work, do_vfree); -		schedule_work(work); -	} else -		kfree(buffer); -} diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index b7106f192b7..99810009333 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -22,6 +22,7 @@  #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" @@ -47,8 +48,8 @@ int apparmor_initialized __initdata;   */  static void apparmor_cred_free(struct cred *cred)  { -	aa_free_task_context(cred->security); -	cred->security = NULL; +	aa_free_task_context(cred_cxt(cred)); +	cred_cxt(cred) = NULL;  }  /* @@ -61,7 +62,7 @@ static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)  	if (!cxt)  		return -ENOMEM; -	cred->security = cxt; +	cred_cxt(cred) = cxt;  	return 0;  } @@ -76,8 +77,8 @@ static int apparmor_cred_prepare(struct cred *new, const struct cred *old,  	if (!cxt)  		return -ENOMEM; -	aa_dup_task_context(cxt, old->security); -	new->security = cxt; +	aa_dup_task_context(cxt, cred_cxt(old)); +	cred_cxt(new) = cxt;  	return 0;  } @@ -86,8 +87,8 @@ static int apparmor_cred_prepare(struct cred *new, const struct cred *old,   */  static void apparmor_cred_transfer(struct cred *new, const struct cred *old)  { -	const struct aa_task_cxt *old_cxt = old->security; -	struct aa_task_cxt *new_cxt = new->security; +	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);  } @@ -126,7 +127,7 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,  	*inheritable = cred->cap_inheritable;  	*permitted = cred->cap_permitted; -	if (!unconfined(profile)) { +	if (!unconfined(profile) && !COMPLAIN_MODE(profile)) {  		*effective = cap_intersect(*effective, profile->caps.allow);  		*permitted = cap_intersect(*permitted, profile->caps.allow);  	} @@ -135,16 +136,16 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,  	return 0;  } -static int apparmor_capable(struct task_struct *task, const struct cred *cred, +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(task, cred, cap, audit); +	int error = cap_capable(cred, ns, cap, audit);  	if (!error) {  		profile = aa_cred_profile(cred);  		if (!unconfined(profile)) -			error = aa_capable(task, profile, cap, audit); +			error = aa_capable(profile, cap, audit);  	}  	return error;  } @@ -261,7 +262,7 @@ static int apparmor_path_unlink(struct path *dir, struct dentry *dentry)  }  static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry, -			       int mode) +			       umode_t mode)  {  	return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE,  				  S_IFDIR); @@ -273,7 +274,7 @@ static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry)  }  static int apparmor_path_mknod(struct path *dir, struct dentry *dentry, -			       int mode, unsigned int dev) +			       umode_t mode, unsigned int dev)  {  	return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode);  } @@ -343,16 +344,15 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,  	return error;  } -static int apparmor_path_chmod(struct dentry *dentry, struct vfsmount *mnt, -			       mode_t mode) +static int apparmor_path_chmod(struct path *path, umode_t mode)  { -	if (!mediated_filesystem(dentry->d_inode)) +	if (!mediated_filesystem(path->dentry->d_inode))  		return 0; -	return common_perm_mnt_dentry(OP_CHMOD, mnt, dentry, AA_MAY_CHMOD); +	return common_perm_mnt_dentry(OP_CHMOD, path->mnt, path->dentry, AA_MAY_CHMOD);  } -static int apparmor_path_chown(struct path *path, uid_t uid, gid_t gid) +static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid)  {  	struct path_cond cond =  { path->dentry->d_inode->i_uid,  				   path->dentry->d_inode->i_mode @@ -373,13 +373,13 @@ static int apparmor_inode_getattr(struct vfsmount *mnt, struct dentry *dentry)  				      AA_MAY_META_READ);  } -static int apparmor_dentry_open(struct file *file, const struct cred *cred) +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->d_inode)) +	if (!mediated_filesystem(file_inode(file)))  		return 0;  	/* If in exec, permission is handled by bprm hooks. @@ -394,7 +394,7 @@ static int apparmor_dentry_open(struct file *file, const struct cred *cred)  	profile = aa_cred_profile(cred);  	if (!unconfined(profile)) { -		struct inode *inode = file->f_path.dentry->d_inode; +		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, @@ -432,7 +432,7 @@ static int common_file_perm(int op, struct file *file, u32 mask)  	BUG_ON(!fprofile);  	if (!file->f_path.mnt || -	    !mediated_filesystem(file->f_path.dentry->d_inode)) +	    !mediated_filesystem(file_inode(file)))  		return 0;  	profile = __aa_current_profile(); @@ -469,7 +469,6 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd)  static int common_mmap(int op, struct file *file, unsigned long prot,  		       unsigned long flags)  { -	struct dentry *dentry;  	int mask = 0;  	if (!file || !file->f_security) @@ -486,21 +485,12 @@ static int common_mmap(int op, struct file *file, unsigned long prot,  	if (prot & PROT_EXEC)  		mask |= AA_EXEC_MMAP; -	dentry = file->f_path.dentry;  	return common_file_perm(op, file, mask);  } -static int apparmor_file_mmap(struct file *file, unsigned long reqprot, -			      unsigned long prot, unsigned long flags, -			      unsigned long addr, unsigned long addr_only) +static int apparmor_mmap_file(struct file *file, unsigned long reqprot, +			      unsigned long prot, unsigned long flags)  { -	int rc = 0; - -	/* do DAC check */ -	rc = cap_file_mmap(file, reqprot, prot, flags, addr, addr_only); -	if (rc || addr_only) -		return rc; -  	return common_mmap(OP_FMMAP, file, prot, flags);  } @@ -515,24 +505,24 @@ static int apparmor_getprocattr(struct task_struct *task, char *name,  				char **value)  {  	int error = -ENOENT; -	struct aa_profile *profile;  	/* released below */  	const struct cred *cred = get_task_cred(task); -	struct aa_task_cxt *cxt = cred->security; -	profile = aa_cred_profile(cred); +	struct aa_task_cxt *cxt = cred_cxt(cred); +	struct aa_profile *profile = NULL;  	if (strcmp(name, "current") == 0) -		error = aa_getprocattr(aa_newest_version(cxt->profile), -				       value); +		profile = aa_get_newest_profile(cxt->profile);  	else if (strcmp(name, "prev") == 0  && cxt->previous) -		error = aa_getprocattr(aa_newest_version(cxt->previous), -				       value); +		profile = aa_get_newest_profile(cxt->previous);  	else if (strcmp(name, "exec") == 0 && cxt->onexec) -		error = aa_getprocattr(aa_newest_version(cxt->onexec), -				       value); +		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; @@ -541,6 +531,8 @@ static int apparmor_getprocattr(struct task_struct *task, char *name,  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; @@ -584,33 +576,37 @@ static int apparmor_setprocattr(struct task_struct *task, char *name,  		} else if (strcmp(command, "permprofile") == 0) {  			error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,  							     AA_DO_TEST); -		} else if (strcmp(command, "permipc") == 0) { -			error = aa_setprocattr_permipc(args); -		} else { -			struct common_audit_data sa; -			COMMON_AUDIT_DATA_INIT(&sa, NONE); -			sa.aad.op = OP_SETPROCATTR; -			sa.aad.info = name; -			sa.aad.error = -EINVAL; -			return aa_audit(AUDIT_APPARMOR_DENIED, NULL, GFP_KERNEL, -					&sa, NULL); -		} +		} else +			goto fail;  	} else if (strcmp(name, "exec") == 0) { -		error = aa_setprocattr_changeprofile(args, AA_ONEXEC, -						     !AA_DO_TEST); -	} else { +		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(); +	struct aa_profile *profile = __aa_current_profile();  	int error = 0;  	if (!unconfined(profile)) @@ -637,13 +633,14 @@ static struct security_operations apparmor_ops = {  	.path_chmod =			apparmor_path_chmod,  	.path_chown =			apparmor_path_chown,  	.path_truncate =		apparmor_path_truncate, -	.dentry_open =			apparmor_dentry_open,  	.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, -	.file_mmap =			apparmor_file_mmap, +	.mmap_file =			apparmor_mmap_file, +	.mmap_addr =			cap_mmap_addr,  	.file_mprotect =		apparmor_file_mprotect,  	.file_lock =			apparmor_file_lock, @@ -669,15 +666,16 @@ static struct security_operations apparmor_ops = {  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(name, p) __param_check(name, p, int) +#define param_check_aabool param_check_bool  static struct kernel_param_ops param_ops_aabool = { +	.flags = KERNEL_PARAM_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(name, p) __param_check(name, p, int) +#define param_check_aauint param_check_uint  static struct kernel_param_ops param_ops_aauint = {  	.set = param_set_aauint,  	.get = param_get_aauint @@ -685,19 +683,18 @@ static struct kernel_param_ops param_ops_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(name, p) __param_check(name, p, int) +#define param_check_aalockpolicy param_check_bool  static struct kernel_param_ops param_ops_aalockpolicy = { +	.flags = KERNEL_PARAM_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); -#define param_check_audit(name, p) __param_check(name, p, int)  static int param_set_mode(const char *val, struct kernel_param *kp);  static int param_get_mode(char *buffer, struct kernel_param *kp); -#define param_check_mode(name, p) __param_check(name, p, int)  /* Flag values, also controllable via /sys/module/apparmor/parameters   * We define special types as we want to do additional mediation. @@ -709,7 +706,7 @@ module_param_call(mode, param_set_mode, param_get_mode,  		  &aa_g_profile_mode, S_IRUSR | S_IWUSR);  /* Debug mode */ -int aa_g_debug; +bool aa_g_debug;  module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);  /* Audit mode */ @@ -720,7 +717,7 @@ module_param_call(audit, param_set_audit, param_get_audit,  /* Determines if audit header is included in audited messages.  This   * provides more context if the audit daemon is not running   */ -int aa_g_audit_header = 1; +bool aa_g_audit_header = 1;  module_param_named(audit_header, aa_g_audit_header, aabool,  		   S_IRUSR | S_IWUSR); @@ -728,12 +725,12 @@ module_param_named(audit_header, aa_g_audit_header, aabool,   * TODO: add in at boot loading of policy, which is the only way to   *       load policy, if lock_policy is set   */ -int aa_g_lock_policy; +bool aa_g_lock_policy;  module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy,  		   S_IRUSR | S_IWUSR);  /* Syscall logging mode */ -int aa_g_logsyscall; +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 */ @@ -743,18 +740,18 @@ 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.   */ -int aa_g_paranoid_load = 1; +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 unsigned int apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE; -module_param_named(enabled, apparmor_enabled, aabool, S_IRUSR); +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 = strict_strtoul(str, 0, &enabled); +	int error = kstrtoul(str, 0, &enabled);  	if (!error)  		apparmor_enabled = enabled ? 1 : 0;  	return 1; @@ -848,7 +845,7 @@ static int param_get_mode(char *buffer, struct kernel_param *kp)  	if (!apparmor_enabled)  		return -EINVAL; -	return sprintf(buffer, "%s", profile_mode_names[aa_g_profile_mode]); +	return sprintf(buffer, "%s", aa_profile_mode_names[aa_g_profile_mode]);  }  static int param_set_mode(const char *val, struct kernel_param *kp) @@ -863,8 +860,8 @@ static int param_set_mode(const char *val, struct kernel_param *kp)  	if (!val)  		return -EINVAL; -	for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) { -		if (strcmp(val, profile_mode_names[i]) == 0) { +	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;  		} @@ -892,7 +889,7 @@ static int __init set_init_cxt(void)  		return -ENOMEM;  	cxt->profile = aa_get_profile(root_ns->unconfined); -	cred->security = cxt; +	cred_cxt(cred) = cxt;  	return 0;  } @@ -921,8 +918,11 @@ static int __init apparmor_init(void)  	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 set_init_cxt_out; +		goto register_security_out;  	}  	/* Report that AppArmor successfully initialized */ @@ -936,9 +936,6 @@ static int __init apparmor_init(void)  	return error; -set_init_cxt_out: -	aa_free_task_context(current->real_cred->security); -  register_security_out:  	aa_free_root_ns(); diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 5cb4dc1f699..727eb4200d5 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -4,7 +4,7 @@   * This file contains AppArmor dfa based regular expression matching engine   *   * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * 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 @@ -23,6 +23,8 @@  #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) @@ -30,7 +32,7 @@   *   * Returns: pointer to table else NULL on failure   * - * NOTE: must be freed by kvfree (not kmalloc) + * NOTE: must be freed by kvfree (not kfree)   */  static struct table_header *unpack_table(char *blob, size_t bsize)  { @@ -57,7 +59,7 @@ static struct table_header *unpack_table(char *blob, size_t bsize)  	if (bsize < tsize)  		goto out; -	table = kvmalloc(tsize); +	table = kvzalloc(tsize);  	if (table) {  		*table = th;  		if (th.td_flags == YYTD_DATA8) @@ -137,8 +139,7 @@ static int verify_dfa(struct aa_dfa *dfa, int flags)  		for (i = 0; i < state_count; i++) {  			if (DEFAULT_TABLE(dfa)[i] >= state_count)  				goto out; -			/* TODO: do check that DEF state recursion terminates */ -			if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { +			if (base_idx(BASE_TABLE(dfa)[i]) + 255 >= trans_count) {  				printk(KERN_ERR "AppArmor DFA next/check upper "  				       "bounds error\n");  				goto out; @@ -194,8 +195,8 @@ void aa_dfa_free_kref(struct kref *kref)   * @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/apparmor.txt - * Assumes the dfa @blob stream has been aligned on a 8 byte boundry + * 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   */ @@ -314,7 +315,7 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,  		u8 *equiv = EQUIV_TABLE(dfa);  		/* default is direct to next state */  		for (; len; len--) { -			pos = base[state] + equiv[(u8) *str++]; +			pos = base_idx(base[state]) + equiv[(u8) *str++];  			if (check[pos] == state)  				state = next[pos];  			else @@ -323,7 +324,7 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,  	} else {  		/* default is direct to next state */  		for (; len; len--) { -			pos = base[state] + (u8) *str++; +			pos = base_idx(base[state]) + (u8) *str++;  			if (check[pos] == state)  				state = next[pos];  			else @@ -335,12 +336,12 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,  }  /** - * aa_dfa_next_state - traverse @dfa to find state @str stops at + * 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_next_state will match @str against the dfa and return the state it + * 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.   * @@ -349,5 +350,79 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,  unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,  			  const char *str)  { -	return aa_dfa_match_len(dfa, start, str, strlen(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 index 36cc0cc39e7..35b394a75d7 100644 --- a/security/apparmor/path.c +++ b/security/apparmor/path.c @@ -13,7 +13,6 @@   */  #include <linux/magic.h> -#include <linux/mnt_namespace.h>  #include <linux/mount.h>  #include <linux/namei.h>  #include <linux/nsproxy.h> @@ -57,33 +56,57 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)  static int d_namespace_path(struct path *path, char *buf, int buflen,  			    char **name, int flags)  { -	struct path root, tmp;  	char *res; -	int connected, error = 0; +	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; +	} -	/* Get the root we want to resolve too, released below */ +	/* resolve paths relative to chroot?*/  	if (flags & PATH_CHROOT_REL) { -		/* resolve paths relative to chroot */ +		struct path root;  		get_fs_root(current->fs, &root); +		res = __d_path(path, &root, buf, buflen); +		path_put(&root);  	} else { -		/* resolve paths relative to namespace */ -		root.mnt = current->nsproxy->mnt_ns->root; -		root.dentry = root.mnt->mnt_root; -		path_get(&root); +		res = d_absolute_path(path, buf, buflen); +		if (!our_mnt(path->mnt)) +			connected = 0;  	} -	tmp = root; -	res = __d_path(path, &tmp, buf, buflen); - -	*name = res;  	/* handle error conditions - and still allow a partial path to  	 * be returned.  	 */ -	if (IS_ERR(res)) { -		error = PTR_ERR(res); -		*name = buf; -		goto out; -	} +	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 @@ -97,10 +120,7 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,  			goto out;  	} -	/* Determine if the path is connected to the expected root */ -	connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt; - -	/* If the path is not connected, +	/* 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 @@ -112,29 +132,19 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,  	 *     namespace root.  	 */  	if (!connected) { -		/* is the disconnect path a sysctl? */ -		if (tmp.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 -			 */ -			error = prepend(name, *name - buf, "/proc", 5); -		} else if (!(flags & PATH_CONNECT_PATH) && +		if (!(flags & PATH_CONNECT_PATH) &&  			   !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) && -			     (tmp.mnt == current->nsproxy->mnt_ns->root && -			      tmp.dentry == tmp.mnt->mnt_root))) { +			     our_mnt(path->mnt))) {  			/* disconnected path, don't return pathname starting  			 * with '/'  			 */ -			error = -ESTALE; +			error = -EACCES;  			if (*res == '/')  				*name = res + 1;  		}  	}  out: -	path_put(&root); -  	return error;  } @@ -149,7 +159,7 @@ out:   * Returns: %0 else error on failure   */  static int get_name_to_buffer(struct path *path, int flags, char *buffer, -			      int size, char **name) +			      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); @@ -161,15 +171,27 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer,  		 */  		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_get_name - compute the pathname of a file + * 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 @@ -182,7 +204,8 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer,   *   * Returns: %0 else error code if could retrieve name   */ -int aa_get_name(struct path *path, int flags, char **buffer, const char **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; @@ -196,7 +219,7 @@ int aa_get_name(struct path *path, int flags, char **buffer, const char **name)  		if (!buf)  			return -ENOMEM; -		error = get_name_to_buffer(path, flags, buf, size, &str); +		error = get_name_to_buffer(path, flags, buf, size, &str, info);  		if (error != -ENAMETOOLONG)  			break; @@ -204,6 +227,7 @@ int aa_get_name(struct path *path, int flags, char **buffer, const char **name)  		size <<= 1;  		if (size > aa_g_path_max)  			return -ENAMETOOLONG; +		*info = NULL;  	}  	*buffer = buf;  	*name = str; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4f0eadee78b..705c2879d3a 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -87,16 +87,16 @@  #include "include/policy.h"  #include "include/policy_unpack.h"  #include "include/resource.h" -#include "include/sid.h"  /* root profile namespace */  struct aa_namespace *root_ns; -const char *profile_mode_names[] = { +const char *const aa_profile_mode_names[] = {  	"enforce",  	"complain",  	"kill", +	"unconfined",  };  /** @@ -142,7 +142,6 @@ static bool policy_init(struct aa_policy *policy, const char *prefix,  	policy->name = (char *)hname_tail(policy->hname);  	INIT_LIST_HEAD(&policy->list);  	INIT_LIST_HEAD(&policy->profiles); -	kref_init(&policy->count);  	return 1;  } @@ -154,13 +153,13 @@ static bool policy_init(struct aa_policy *policy, const char *prefix,  static void policy_destroy(struct aa_policy *policy)  {  	/* still contains profiles -- invalid */ -	if (!list_empty(&policy->profiles)) { +	if (on_list_rcu(&policy->profiles)) {  		AA_ERROR("%s: internal error, "  			 "policy '%s' still contains profiles\n",  			 __func__, policy->name);  		BUG();  	} -	if (!list_empty(&policy->list)) { +	if (on_list_rcu(&policy->list)) {  		AA_ERROR("%s: internal error, policy '%s' still on list\n",  			 __func__, policy->name);  		BUG(); @@ -175,7 +174,7 @@ static void policy_destroy(struct aa_policy *policy)   * @head: list to search  (NOT NULL)   * @name: name to search for  (NOT NULL)   * - * Requires: correct locks for the @head list be held + * Requires: rcu_read_lock be held   *   * Returns: unrefcounted policy that match @name or NULL if not found   */ @@ -183,7 +182,7 @@ static struct aa_policy *__policy_find(struct list_head *head, const char *name)  {  	struct aa_policy *policy; -	list_for_each_entry(policy, head, list) { +	list_for_each_entry_rcu(policy, head, list) {  		if (!strcmp(policy->name, name))  			return policy;  	} @@ -196,7 +195,7 @@ static struct aa_policy *__policy_find(struct list_head *head, const char *name)   * @str: string to search for  (NOT NULL)   * @len: length of match required   * - * Requires: correct locks for the @head list be held + * Requires: rcu_read_lock be held   *   * Returns: unrefcounted policy that match @str or NULL if not found   * @@ -208,7 +207,7 @@ static struct aa_policy *__policy_strn_find(struct list_head *head,  {  	struct aa_policy *policy; -	list_for_each_entry(policy, head, list) { +	list_for_each_entry_rcu(policy, head, list) {  		if (aa_strneq(policy->name, str, len))  			return policy;  	} @@ -285,23 +284,21 @@ static struct aa_namespace *alloc_namespace(const char *prefix,  		goto fail_ns;  	INIT_LIST_HEAD(&ns->sub_ns); -	rwlock_init(&ns->lock); +	mutex_init(&ns->lock);  	/* released by free_namespace */  	ns->unconfined = aa_alloc_profile("unconfined");  	if (!ns->unconfined)  		goto fail_unconfined; -	ns->unconfined->sid = aa_alloc_sid(); -	ns->unconfined->flags = PFLAG_UNCONFINED | PFLAG_IX_ON_NAME_ERROR | -	    PFLAG_IMMUTABLE; +	ns->unconfined->flags = PFLAG_IX_ON_NAME_ERROR | +		PFLAG_IMMUTABLE | PFLAG_NS_COUNT; +	ns->unconfined->mode = APPARMOR_UNCONFINED; -	/* -	 * released by free_namespace, however __remove_namespace breaks -	 * the cyclic references (ns->unconfined, and unconfined->ns) and -	 * replaces with refs to parent namespace unconfined -	 */ -	ns->unconfined->ns = aa_get_namespace(ns); +	/* ns and ns->unconfined share ns->unconfined refcount */ +	ns->unconfined->ns = ns; + +	atomic_set(&ns->uniq_null, 0);  	return ns; @@ -327,30 +324,19 @@ static void free_namespace(struct aa_namespace *ns)  	policy_destroy(&ns->base);  	aa_put_namespace(ns->parent); -	if (ns->unconfined && ns->unconfined->ns == ns) -		ns->unconfined->ns = NULL; - -	aa_put_profile(ns->unconfined); +	ns->unconfined->ns = NULL; +	aa_free_profile(ns->unconfined);  	kzfree(ns);  }  /** - * aa_free_namespace_kref - free aa_namespace by kref (see aa_put_namespace) - * @kr: kref callback for freeing of a namespace  (NOT NULL) - */ -void aa_free_namespace_kref(struct kref *kref) -{ -	free_namespace(container_of(kref, struct aa_namespace, base.count)); -} - -/**   * __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: ns lock be held + * Requires: rcu_read_lock be held   */  static struct aa_namespace *__aa_find_namespace(struct list_head *head,  						const char *name) @@ -373,9 +359,9 @@ struct aa_namespace *aa_find_namespace(struct aa_namespace *root,  {  	struct aa_namespace *ns = NULL; -	read_lock(&root->lock); +	rcu_read_lock();  	ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); -	read_unlock(&root->lock); +	rcu_read_unlock();  	return ns;  } @@ -392,7 +378,7 @@ static struct aa_namespace *aa_prepare_namespace(const char *name)  	root = aa_current_profile()->ns; -	write_lock(&root->lock); +	mutex_lock(&root->lock);  	/* if name isn't specified the profile is loaded to the current ns */  	if (!name) { @@ -405,31 +391,23 @@ static struct aa_namespace *aa_prepare_namespace(const char *name)  	/* released by caller */  	ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));  	if (!ns) { -		/* namespace not found */ -		struct aa_namespace *new_ns; -		write_unlock(&root->lock); -		new_ns = alloc_namespace(root->base.hname, name); -		if (!new_ns) -			return NULL; -		write_lock(&root->lock); -		/* test for race when new_ns was allocated */ -		ns = __aa_find_namespace(&root->sub_ns, name); -		if (!ns) { -			/* add parent ref */ -			new_ns->parent = aa_get_namespace(root); - -			list_add(&new_ns->base.list, &root->sub_ns); -			/* add list ref */ -			ns = aa_get_namespace(new_ns); -		} else { -			/* raced so free the new one */ -			free_namespace(new_ns); -			/* get reference on namespace */ -			aa_get_namespace(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: -	write_unlock(&root->lock); +	mutex_unlock(&root->lock);  	/* return ref */  	return ns; @@ -447,7 +425,7 @@ out:  static void __list_add_profile(struct list_head *list,  			       struct aa_profile *profile)  { -	list_add(&profile->base.list, list); +	list_add_rcu(&profile->base.list, list);  	/* get list reference */  	aa_get_profile(profile);  } @@ -466,50 +444,8 @@ static void __list_add_profile(struct list_head *list,   */  static void __list_remove_profile(struct aa_profile *profile)  { -	list_del_init(&profile->base.list); -	if (!(profile->flags & PFLAG_NO_LIST_REF)) -		/* release list reference */ -		aa_put_profile(profile); -} - -/** - * __replace_profile - replace @old with @new on a list - * @old: profile to be replaced  (NOT NULL) - * @new: profile to replace @old with  (NOT NULL) - * - * 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) -{ -	struct aa_policy *policy; -	struct aa_profile *child, *tmp; - -	if (old->parent) -		policy = &old->parent->base; -	else -		policy = &old->ns->base; - -	/* released when @new is freed */ -	new->parent = aa_get_profile(old->parent); -	new->ns = aa_get_namespace(old->ns); -	new->sid = old->sid; -	__list_add_profile(&policy->profiles, new); -	/* inherit children */ -	list_for_each_entry_safe(child, tmp, &old->base.profiles, base.list) { -		aa_put_profile(child->parent); -		child->parent = aa_get_profile(new); -		/* list refcount transferred to @new*/ -		list_move(&child->base.list, &new->base.profiles); -	} - -	/* released by free_profile */ -	old->replacedby = aa_get_profile(new); -	__list_remove_profile(old); +	list_del_rcu(&profile->base.list); +	aa_put_profile(profile);  }  static void __profile_list_release(struct list_head *head); @@ -525,7 +461,8 @@ static void __remove_profile(struct aa_profile *profile)  	/* release any children lists first */  	__profile_list_release(&profile->base.profiles);  	/* released by free_profile */ -	profile->replacedby = aa_get_profile(profile->ns->unconfined); +	__aa_update_replacedby(profile, profile->ns->unconfined); +	__aa_fs_profile_rmdir(profile);  	__list_remove_profile(profile);  } @@ -553,14 +490,17 @@ static void destroy_namespace(struct aa_namespace *ns)  	if (!ns)  		return; -	write_lock(&ns->lock); +	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); -	write_unlock(&ns->lock); +	if (ns->parent) +		__aa_update_replacedby(ns->unconfined, ns->parent->unconfined); +	__aa_fs_namespace_rmdir(ns); +	mutex_unlock(&ns->lock);  }  /** @@ -571,25 +511,9 @@ static void destroy_namespace(struct aa_namespace *ns)   */  static void __remove_namespace(struct aa_namespace *ns)  { -	struct aa_profile *unconfined = ns->unconfined; -  	/* remove ns from namespace list */ -	list_del_init(&ns->base.list); - -	/* -	 * break the ns, unconfined profile cyclic reference and forward -	 * all new unconfined profiles requests to the parent namespace -	 * This will result in all confined tasks that have a profile -	 * being removed, inheriting the parent->unconfined profile. -	 */ -	if (ns->parent) -		ns->unconfined = aa_get_profile(ns->parent->unconfined); - +	list_del_rcu(&ns->base.list);  	destroy_namespace(ns); - -	/* release original ns->unconfined ref */ -	aa_put_profile(unconfined); -	/* release ns->base.list ref, from removal above */  	aa_put_namespace(ns);  } @@ -635,6 +559,84 @@ void __init aa_free_root_ns(void)  	 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) @@ -650,13 +652,23 @@ struct aa_profile *aa_alloc_profile(const char *hname)  	if (!profile)  		return NULL; -	if (!policy_init(&profile->base, NULL, hname)) { -		kzfree(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;  }  /** @@ -665,7 +677,7 @@ struct aa_profile *aa_alloc_profile(const char *hname)   * @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-sid. + * 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 @@ -677,96 +689,39 @@ struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat)  {  	struct aa_profile *profile = NULL;  	char *name; -	u32 sid = aa_alloc_sid(); +	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, sid); +	sprintf(name, "%s//null-%x", parent->base.hname, uniq);  	profile = aa_alloc_profile(name);  	kfree(name);  	if (!profile)  		goto fail; -	profile->sid = sid;  	profile->mode = APPARMOR_COMPLAIN;  	profile->flags = PFLAG_NULL;  	if (hat)  		profile->flags |= PFLAG_HAT;  	/* released on free_profile */ -	profile->parent = aa_get_profile(parent); +	rcu_assign_pointer(profile->parent, aa_get_profile(parent));  	profile->ns = aa_get_namespace(parent->ns); -	write_lock(&profile->ns->lock); +	mutex_lock(&profile->ns->lock);  	__list_add_profile(&parent->base.profiles, profile); -	write_unlock(&profile->ns->lock); +	mutex_unlock(&profile->ns->lock);  	/* refcount released by caller */  	return profile;  fail: -	aa_free_sid(sid);  	return NULL;  } -/** - * 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. - */ -static void free_profile(struct aa_profile *profile) -{ -	AA_DEBUG("%s(%p)\n", __func__, profile); - -	if (!profile) -		return; - -	if (!list_empty(&profile->base.list)) { -		AA_ERROR("%s: internal error, " -			 "profile '%s' still on ns list\n", -			 __func__, profile->base.name); -		BUG(); -	} - -	/* free children profiles */ -	policy_destroy(&profile->base); -	aa_put_profile(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); - -	aa_free_sid(profile->sid); -	aa_put_dfa(profile->xmatch); - -	aa_put_profile(profile->replacedby); - -	kzfree(profile); -} - -/** - * 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, -					    base.count); - -	free_profile(p); -} -  /* TODO: profile accounting - setup in remove */  /** @@ -774,7 +729,7 @@ void aa_free_profile_kref(struct kref *kref)   * @head: list to search  (NOT NULL)   * @name: name of profile (NOT NULL)   * - * Requires: ns lock protecting list be held + * Requires: rcu_read_lock be held   *   * Returns: unrefcounted profile ptr, or NULL if not found   */ @@ -789,7 +744,7 @@ static struct aa_profile *__find_child(struct list_head *head, const char *name)   * @name: name of profile (NOT NULL)   * @len: length of @name substring to match   * - * Requires: ns lock protecting list be held + * Requires: rcu_read_lock be held   *   * Returns: unrefcounted profile ptr, or NULL if not found   */ @@ -810,9 +765,9 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name)  {  	struct aa_profile *profile; -	read_lock(&parent->ns->lock); +	rcu_read_lock();  	profile = aa_get_profile(__find_child(&parent->base.profiles, name)); -	read_unlock(&parent->ns->lock); +	rcu_read_unlock();  	/* refcount released by caller */  	return profile; @@ -827,7 +782,7 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name)   * that matches hname does not need to exist, in general this   * is used to load a new profile.   * - * Requires: ns->lock be held + * Requires: rcu_read_lock be held   *   * Returns: unrefcounted policy or NULL if not found   */ @@ -859,7 +814,7 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns,   * @base: base list to start looking up profile name from  (NOT NULL)   * @hname: hierarchical profile name  (NOT NULL)   * - * Requires: ns->lock be held + * Requires: rcu_read_lock be held   *   * Returns: unrefcounted profile pointer or NULL if not found   * @@ -898,9 +853,15 @@ struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname)  {  	struct aa_profile *profile; -	read_lock(&ns->lock); -	profile = aa_get_profile(__lookup_profile(&ns->base, hname)); -	read_unlock(&ns->lock); +	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; @@ -930,26 +891,6 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,  }  /** - * __add_new_profile - simple wrapper around __list_add_profile - * @ns: namespace that profile is being added to  (NOT NULL) - * @policy: the policy container to add the profile to  (NOT NULL) - * @profile: profile to add  (NOT NULL) - * - * add a profile to a list and do other required basic allocations - */ -static void __add_new_profile(struct aa_namespace *ns, struct aa_policy *policy, -			      struct aa_profile *profile) -{ -	if (policy != &ns->base) -		/* released on profile replacement or free_profile */ -		profile->parent = aa_get_profile((struct aa_profile *) policy); -	__list_add_profile(&policy->profiles, profile); -	/* released on free_profile */ -	profile->sid = aa_alloc_sid(); -	profile->ns = aa_get_namespace(ns); -} - -/**   * aa_audit_policy - Do auditing of policy changes   * @op: policy operation being performed   * @gfp: memory allocation flags @@ -963,11 +904,13 @@ static int audit_policy(int op, gfp_t gfp, const char *name, const char *info,  			int error)  {  	struct common_audit_data sa; -	COMMON_AUDIT_DATA_INIT(&sa, NONE); -	sa.aad.op = op; -	sa.aad.name = name; -	sa.aad.info = info; -	sa.aad.error = error; +	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); @@ -995,6 +938,121 @@ bool aa_may_manage_policy(int op)  	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) @@ -1009,21 +1067,17 @@ bool aa_may_manage_policy(int op)   */  ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)  { -	struct aa_policy *policy; -	struct aa_profile *old_profile = NULL, *new_profile = NULL; -	struct aa_profile *rename_profile = NULL; -	struct aa_namespace *ns = NULL;  	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 */ -	new_profile = aa_unpack(udata, size, &ns_name); -	if (IS_ERR(new_profile)) { -		error = PTR_ERR(new_profile); -		new_profile = NULL; -		goto fail; -	} +	error = aa_unpack(udata, size, &lh, &ns_name); +	if (error) +		goto out;  	/* released below */  	ns = aa_prepare_namespace(ns_name); @@ -1034,77 +1088,140 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)  		goto fail;  	} -	name = new_profile->base.hname; +	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; +		} -	write_lock(&ns->lock); -	/* no ref on policy only use inside lock */ -	policy = __lookup_parent(ns, new_profile->base.hname); +		/* released when @new is freed */ +		ent->new->ns = aa_get_namespace(ns); -	if (!policy) { -		info = "parent does not exist"; -		error = -ENOENT; -		goto audit; +		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)); +		}  	} -	old_profile = __find_child(&policy->profiles, new_profile->base.name); -	/* released below */ -	aa_get_profile(old_profile); +	/* create new fs entries for introspection if needed */ +	list_for_each_entry(ent, &lh, list) { +		if (ent->old) { +			/* inherit old interface files */ -	if (new_profile->rename) { -		rename_profile = __lookup_profile(&ns->base, -						  new_profile->rename); -		/* released below */ -		aa_get_profile(rename_profile); +			/* 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 (!rename_profile) { -			info = "profile to rename does not exist"; -			name = new_profile->rename; -			error = -ENOENT; -			goto audit; +		if (error) { +			info = "failed to create "; +			goto fail_lock;  		}  	} -	error = replacement_allowed(old_profile, noreplace, &info); -	if (error) -		goto audit; - -	error = replacement_allowed(rename_profile, noreplace, &info); -	if (error) -		goto audit; - -audit: -	if (!old_profile && !rename_profile) -		op = OP_PROF_LOAD; - -	error = audit_policy(op, GFP_ATOMIC, name, info, error); - -	if (!error) { -		if (rename_profile) -			__replace_profile(rename_profile, new_profile); -		if (old_profile) { -			/* when there are both rename and old profiles -			 * inherit old profiles sid -			 */ -			if (rename_profile) -				aa_free_sid(new_profile->sid); -			__replace_profile(old_profile, new_profile); +	/* 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);  		} -		if (!(old_profile || rename_profile)) -			__add_new_profile(ns, policy, new_profile); +		aa_load_ent_free(ent);  	} -	write_unlock(&ns->lock); +	mutex_unlock(&ns->lock);  out:  	aa_put_namespace(ns); -	aa_put_profile(rename_profile); -	aa_put_profile(old_profile); -	aa_put_profile(new_profile); +  	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;  } @@ -1138,14 +1255,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)  	if (fqname[0] == ':') {  		char *ns_name;  		name = aa_split_fqname(fqname, &ns_name); -		if (ns_name) { -			/* released below */ -			ns = aa_find_namespace(root, ns_name); -			if (!ns) { -				info = "namespace does not exist"; -				error = -ENOENT; -				goto fail; -			} +		/* released below */ +		ns = aa_find_namespace(root, ns_name); +		if (!ns) { +			info = "namespace does not exist"; +			error = -ENOENT; +			goto fail;  		}  	} else  		/* released below */ @@ -1153,12 +1268,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)  	if (!name) {  		/* remove namespace - can only happen if fqname[0] == ':' */ -		write_lock(&ns->parent->lock); +		mutex_lock(&ns->parent->lock);  		__remove_namespace(ns); -		write_unlock(&ns->parent->lock); +		mutex_unlock(&ns->parent->lock);  	} else {  		/* remove profile */ -		write_lock(&ns->lock); +		mutex_lock(&ns->lock);  		profile = aa_get_profile(__lookup_profile(&ns->base, name));  		if (!profile) {  			error = -ENOENT; @@ -1167,7 +1282,7 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)  		}  		name = profile->base.hname;  		__remove_profile(profile); -		write_unlock(&ns->lock); +		mutex_unlock(&ns->lock);  	}  	/* don't fail removal if audit fails */ @@ -1177,7 +1292,7 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)  	return size;  fail_ns_lock: -	write_unlock(&ns->lock); +	mutex_unlock(&ns->lock);  	aa_put_namespace(ns);  fail: diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index eb3700e9fd3..a689f10930b 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -12,8 +12,8 @@   * 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/apparmor.txt + * 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.   */ @@ -24,10 +24,10 @@  #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" -#include "include/sid.h"  /*   * The AppArmor interface treats data as a type byte followed by the @@ -70,13 +70,13 @@ struct aa_ext {  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; +	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); +	if (sa->aad->iface.pos) +		audit_log_format(ab, " offset=%ld", sa->aad->iface.pos);  }  /** @@ -84,7 +84,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)   * @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 (NOT NULL) + * @e: buffer position info   * @error: error code   *   * Returns: %0 or error @@ -94,12 +94,15 @@ static int audit_iface(struct aa_profile *new, const char *name,  {  	struct aa_profile *profile = __aa_current_profile();  	struct common_audit_data sa; -	COMMON_AUDIT_DATA_INIT(&sa, NONE); -	sa.aad.iface.pos = e->pos - e->start; -	sa.aad.iface.target = new; -	sa.aad.name = name; -	sa.aad.info = info; -	sa.aad.error = error; +	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); @@ -287,6 +290,9 @@ static int unpack_strdup(struct aa_ext *e, char **string, const char *name)  	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) @@ -328,8 +334,10 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)  		/*  		 * 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; +		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); @@ -359,7 +367,7 @@ fail:   * @e: serialized data extent information  (NOT NULL)   * @profile: profile to add the accept table to (NOT NULL)   * - * Returns: 1 if table succesfully unpacked + * Returns: 1 if table successfully unpacked   */  static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)  { @@ -381,11 +389,11 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)  		profile->file.trans.size = size;  		for (i = 0; i < size; i++) {  			char *str; -			int c, j, size = unpack_strdup(e, &str, NULL); +			int c, j, size2 = unpack_strdup(e, &str, NULL);  			/* unpack_strdup verifies that the last character is  			 * null termination byte.  			 */ -			if (!size) +			if (!size2)  				goto fail;  			profile->file.trans.table[i] = str;  			/* verify that name doesn't start with space */ @@ -393,7 +401,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)  				goto fail;  			/* count internal #  of internal \0 */ -			for (c = j = 0; j < size - 2; j++) { +			for (c = j = 0; j < size2 - 2; j++) {  				if (!str[j])  					c++;  			} @@ -440,11 +448,11 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)  		if (size > RLIM_NLIMITS)  			goto fail;  		for (i = 0; i < size; i++) { -			u64 tmp = 0; +			u64 tmp2 = 0;  			int a = aa_map_resource(i); -			if (!unpack_u64(e, &tmp, NULL)) +			if (!unpack_u64(e, &tmp2, NULL))  				goto fail; -			profile->rlimits.limits[a].rlim_max = tmp; +			profile->rlimits.limits[a].rlim_max = tmp2;  		}  		if (!unpack_nameX(e, AA_ARRAYEND, NULL))  			goto fail; @@ -468,7 +476,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)  {  	struct aa_profile *profile = NULL;  	const char *name = NULL; -	int error = -EPROTO; +	int i, error = -EPROTO;  	kernel_cap_t tmpcap;  	u32 tmp; @@ -485,6 +493,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)  	/* 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)) { @@ -504,12 +515,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)  		goto fail;  	if (!unpack_u32(e, &tmp, NULL))  		goto fail; -	if (tmp) +	if (tmp & PACKED_FLAG_HAT)  		profile->flags |= PFLAG_HAT;  	if (!unpack_u32(e, &tmp, NULL))  		goto fail; -	if (tmp) +	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) @@ -554,11 +569,35 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)  			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)) { @@ -585,7 +624,7 @@ fail:  	else if (!name)  		name = "unknown";  	audit_iface(profile, name, "failed to unpack profile", e, error); -	aa_put_profile(profile); +	aa_free_profile(profile);  	return ERR_PTR(error);  } @@ -593,29 +632,41 @@ fail:  /**   * 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, const char **ns) +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")) { -		audit_iface(NULL, NULL, "invalid profile format", e, error); -		return error; -	} +		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; +		/* 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, ns, "namespace")) -		*ns = NULL; +	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;  } @@ -664,18 +715,40 @@ static int verify_profile(struct aa_profile *profile)  	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 data loaded from user space + * 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 or ERR_PTR + * 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 else error pointer if fails to unpack + * Returns: profile(s) on @lh else error pointer if fails to unpack   */ -struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns) +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 = { @@ -684,20 +757,49 @@ struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns)  		.pos = udata,  	}; -	error = verify_header(&e, ns); -	if (error) -		return ERR_PTR(error); +	*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; -	profile = unpack_profile(&e); -	if (IS_ERR(profile)) -		return profile; +		error = aa_calc_profile_hash(profile, e.version, start, +					     e.pos - start); +		if (error) +			goto fail_profile; -	error = verify_profile(profile); -	if (error) { -		aa_put_profile(profile); -		profile = ERR_PTR(error); +		ent = aa_load_ent_alloc(); +		if (!ent) { +			error = -ENOMEM; +			goto fail_profile; +		} + +		ent->new = profile; +		list_add_tail(&ent->list, lh);  	} -	/* return refcount */ -	return profile; +	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 index 04a2cf8d1b6..b125acc9aa2 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -16,6 +16,7 @@  #include "include/context.h"  #include "include/policy.h"  #include "include/domain.h" +#include "include/procattr.h"  /** @@ -36,7 +37,7 @@ 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 = profile_mode_names[profile->mode]; +	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; @@ -162,9 +163,3 @@ int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test)  	name = aa_split_fqname(fqname, &ns_name);  	return aa_change_profile(ns_name, name, onexec, test);  } - -int aa_setprocattr_permipc(char *fqname) -{ -	/* TODO: add ipc permission querying */ -	return -ENOTSUPP; -} diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index a4136c10b1c..748bf0ca6c9 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -15,6 +15,7 @@  #include <linux/audit.h>  #include "include/audit.h" +#include "include/context.h"  #include "include/resource.h"  #include "include/policy.h" @@ -23,13 +24,18 @@   */  #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); +			 rlim_names[sa->aad->rlim.rlim], sa->aad->rlim.max);  }  /** @@ -45,12 +51,14 @@ 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,}; -	COMMON_AUDIT_DATA_INIT(&sa, NONE); -	sa.aad.op = OP_SETRLIMIT, -	sa.aad.rlim.rlim = resource; -	sa.aad.rlim.max = value; -	sa.aad.error = error; +	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);  } @@ -83,17 +91,25 @@ int aa_map_resource(int resource)  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) -	 * processes.  AppArmor rules currently have the implicit assumption -	 * that the task is setting the resource of the current process +	 * profiles.  AppArmor rules currently have the implicit assumption +	 * that the task is setting the resource of a task confined with +	 * the same profile.  	 */ -	if ((task != current->group_leader) || +	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);  }  | 
