diff options
Diffstat (limited to 'security/apparmor/policy.c')
| -rw-r--r-- | security/apparmor/policy.c | 711 | 
1 files changed, 413 insertions, 298 deletions
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:  | 
