[apparmor] [PATCH 18/36] apparmor: add basic support for implicit labeling of files

John Johansen john.johansen at canonical.com
Wed May 1 21:31:03 UTC 2013


The labeling of files is implied by the set of rules and profiles.
Add the ability to set implicit labels on files to reduce the number
of path and rule lookups that are needed.

Signed-off-by: John Johansen <john.johansen at canonical.com>
---
 security/apparmor/Kconfig          |  11 +
 security/apparmor/Makefile         |   2 +-
 security/apparmor/domain.c         |   4 +-
 security/apparmor/include/label.h  | 215 +++++++++++++
 security/apparmor/include/policy.h |  43 +--
 security/apparmor/label.c          | 611 +++++++++++++++++++++++++++++++++++++
 security/apparmor/policy.c         |  96 +++---
 security/apparmor/policy_unpack.c  |   7 +-
 8 files changed, 909 insertions(+), 80 deletions(-)
 create mode 100644 security/apparmor/include/label.h
 create mode 100644 security/apparmor/label.c

diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
index 9ede3b1..17b812c 100644
--- a/security/apparmor/Kconfig
+++ b/security/apparmor/Kconfig
@@ -30,6 +30,17 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
 
 	  If you are unsure how to answer this question, answer 1.
 
+config SECURITY_APPARMOR_STATS
+	bool "enable debug statitics"
+	depends on SECURITY_APPARMOR
+	select APPARMOR_LABEL_STATS
+	default n
+	help
+	  This enables keeping statistics on various internal structures
+	  and functions in apparmor.
+
+	  If you are unsure how to answer this question, answer N.
+
 config SECURITY_APPARMOR_UNCONFINED_INIT
 	bool "Set init to unconfined on boot"
 	depends on SECURITY_APPARMOR
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 5706b74..c6fc5e4 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
 
 apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
               path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
-              resource.o sid.o file.o
+              resource.o sid.o file.o label.o
 
 clean-files := capability_names.h rlim_names.h
 
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index e400800..ccf19eb 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -145,7 +145,7 @@ static struct aa_profile *__attach_match(const char *name,
 	struct aa_profile *profile, *candidate = NULL;
 
 	list_for_each_entry_rcu(profile, head, base.list) {
-		if (profile->flags & PFLAG_NULL)
+		if (profile->label.flags & FLAG_NULL)
 			continue;
 		if (profile->xmatch && profile->xmatch_len > len) {
 			unsigned int state = aa_dfa_match(profile->xmatch,
@@ -372,7 +372,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
 			     &name, &info);
 	if (error) {
 		if (unconfined(profile) ||
-		    (profile->flags & PFLAG_IX_ON_NAME_ERROR))
+		    (profile->label.flags & FLAG_IX_ON_NAME_ERROR))
 			error = 0;
 		name = bprm->filename;
 		goto audit;
diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h
new file mode 100644
index 0000000..a471d0b
--- /dev/null
+++ b/security/apparmor/include/label.h
@@ -0,0 +1,215 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor label 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 __AA_LABEL_H
+#define __AA_LABEL_H
+
+#include <linux/atomic.h>
+#include <linux/audit.h>
+#include <linux/rbtree.h>
+#include <linux/rcupdate.h>
+
+#include "apparmor.h"
+
+struct aa_namespace;
+
+struct labelset_stats {
+	atomic_t sread;
+	atomic_t fread;
+	atomic_t msread;
+	atomic_t mfread;
+
+	atomic_t insert;
+	atomic_t existing;
+	atomic_t minsert;
+	atomic_t mexisting;
+
+	atomic_t invalid;		/* outstanding invalid */
+};
+
+struct label_stats {
+	struct labelset_stats set_stats;
+
+	atomic_t allocated;
+	atomic_t failed;
+	atomic_t freed;
+
+	atomic_t printk_name_alloc;
+	atomic_t printk_name_fail;
+	atomic_t seq_print_name_alloc;
+	atomic_t seq_print_name_fail;
+	atomic_t audit_name_alloc;
+	atomic_t audit_name_fail;
+};
+
+
+#ifdef AA_LABEL_STATS
+#define labelstats_inc(X) atomic_inc(stats.(X))
+#define labelstats_dec(X) atomic_dec(stats.(X))
+#define labelsetstats_inc(LS, X)		\
+	do {					\
+		labelstats_inc(set_stats.##X);	\
+		atomic_inc((LS)->stats.(X));	\
+	} while (0)
+#define labelsetstats_dec(LS, X)		\
+	do {					\
+		labelstats_dec(set_stats.##X);	\
+		atomic_dec((LS)->stats.(X));	\
+	} while (0)
+#else
+#define labelstats_inc(X)
+#define labelstats_dec(X)
+#define labelsetstats_inc(LS, X)
+#define labelsetstats_dec(LS, X)
+#endif
+#define labelstats_init(X)
+
+/* struct aa_labelset - set of labels for a namespace
+ *
+ * Labels within the label set do not have a ref count and only exist
+ * within the set as long a refcount is held.  Once a labels last
+ * refcount is put it is removed from the set.
+ */
+struct aa_labelset {
+	rwlock_t lock;
+
+	struct rb_root root;
+
+	/* stats */
+#ifdef APPARMOR_LABEL_STATS
+	struct labelset_stats stats;
+#endif
+
+};
+
+#define __labelset_for_each(LS, N) \
+	for((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N))
+
+void aa_labelset_destroy(struct aa_labelset *ls);
+void aa_labelset_init(struct aa_labelset *ls);
+
+
+enum label_flags {
+	FLAG_HAT = 1,			/* profile is a hat */
+	FLAG_UNCONFINED = 2,		/* label unconfined only if all
+					 * constituant profiles unconfined */
+	FLAG_NULL = 4,			/* profile is null learning profile */
+	FLAG_IX_ON_NAME_ERROR = 8,	/* fallback to ix on name lookup fail */
+	FLAG_IMMUTABLE = 0x10,		/* don't allow changes/replacement */
+	FLAG_USER_DEFINED = 0x20,	/* user based profile - lower privs */
+	FLAG_NO_LIST_REF = 0x40,	/* list doesn't keep profile ref */
+	FLAG_NS_COUNT = 0x80,		/* carries NS ref count */
+	FLAG_IN_TREE = 0x100,		/* label is in tree */
+	FLAG_PROFILE = 0x200,		/* label is a profile */
+	FALG_EXPLICIT = 0x400,		/* explict static label */
+	FLAG_INVALID = 0x800,		/* replaced/removed */
+	FLAG_RENAMED = 0x1000,		/* label has renaming in it */
+	FLAG_REVOKED = 0x2000,		/* label has revocation in it */
+
+	/* These flags must correspond with PATH_flags */
+	FLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
+};
+
+/* struct aa_label - lazy labeling struct
+ * @count: ref count of active users
+ * @node: rbtree position
+ * @rcu: rcu callback struct
+ * @name: text representation of the label (MAYBE_NULL)
+ * @flags: invalid and other flags - values may change under label set lock
+ * @sid: sid that references this label
+ * @size: number of entries in @ent[]
+ * @ent: set of profiles for label, actual size determined by @size
+ */
+struct aa_label {
+	struct kref count;
+	struct rb_node node;
+	struct rcu_head rcu;
+	__counted char *hname;
+	long flags;
+	u32 sid;
+	int size;
+	struct aa_profile *ent[1];
+};
+
+#define label_isprofile(X) ((X)->flags & FLAG_PROFILE)
+#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED)
+#define label_invalid(X) ((X)->flags & FLAG_INVALID)
+#define __label_invalidate(X) do {	   \
+	labelsetstats_inc(labels_set(X), invalid); \
+	((X)->flags |= FLAG_INVALID);	   \
+} while (0)
+#define labels_last(X) ((X)->ent[(X)->size - 1])
+#define labels_ns(X) (labels_last(X)->ns)
+#define labels_set(X) (&labels_ns(X)->labels)
+#define labels_profile(X) ((X)->ent[0])
+
+int aa_label_next_confined(struct aa_label *l, int i);
+
+/* for each profile in a label */
+#define label_for_each(I, L, P)				\
+	for ((I) = 0;					\
+	     (I) < (L)->size && ((P) = (L)->ent[(I)]);	\
+	     ++(I))
+
+/* for each profile that is enforcing confinement in a label */
+#define label_for_each_confined(I, L, P)		\
+	for ((I) = aa_label_next_confined((L), 0);	\
+	     (I) < (L)->size && ((P) = (L)->ent[(I)]);	\
+	     (I) = aa_label_next_confined((L), I + 1))
+
+
+void aa_labelset_destroy(struct aa_labelset *ls);
+void aa_labelset_init(struct aa_labelset *ls);
+void __aa_labelset_invalidate_all(struct aa_namespace *ns,
+				  struct aa_profile *p);
+
+void aa_label_destroy(struct aa_label *label);
+void aa_label_free(struct aa_label *label);
+void aa_label_kref(struct kref *kref);
+bool aa_label_init(struct aa_label *label, int size);
+struct aa_label *aa_label_alloc(int size, gfp_t gfp);
+
+bool aa_label_remove(struct aa_labelset *ls, struct aa_label *label);
+struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l);
+bool aa_label_replace(struct aa_labelset *ls, struct aa_label *old,
+		      struct aa_label *new);
+bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old,
+			  struct aa_label *new);
+
+struct aa_label *aa_label_find(struct aa_labelset *ls, struct aa_label *l);
+
+
+
+static inline struct aa_label *aa_get_label(struct aa_label *l)
+{
+	if (l)
+		kref_get(&(l->count));
+
+	return l;
+}
+
+static inline struct aa_label *aa_get_label_not0(struct aa_label *l)
+{
+	if (l && kref_get_not0(&l->count))
+		return l;
+
+	return NULL;
+}
+
+static inline void aa_put_label(struct aa_label *l)
+{
+	if (l)
+		kref_put(&l->count, aa_label_kref);
+}
+
+#endif /* __AA_LABEL_H */
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index 0b247b3..0a29372 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -27,6 +27,7 @@
 #include "capability.h"
 #include "domain.h"
 #include "file.h"
+#include "label.h"
 #include "resource.h"
 
 extern const char *const aa_profile_mode_names[];
@@ -40,9 +41,9 @@ extern const char *const aa_profile_mode_names[];
 
 #define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
 
-#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT)
+#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
 
-#define PROFILE_INVALID(_profile) ((_profile)->flags & PFLAG_INVALID)
+#define PROFILE_INVALID(_profile) ((_profile)->label.flags & FLAG_INVALID)
 
 #define list_empty_rcu(X) (list_empty(X) || (X)->prev == LIST_POISON2)
 /*
@@ -58,20 +59,6 @@ enum profile_mode {
 	APPARMOR_UNCONFINED,	/* profile set to unconfined */
 };
 
-enum profile_flags {
-	PFLAG_HAT = 1,			/* profile is a hat */
-	PFLAG_NULL = 4,			/* profile is null learning profile */
-	PFLAG_IX_ON_NAME_ERROR = 8,	/* fallback to ix on name lookup fail */
-	PFLAG_IMMUTABLE = 0x10,		/* don't allow changes/replacement */
-	PFLAG_USER_DEFINED = 0x20,	/* user based profile - lower privs */
-	PFLAG_NO_LIST_REF = 0x40,	/* list doesn't keep profile ref */
-	PFLAG_OLD_NULL_TRANS = 0x100,	/* use // as the null transition */
-	PFLAG_INVALID = 0x200,		/* profile replaced/removed */
-	PFLAG_NS_COUNT = 0x400,		/* carries NS ref count */
-
-	/* These flags must correspond with PATH_flags */
-	PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
-};
 
 struct aa_profile;
 
@@ -135,6 +122,8 @@ struct aa_namespace {
 	struct list_head sub_ns;
 	atomic_t uniq_null;
 	long uniq_id;
+	int level;
+	struct aa_labelset labels;
 
 	struct dentry *dents[AAFS_NS_SIZEOF];
 };
@@ -155,11 +144,8 @@ struct aa_replacedby {
 	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
@@ -169,7 +155,6 @@ struct aa_replacedby {
  * @xmatch_len: xmatch prefix len, used to determine xmatch priority
  * @audit: the auditing mode of the profile
  * @mode: the enforcement mode of the profile
- * @flags: flags controlling profile behavior
  * @path_flags: flags controlling path generation behavior
  * @size: the memory consumed by this profiles rules
  * @policy: general match rules governing policy
@@ -196,6 +181,7 @@ struct aa_replacedby {
  */
 struct aa_profile {
 	struct aa_policy base;
+	struct aa_label label;
 	struct kref count;
 	struct rcu_head rcu;
 	struct aa_profile __rcu *parent;
@@ -209,7 +195,6 @@ struct aa_profile {
 	int xmatch_len;
 	enum audit_mode audit;
 	long mode;
-	long flags;
 	u32 path_flags;
 	int size;
 
@@ -229,6 +214,7 @@ void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
 
 bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view);
 const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child);
+void aa_free_namespace(struct aa_namespace *ns);
 int aa_alloc_root_ns(void);
 void aa_free_root_ns(void);
 void aa_free_namespace_kref(struct kref *kref);
@@ -244,6 +230,8 @@ struct aa_profile *aa_setup_default_profile(void);
 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_lookupn_profile(struct aa_namespace *ns,
+				      const char *hname, size_t n);
 struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name);
 struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
 
@@ -253,7 +241,8 @@ ssize_t aa_remove_profiles(char *name, size_t size);
 #define PROF_ADD 1
 #define PROF_REPLACE 0
 
-#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
+#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
+#define unconfined(X) profile_unconfined(X)
 
 
 /**
@@ -266,7 +255,7 @@ ssize_t aa_remove_profiles(char *name, size_t size);
 static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
 {
 	if (p)
-		kref_get(&(p->count));
+		kref_get(&(p->label.count));
 
 	return p;
 }
@@ -280,7 +269,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
  */
 static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
 {
-	if (p && kref_get_not0(&p->count))
+	if (p && kref_get_not0(&p->label.count))
 		return p;
 
 	return NULL;
@@ -300,7 +289,7 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p)
 	rcu_read_lock();
 	do {
 		c = rcu_dereference(*p);
-	} while (c && !kref_get_not0(&c->count));
+	} while (c && !kref_get_not0(&c->label.count));
 	rcu_read_unlock();
 
 	return c;
@@ -332,7 +321,7 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
 static inline void aa_put_profile(struct aa_profile *p)
 {
 	if (p)
-		kref_put(&p->count, aa_free_profile_kref);
+		kref_put(&p->label.count, aa_label_kref);
 }
 
 static inline struct aa_replacedby *aa_get_replacedby(struct aa_replacedby *p)
@@ -355,7 +344,7 @@ static inline void __aa_update_replacedby(struct aa_profile *orig,
 {
 	struct aa_profile *tmp = rcu_dereference(orig->replacedby->profile);
 	rcu_assign_pointer(orig->replacedby->profile, aa_get_profile(new));
-	orig->flags |= PFLAG_INVALID;
+	orig->label.flags |= FLAG_INVALID;
 	aa_put_profile(tmp);
 }
 
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
new file mode 100644
index 0000000..c369dcb
--- /dev/null
+++ b/security/apparmor/label.c
@@ -0,0 +1,611 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor label 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.
+ */
+
+#include <linux/audit.h>
+#include <linux/seq_file.h>
+
+#include "include/apparmor.h"
+#include "include/label.h"
+#include "include/policy.h"
+#include "include/sid.h"
+
+
+/*
+ * the aa_label represents the set of profiles confining an object
+ *
+ * Labels maintain a reference count to the set of pointers they reference
+ * Labels are ref counted by
+ *   tasks and object via the security field/security context off the field
+ *   code - will take a ref count on a label if it needs the label
+ *          beyond what is possible with an rcu_read_lock.
+ *   profiles - each profile is a label
+ *   sids - a pinned sid will keep a refcount of the label it is
+ *          referencing
+ *
+ * Labels are not ref count by the label set, so they maybe removed and
+ * freed when no longer in use.
+ *
+ * Label invalidation:
+ * Labels can be marked invalid, this will not stop a label from being
+ * used immediately but will cause references to the invalid label to
+ * be replaced when appropriate.
+ *
+ * There are 3 events that can result in a label being marked invalid
+ * - one of the profiles it is composed of is replaced/removed from the system
+ *   the profile will be marked invalid and can be replaced with the
+ *   unconfined profile
+ * - one of the profiles it is composed of is renamed forcing a label
+ *   reordering.
+ *   lookup the profile with the new name on label lookup
+ * - one of the profiles it is composed of is revoked
+ *   lookup the profile with the same name, if none replace with unconfined
+ *
+ */
+
+#define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __FUNCTION__, #X)
+
+
+/* helper fn for label_for_each_confined
+ * - skip delegation? How exactly?
+ * - skip profiles being cached on label with explicit rule access
+ */
+int aa_label_next_confined(struct aa_label *l, int i)
+{
+	for (; i < l->size; i++) {
+		if (!profile_unconfined(l->ent[i]))
+			return i;
+	}
+
+	return i;
+}
+
+static bool profile_in_label(struct aa_profile *profile, struct aa_label *l)
+{
+	struct aa_profile *p;
+	int i;
+
+	AA_WARN(!profile);
+	AA_WARN(!l);
+
+	label_for_each(i, l, p) {
+		if (p == profile)
+			return true;
+	}
+
+	return false;
+}
+
+void aa_label_destroy(struct aa_label *label)
+{
+	AA_WARN(!label);
+
+	if (label_invalid(label))
+		labelsetstats_dec(labels_set(label), invalid);
+
+	if (!label_isprofile(label)) {
+		struct aa_profile *profile;
+		int i;
+
+		aa_put_str(label->hname);
+
+		label_for_each(i, label, profile)
+			aa_put_profile(profile);
+	}
+
+	aa_free_sid(label->sid);
+}
+
+void aa_label_free(struct aa_label *label)
+{
+	if (!label)
+		return;
+
+	aa_label_destroy(label);
+	labelstats_inc(freed);
+	kzfree(label);
+}
+
+static void label_free_rcu(struct rcu_head *head)
+{
+	struct aa_label *l = container_of(head, struct aa_label, rcu);
+
+	if (l->flags & FLAG_NS_COUNT)
+		aa_free_namespace(labels_ns(l));
+	else if (label_isprofile(l))
+		aa_free_profile(labels_profile(l));
+	else
+		aa_label_free(l);
+}
+
+static bool __aa_label_remove(struct aa_labelset *ls, struct aa_label *label);
+void aa_label_kref(struct kref *kref)
+{
+	struct aa_label *l = container_of(kref, struct aa_label, count);
+	struct aa_labelset *ls = labels_set(l);
+	unsigned long flags;
+
+
+	write_lock_irqsave(&ls->lock, flags);
+	(void) __aa_label_remove(ls, l);
+	write_unlock_irqrestore(&ls->lock, flags);
+
+	/* TODO: if compound label and not invalid add to reclaim cache */
+	call_rcu(&l->rcu, label_free_rcu);
+}
+
+bool aa_label_init(struct aa_label *label, int size)
+{
+	AA_WARN(!label);
+	AA_WARN(size < 1);
+
+	label->sid = aa_alloc_sid();
+	if (label->sid == AA_SID_INVALID)
+		return false;
+
+	label->size = size;
+	kref_init(&label->count);
+	RB_CLEAR_NODE(&label->node);
+
+	return true;
+}
+
+/**
+ * aa_label_alloc - allocate a label with a profile vector of @size length
+ * @size: size of profile vector in the label
+ * @gfp: memory allocation type
+ *
+ * Returns: new label
+ *     else NULL if failed
+ */
+struct aa_label *aa_label_alloc(int size, gfp_t gfp)
+{
+	struct aa_label *label;
+
+	AA_WARN(size < 1);
+
+	label = kzalloc(sizeof(*label) + sizeof(struct aa_label *) * (size - 1),
+			gfp);
+	AA_DEBUG("%s (%p)\n", __func__, label);
+	if (!label)
+		goto fail;
+
+	if (!aa_label_init(label, size))
+		goto fail;
+
+	labelstats_inc(allocated);
+
+	return label;
+
+fail:
+	kfree(label);
+	labelstats_inc(failed);
+
+	return NULL;
+}
+
+static bool __aa_label_remove(struct aa_labelset *ls, struct aa_label *label)
+{
+	AA_WARN(!ls);
+	AA_WARN(!label);
+	AA_WARN(write_can_lock(&ls->lock));
+	AA_WARN(labels_set(label) != ls);
+
+	if (label_invalid(label))
+		labelstats_dec(invalid_intree);
+	else
+		__label_invalidate(label);
+
+	if (label->flags & FLAG_IN_TREE) {
+		labelsetstats_dec(ls, intree);
+		rb_erase(&label->node, &ls->root);
+		label->flags &= ~FLAG_IN_TREE;
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * aa_label_remove - remove a label from the labelset
+ * @ls: set to remove the label from
+ * @l: label to remove
+ *
+ * Returns: true if @l was removed from the tree
+ *     else @l was not in tree so it could not be removed
+ */
+bool aa_label_remove(struct aa_labelset *ls, struct aa_label *l)
+{
+	unsigned long flags;
+	bool res;
+
+	write_lock_irqsave(&ls->lock, flags);
+	res = __aa_label_remove(ls, l);
+	write_unlock_irqrestore(&ls->lock, flags);
+
+	return res;
+}
+
+static bool __aa_label_replace(struct aa_labelset *ls, struct aa_label *old,
+			       struct aa_label *new)
+{
+	AA_WARN(!ls);
+	AA_WARN(!old);
+	AA_WARN(!new);
+	AA_WARN(write_can_lock(&ls->lock));
+	AA_WARN(labels_set(old) != ls);
+	AA_WARN(new->flags & FLAG_IN_TREE);
+
+	if (label_invalid(old))
+		labelstats_dec(invalid_intree);
+	else
+		__label_invalidate(old);
+
+	if (old->flags & FLAG_IN_TREE) {
+		rb_replace_node(&old->node, &new->node, &ls->root);
+		old->flags &= ~FLAG_IN_TREE;
+		new->flags |= FLAG_IN_TREE;
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * aa_label_replace - replace a label @old with a new version @new
+ * @ls: labelset being manipulated
+ * @old: label to replace
+ * @new: label replacing @old
+ *
+ * Returns: true if @old was in tree and replaced
+ *     else @old was not in tree, and @new was not inserted
+ */
+bool aa_label_replace(struct aa_labelset *ls, struct aa_label *old,
+		      struct aa_label *new)
+{
+	unsigned long flags;
+	bool res;
+
+	write_lock_irqsave(&ls->lock, flags);
+	res = __aa_label_replace(ls, old, new);
+	write_unlock_irqrestore(&ls->lock, flags);
+
+	return res;
+}
+
+static struct aa_label *__aa_label_insert(struct aa_labelset *ls,
+					  struct aa_label *l);
+/**
+ * aa_label_make_newest - replace a label @old with a new version @new
+ * @ls: labelset being manipulated
+ * @old: label to replace
+ * @new: label replacing @old
+ *
+ * Returns: true if @new was added to tree
+ *     else there was an error
+ */
+bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old,
+			  struct aa_label *new)
+{
+	unsigned long flags;
+	bool res;
+
+	write_lock_irqsave(&ls->lock, flags);
+	res = __aa_label_replace(ls, old, new);
+	if (!res) {
+		struct aa_label *l = __aa_label_insert(ls, new);
+		if (l != new)
+			res = __aa_label_replace(ls, l, new);
+		aa_put_label(l);
+	}
+	write_unlock_irqrestore(&ls->lock, flags);
+
+	return res;
+}
+
+/**
+ * profile_cmp - profile comparision for set ordering
+ * @a: profile to compare (NOT NULL)
+ * @b: profile to compare (NOT NULL)
+ *
+ * Returns: <0  if a < b
+ *          ==0 if a == b
+ *          >0  if a > b
+ */
+static int profile_cmp(struct aa_profile *a, struct aa_profile *b)
+{
+	int res;
+
+	AA_WARN(!a);
+	AA_WARN(!b);
+	AA_WARN(!a->ns);
+	AA_WARN(!b->ns);
+	AA_WARN(!a->base.hname);
+	AA_WARN(!b->base.hname);
+
+	if (a == b)
+		return 0;
+	if (a->ns == b->ns)
+		return strcmp(a->base.hname, b->base.hname);
+	res = a->ns->level - b->ns->level;
+	if (res)
+		return res;
+
+	return strcmp(a->ns->base.name, b->ns->base.name);
+}
+
+/**
+ * label_cmp - label comparision for set ordering
+ * @a: label to compare (NOT NULL)
+ * @b: label to compare (NOT NULL)
+ *
+ * Returns: <0  if a < b
+ *          ==0 if a == b
+ *          >0  if a > b
+ */
+static int label_cmp(struct aa_label *a, struct aa_label *b)
+{
+	int i;
+
+	AA_WARN(!a);
+	AA_WARN(!b);
+
+	if (a == b)
+		return 0;
+
+	for (i = 0; i < a->size && i < b->size; i++) {
+		int res = profile_cmp(a->ent[i], b->ent[i]);
+		if (res != 0)
+			return res;
+	}
+
+	return a->size - b->size;
+}
+
+/**
+ * __aa_label_find - find label @l in label set
+ * @ls: set of labels to search (NOT NULL)
+ * @l: label to find (NOT NULL)
+ *
+ * Requires: @ls lock held
+ *           caller to hold a valid ref on l
+ *
+ * Returns: unref counted @l if @l is in tree
+ *          unref counted label that is equiv to @l in tree
+ *     else NULL if @l or equiv is not in tree
+ */
+static struct aa_label *__aa_label_find(struct aa_labelset *ls,
+					struct aa_label *l)
+{
+	struct rb_node *node;
+
+	AA_WARN(!ls);
+	AA_WARN(!l);
+
+	node = ls->root.rb_node;
+	while (node) {
+		struct aa_label *this = rb_entry(node, struct aa_label, node);
+		int result = label_cmp(l, this);
+
+		if (result < 0)
+			node = node->rb_left;
+		else if (result > 0)
+			node = node->rb_right;
+		else
+			return this;
+	}
+
+	return NULL;
+}
+
+/**
+ * aa_label_find - find label @l in label set
+ * @ls: set of labels to search (NOT NULL)
+ * @l: label to find (NOT NULL)
+ *
+ * Requires: caller to hold a valid ref on l
+ *
+ * Returns: refcounted @l if @l is in tree
+ *          refcounted label that is equiv to @l in tree
+ *     else NULL if @l or equiv is not in tree
+ */
+struct aa_label *aa_label_find(struct aa_labelset *ls, struct aa_label *l)
+{
+	struct aa_label *label;
+	unsigned long flags;
+
+	AA_WARN(!ls);
+	AA_WARN(!l);
+
+	read_lock_irqsave(&ls->lock, flags);
+	label = aa_get_label(__aa_label_find(ls, l));
+	labelstats_inc(sread);
+	read_unlock_irqrestore(&ls->lock, flags);
+
+	return label;
+}
+
+/**
+ * __aa_label_insert - attempt to insert @l into a label set
+ * @ls: set of labels to insert @l into (NOT NULL)
+ * @l: new label to insert (NOT NULL)
+ *
+ * Requires: @ls->lock
+ *           caller to hold a valid ref on l
+ *
+ * Returns: ref counted @l if successful in inserting @l
+ *          else ref counted equivalent label that is already in the set.
+ */
+static struct aa_label *__aa_label_insert(struct aa_labelset *ls,
+					  struct aa_label *l)
+{
+	struct rb_node **new, *parent = NULL;
+
+	AA_WARN(!ls);
+	AA_WARN(!l);
+	AA_WARN(write_can_lock(&ls->lock));
+	AA_WARN(l->flags & FLAG_IN_TREE);
+
+	/* Figure out where to put new node */
+	new = &ls->root.rb_node;
+	while (*new) {
+		struct aa_label *this = rb_entry(*new, struct aa_label, node);
+		int result = label_cmp(l, this);
+
+		parent = *new;
+		if (result == 0) {
+			labelsetstats_inc(ls, existing);
+			return aa_get_label(this);
+		} else if (result < 0)
+			new = &((*new)->rb_left);
+		else /* (result > 0) */
+			new = &((*new)->rb_right);
+	}
+
+	/* Add new node and rebalance tree. */
+	rb_link_node(&l->node, parent, new);
+	rb_insert_color(&l->node, &ls->root);
+	l->flags |= FLAG_IN_TREE;
+	labelsetstats_inc(ls, insert);
+	labelsetstats_inc(ls, intree);
+
+        return 	aa_get_label(l);
+}
+
+/**
+ * aa_label_insert - insert label @l into @ls or return existing label
+ * @ls - labelset to insert @l into
+ * @l - label to insert
+ *
+ * Requires: caller to hold a valid ref on l
+ *
+ * Returns: ref counted @l if successful in inserting @l
+ *     else ref counted equivalent label that is already in the set
+ */
+struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l)
+{
+	struct aa_label *label;
+	unsigned long flags;
+
+	AA_WARN(!ls);
+	AA_WARN(!l);
+
+	/* check if label exists before taking lock */
+	if (!label_invalid(l)) {
+		read_lock_irqsave(&ls->lock, flags);
+		label = aa_get_label(__aa_label_find(ls, l));
+		read_unlock_irqrestore(&ls->lock, flags);
+		labelstats_inc(fread);
+		if (label)
+			return label;
+	}
+
+	write_lock_irqsave(&ls->lock, flags);
+	label = __aa_label_insert(ls, l);
+	write_unlock_irqrestore(&ls->lock, flags);
+
+	return label;
+}
+
+
+/**
+ * aa_labelset_destroy - remove all labels from the label set
+ * @ls: label set to cleanup (NOT NULL)
+ *
+ * Labels that are removed from the set may still exist beyond the set
+ * being destroyed depending on their reference counting
+ */
+void aa_labelset_destroy(struct aa_labelset *ls)
+{
+	struct rb_node *node;
+	unsigned long flags;
+
+	AA_WARN(!ls);
+
+	write_lock_irqsave(&ls->lock, flags);
+	for (node = rb_first(&ls->root); node; node = rb_first(&ls->root)) {
+		struct aa_label *this = rb_entry(node, struct aa_label, node);
+		__aa_label_remove(ls, this);
+	}
+	write_unlock_irqrestore(&ls->lock, flags);
+}
+
+/*
+ * @ls: labelset to init (NOT NULL)
+ */
+void aa_labelset_init(struct aa_labelset *ls)
+{
+	AA_WARN(!ls);
+
+	rwlock_init(&ls->lock);
+	ls->root = RB_ROOT;
+	labelstats_init(&ls);
+}
+
+/**
+ * label_invalidate_labelset - invalidate labels caused to be invalid by @l
+ * @ls: labelset to invalidate (NOT NULL)
+ * @p: profile that is invalid and causing the invalidation (NOT NULL)
+ *
+ * Takes invalidated label @l and invalidates all labels in the labelset
+ * of @l that contain the invalid profiles in @l that caused @l to become
+ * invalid
+ */
+static void labelset_invalidate(struct aa_labelset *ls, struct aa_profile *p)
+{
+	unsigned long flags;
+	struct rb_node *node;
+
+	AA_WARN(!ls);
+	AA_WARN(!p);
+
+	write_lock_irqsave(&ls->lock, flags);
+
+	__labelset_for_each(ls, node) {
+		struct aa_label *label = rb_entry(node, struct aa_label, node);
+		if (profile_in_label(p, label)) {
+			__label_invalidate(label);
+			/* TODO: replace invalidated label */
+		}
+	}
+
+	labelstats_inc(invalid);
+	labelstats_inc(invalid_intree);
+
+	write_unlock_irqrestore(&ls->lock, flags);
+}
+
+/**
+ * __aa_labelset_invalidate_all - invalidate labels in @ns and below
+ * @ns: ns to start invalidation at (NOT NULL)
+ * @p: profile that is causing invalidation (NOT NULL)
+ *
+ * Requires: @ns lock be held
+ *
+ * Invalidates labels based on @p in @ns and any children namespaces.
+*/
+void __aa_labelset_invalidate_all(struct aa_namespace *ns, struct aa_profile *p)
+{
+	struct aa_namespace *child;
+
+	AA_WARN(!ns);
+	AA_WARN(!p);
+	AA_WARN(!mutex_is_locked(&ns->lock));
+
+	labelset_invalidate(&ns->labels, p);
+
+	list_for_each_entry(child, &ns->sub_ns, base.list) {
+		mutex_lock(&child->lock);
+		__aa_labelset_invalidate_all(child, p);
+		mutex_unlock(&child->lock);
+	}
+}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 935ba8e..1d80595 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -82,6 +82,7 @@
 #include "include/context.h"
 #include "include/file.h"
 #include "include/ipc.h"
+#include "include/label.h"
 #include "include/match.h"
 #include "include/path.h"
 #include "include/policy.h"
@@ -298,8 +299,8 @@ static struct aa_namespace *alloc_namespace(const char *prefix,
 	if (!ns->unconfined)
 		goto fail_unconfined;
 
-	ns->unconfined->flags = PFLAG_IX_ON_NAME_ERROR |
-		PFLAG_IMMUTABLE | PFLAG_NS_COUNT;
+	ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR |
+		FLAG_IMMUTABLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
 	ns->unconfined->mode = APPARMOR_UNCONFINED;
 
 	/* ns and ns->unconfined share ns->unconfined refcount */
@@ -307,6 +308,8 @@ static struct aa_namespace *alloc_namespace(const char *prefix,
 
 	atomic_set(&ns->uniq_null, 0);
 
+	aa_labelset_init(&ns->labels);
+
 	return ns;
 
 fail_unconfined:
@@ -318,18 +321,19 @@ fail_ns:
 
 void aa_free_profile(struct aa_profile *profile);
 /**
- * free_namespace - free a profile namespace
+ * aa_free_namespace - free a profile namespace
  * @ns: the namespace to free  (MAYBE NULL)
  *
  * Requires: All references to the namespace must have been put, if the
  *           namespace was referenced by a profile confining a task,
  */
-static void free_namespace(struct aa_namespace *ns)
+void aa_free_namespace(struct aa_namespace *ns)
 {
 	if (!ns)
 		return;
 
 	policy_destroy(&ns->base);
+	aa_labelset_destroy(&ns->labels);
 	aa_put_namespace(ns->parent);
 
 	ns->unconfined->ns = NULL;
@@ -405,11 +409,12 @@ static struct aa_namespace *aa_prepare_namespace(const char *name)
 		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);
+			aa_free_namespace(ns);
 			ns = NULL;
 			goto out;
 		}
 		ns->parent = aa_get_namespace(root);
+		ns->level = root->level + 1;
 		list_add_rcu(&ns->base.list, &root->sub_ns);
 		/* add list ref */
 		aa_get_namespace(ns);
@@ -628,29 +633,6 @@ void aa_free_profile(struct aa_profile *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)
  *
@@ -672,7 +654,11 @@ struct aa_profile *aa_alloc_profile(const char *hname)
 
 	if (!policy_init(&profile->base, NULL, hname))
 		goto fail;
-	kref_init(&profile->count);
+	if (!aa_label_init(&profile->label, 1))
+		goto fail;
+	profile->label.hname = profile->base.hname;
+	profile->label.flags |= FLAG_PROFILE;
+	profile->label.ent[0] = profile;
 
 	/* refcount released by caller */
 	return profile;
@@ -716,9 +702,9 @@ struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat)
 		goto fail;
 
 	profile->mode = APPARMOR_COMPLAIN;
-	profile->flags |= PFLAG_NULL;
+	profile->label.flags |= FLAG_NULL;
 	if (hat)
-		profile->flags |= PFLAG_HAT;
+		profile->label.flags |= FLAG_HAT;
 
 	/* released on free_profile */
 	rcu_assign_pointer(profile->parent, aa_get_profile(parent));
@@ -745,7 +731,7 @@ struct aa_profile *aa_setup_default_profile(void)
 		return NULL;
 
 	/* the default profile pretends to be unconfined until it is replaced */
-	profile->flags |= PFLAG_IX_ON_NAME_ERROR;
+	profile->label.flags |= FLAG_IX_ON_NAME_ERROR | FLAG_UNCONFINED;
 	profile->mode = APPARMOR_UNCONFINED;
 
 	profile->ns = aa_get_namespace(root_ns);
@@ -846,9 +832,10 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns,
 }
 
 /**
- * __lookup_profile - lookup the profile matching @hname
+ * __lookupn_profile - lookup the profile matching @hname
  * @base: base list to start looking up profile name from  (NOT NULL)
  * @hname: hierarchical profile name  (NOT NULL)
+ * @n: length of @hname
  *
  * Requires: rcu_read_lock be held
  *
@@ -856,53 +843,68 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns,
  *
  * Do a relative name lookup, recursing through profile tree.
  */
-static struct aa_profile *__lookup_profile(struct aa_policy *base,
-					   const char *hname)
+static struct aa_profile *__lookupn_profile(struct aa_policy *base,
+					    const char *hname, size_t n)
 {
 	struct aa_profile *profile = NULL;
-	char *split;
+	const char *split, *name = hname;
 
-	for (split = strstr(hname, "//"); split;) {
-		profile = __strn_find_child(&base->profiles, hname,
-					    split - hname);
+	for (split = strstr(hname, "//"); split && (split - hname <= n);) {
+		profile = __strn_find_child(&base->profiles, name,
+					    split - name);
 		if (!profile)
 			return NULL;
 
 		base = &profile->base;
-		hname = split + 2;
-		split = strstr(hname, "//");
+		name = split + 2;
+		split = strstr(name, "//");
 	}
 
-	profile = __find_child(&base->profiles, hname);
+	if (name - hname <= n)
+		return __strn_find_child(&base->profiles, name,
+					 n - (name - hname));
+	return NULL;
+}
 
-	return profile;
+static struct aa_profile *__lookup_profile(struct aa_policy *base,
+					   const char *hname)
+{
+	return __lookupn_profile(base, hname, strlen(hname));
 }
 
 /**
  * aa_lookup_profile - find a profile by its full or partial name
  * @ns: the namespace to start from (NOT NULL)
  * @hname: name to do lookup on.  Does not contain namespace prefix (NOT NULL)
+ * @n: size of @hname
  *
  * Returns: refcounted profile or NULL if not found
  */
-struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname)
+struct aa_profile *aa_lookupn_profile(struct aa_namespace *ns,
+				      const char *hname, size_t n)
 {
 	struct aa_profile *profile;
 
 	rcu_read_lock();
 	do {
-		profile = __lookup_profile(&ns->base, hname);
+		profile = __lookupn_profile(&ns->base, hname, n);
 	} 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)
+	if (!profile && strncmp(hname, "unconfined", n) == 0)
 		profile = aa_get_newest_profile(ns->unconfined);
 
 	/* refcount released by caller */
 	return profile;
 }
 
+struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname)
+{
+	return aa_lookupn_profile(ns, hname, strlen(hname));
+}
+
+
 /**
  * replacement_allowed - test to see if replacement is allowed
  * @profile: profile to test if it can be replaced  (MAYBE NULL)
@@ -915,7 +917,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,
 			       const char **info)
 {
 	if (profile) {
-		if (profile->flags & PFLAG_IMMUTABLE) {
+		if (profile->label.flags & FLAG_IMMUTABLE) {
 			*info = "cannot replace immutible profile";
 			return -EPERM;
 		} else if (noreplace) {
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index f5b9977..5035798 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -514,7 +514,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
 	if (!unpack_u32(e, &tmp, NULL))
 		goto fail;
 	if (tmp & PACKED_FLAG_HAT)
-		profile->flags |= PFLAG_HAT;
+		profile->label.flags |= FLAG_HAT;
 	if (!unpack_u32(e, &tmp, NULL))
 		goto fail;
 	if (tmp == PACKED_MODE_COMPLAIN)
@@ -533,10 +533,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
 
 	/* path_flags is optional */
 	if (unpack_u32(e, &profile->path_flags, "path_flags"))
-		profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
+		profile->path_flags |= profile->label.flags &
+			FLAG_MEDIATE_DELETED;
 	else
 		/* set a default value if path_flags field is not present */
-		profile->path_flags = PFLAG_MEDIATE_DELETED;
+		profile->path_flags = FLAG_MEDIATE_DELETED;
 
 	if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
 		goto fail;
-- 
1.8.1.2




More information about the AppArmor mailing list