[apparmor] [PATCH 40/43] apparmor: add abilitiy to print labels and update interface to use

John Johansen john.johansen at canonical.com
Fri Feb 8 21:01:16 UTC 2013


Signed-off-by: John Johansen <john.johansen at canonical.com>
---
 security/apparmor/include/label.h |   18 +-
 security/apparmor/label.c         |  510 +++++++++++++++++++++++++++++++++++++
 security/apparmor/policy.c        |    3 +
 security/apparmor/procattr.c      |   53 ++--
 4 files changed, 545 insertions(+), 39 deletions(-)

diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h
index a1a65dc..fe4f352 100644
--- a/security/apparmor/include/label.h
+++ b/security/apparmor/include/label.h
@@ -196,7 +196,23 @@ bool aa_label_replace(struct aa_labelset *ls, struct aa_label *old,
 
 struct aa_label *aa_label_find(struct aa_labelset *ls, struct aa_label *l);
 
-
+bool aa_update_label_name(struct aa_namespace *ns, struct aa_label *label,
+			  gfp_t gfp);
+
+int aa_profile_snprint(char *str, size_t size, struct aa_namespace *ns,
+		       struct aa_profile *profile, bool mode);
+int aa_label_snprint(char *str, size_t size, struct aa_namespace *ns,
+		     struct aa_label *label, bool mode);
+int aa_label_asprint(char **strp, struct aa_namespace *ns,
+		     struct aa_label *label, bool mode, gfp_t gfp);
+void aa_label_audit(struct audit_buffer *ab, struct aa_namespace *ns,
+		    struct aa_label *label, bool mode, gfp_t gfp);
+void aa_label_seq_print(struct seq_file *f, struct aa_namespace *ns,
+			struct aa_label *label, bool mode, gfp_t gfp);
+void aa_label_printk(struct aa_namespace *ns, struct aa_label *label,
+		     bool mode, gfp_t gfp);
+struct aa_label *aa_label_parse(struct aa_namespace *base, char *str,
+				gfp_t gfp);
 
 static inline struct aa_label *aa_get_label(struct aa_label *l)
 {
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
index e408234..448a287 100644
--- a/security/apparmor/label.c
+++ b/security/apparmor/label.c
@@ -512,6 +512,516 @@ static struct aa_label *__aa_label_insert(struct aa_labelset *ls,
 }
 
 /**
+ * aa_update_label_name - update a label to have a stored name
+ * @ns: ns being viewed from (NOT NULL)
+ * @label: label to update (NOT NULL)
+ * @gfp: type of memory allocation
+ *
+ * Requires: labels_set(label) not locked in caller
+ *
+ * note: only updates the label name if it does not have a name already
+ *       and if it is in the labelset
+ */
+bool aa_update_label_name(struct aa_namespace *ns, struct aa_label *label,
+			  gfp_t gfp)
+{
+	struct aa_labelset *ls;
+	unsigned long flags;
+	char *name;
+	bool res = false;
+
+	AA_WARN(!ns);
+	AA_WARN(!label);
+
+	if (label->hname || !(label->flags & FLAG_IN_TREE) ||
+	    labels_ns(label) != ns)
+		return res;
+
+	if (aa_label_asprint(&name, ns, label, false, gfp) == -1)
+		return res;
+
+	ls = labels_set(label);
+	write_lock_irqsave(&ls->lock, flags);
+	if (!label->hname && label->flags & FLAG_IN_TREE) {
+		label->hname = name;
+		res = true;
+	} else
+		kfree(name);
+	write_unlock_irqrestore(&ls->lock, flags);
+
+	return res;
+}
+
+/* cached label name is present and visible
+ * @label->hname only exists if label is namespace hierachical */
+static inline bool label_name_visible(struct aa_namespace *ns,
+				      struct aa_label *label)
+{
+	if (label->hname && labels_ns(label) == ns)
+		return true;
+
+	return false;
+}
+
+/* helper macro for snprint routines */
+#define update_for_len(total, len, size, str)	\
+do {					\
+	if (len < 0)			\
+		return len;		\
+	total += len;			\
+	len = min(len, size);		\
+	size -= len;			\
+	str += len;			\
+} while (0)
+
+/**
+ * aa_modename_snprint - print the mode name of a profile or label to a buffer
+ * @str: buffer to write to (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to print the mode of (NOT NULL)
+ *
+ * Returns: size of name written or would be written if larger than
+ *          available buffer
+ *
+ * Note: will print every mode name visible (mode1)(mode2)(mode3)
+ *       this is likely not what is desired for most interfaces
+ *       use aa_mode_snprint to get the standard mode format
+ */
+int aa_modename_snprint(char *str, size_t size, struct aa_namespace *ns,
+			struct aa_label *label)
+{
+	struct aa_profile *profile;
+	int i, total = 0;
+	size_t len;
+
+	label_for_each(i, label, profile) {
+		const char *modestr;
+		if (!aa_ns_visible(ns, profile->ns))
+			continue;
+		/* no mode for 'unconfined' */
+		if (profile_unconfined(profile) &&
+		    profile == profile->ns->unconfined)
+			break;
+		modestr = aa_profile_mode_names[profile->mode];
+		len = snprintf(str, size, "(%s)", modestr);
+		update_for_len(total, len, size, str);
+	}
+
+	return total;
+}
+
+/**
+ * aa_modechr_snprint - print the mode chr of a profile or labels to a buffer
+ * @str: buffer to write to (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to print the mode chr of (NOT NULL)
+ *
+ * Returns: size of mode string written or would be written if larger than
+ *          available buffer
+ *
+ * Note: will print the chr of every visible profile (123)
+ *       this is likely not what is desired for most interfaces
+ *       use aa_mode_snprint to get the standard mode format
+ */
+static int aa_modechr_snprint(char *str, size_t size, struct aa_namespace *ns,
+			      struct aa_label *label)
+{
+	struct aa_profile *profile;
+	int i, total = 0;
+	size_t len;
+
+	len = snprintf(str, size, "(");
+	update_for_len(total, len, size, str);
+	label_for_each(i, label, profile) {
+		const char *modestr;
+		if (!aa_ns_visible(ns, profile->ns))
+			continue;
+		modestr = aa_profile_mode_names[profile->mode];
+		/* just the first char of the modestr */
+		len = snprintf(str, size, "%c", *modestr);
+		update_for_len(total, len, size, str);
+	}
+	len = snprintf(str, size, ")");
+	update_for_len(total, len, size, str);
+
+	return total;
+}
+
+/**
+ * aa_mode_snprint - print the mode of a profile or label to a buffer
+ * @str: buffer to write to (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to print the mode of (NOT NULL)
+ * @count: number of label entries to be printed (<= 0 if unknown)
+ *
+ * Returns: size of name written or would be written if larger than
+ *          available buffer
+ *
+ * Note: dynamically switches between mode name, and mode char format as
+ *       appropriate
+ *       will not print anything if the label is not visible
+ */
+static int aa_mode_snprint(char *str, size_t size, struct aa_namespace *ns,
+			   struct aa_label *label, int count)
+{
+	struct aa_profile *profile;
+	int i;
+
+	if (count <= 0) {
+		count = 0;
+		label_for_each(i, label, profile) {
+			if (aa_ns_visible(ns, profile->ns))
+				count++;
+		}
+	}
+
+	if (count == 0)
+		return 0;
+
+	if (count == 1)
+		return aa_modename_snprint(str, size, ns, label);
+
+	return aa_modechr_snprint(str, size, ns, label);
+}
+
+/**
+ * aa_snprint_profile - print a profile name to a buffer
+ * @str: buffer to write to. (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @profile: profile to view (NOT NULL)
+ * @mode: whether to include the mode string
+ *
+ * Returns: size of name written or would be written if larger than
+ *          available buffer
+ *
+ * Note: will not print anything if the profile is not visible
+ */
+int aa_profile_snprint(char *str, size_t size, struct aa_namespace *ns,
+		       struct aa_profile *profile, bool mode)
+{
+	const char *ns_name = aa_ns_name(ns, profile->ns);
+
+	AA_WARN(!str && size != 0);
+	AA_WARN(!ns);
+	AA_WARN(!profile);
+
+	if (!ns_name)
+		return 0;
+
+	if (mode && profile != profile->ns->unconfined) {
+		const char *modestr = aa_profile_mode_names[profile->mode];
+		if (strlen(ns_name))
+			return snprintf(str, size, ":%s://%s (%s)", ns_name,
+					profile->base.hname, modestr);
+		return snprintf(str, size, "%s (%s)", profile->base.hname,
+				modestr);
+	}
+
+	if (strlen(ns_name))
+		return snprintf(str, size, ":%s://%s", ns_name,
+				profile->base.hname);
+	return snprintf(str, size, "%s", profile->base.hname);
+}
+
+/**
+ * aa_label_snprint - print a label name to a string buffer
+ * @str: buffer to write to. (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to view (NOT NULL)
+ * @mode: whether to include the mode string
+ *
+ * Returns: size of name written or would be written if larger than
+ *          available buffer
+ *
+ * Note: labels do not have to be strictly hierarchical to the ns as
+ *       objects may be shared across different namespaces and thus
+ *       pickup labeling from each ns.  If a particular part of the
+ *       label is not visible it will just be excluded.  And if none
+ *       of the label is visble "---" will be used.
+ */
+int aa_label_snprint(char *str, size_t size, struct aa_namespace *ns,
+		     struct aa_label *label, bool mode)
+{
+	struct aa_profile *profile;
+	int i, count = 0, total = 0;
+	size_t len;
+
+	AA_WARN(!str && size != 0);
+	AA_WARN(!ns);
+	AA_WARN(!label);
+
+	label_for_each(i, label, profile) {
+		if (aa_ns_visible(ns, profile->ns)) {
+			if (count > 0) {
+				len = snprintf(str, size, "//&");
+				update_for_len(total, len, size, str);
+			}
+			len = aa_profile_snprint(str, size, ns, profile, false);
+			update_for_len(total, len, size, str);
+			count++;
+		}
+	}
+
+	if (count == 0)
+		return snprintf(str, size, aa_hidden_ns_name);
+
+	/* count == 1 && ... is for backwards compat where the mode
+	 * is not displayed for 'unconfined' in the current ns
+	 */
+	if (mode &&
+	    !(count == 1 && labels_ns(label) == ns &&
+	      labels_profile(label) == ns->unconfined)) {
+		len = snprintf(str, size, " ");
+		update_for_len(total, len, size, str);
+		len = aa_mode_snprint(str, size, ns, label, count);
+		update_for_len(total, len, size, str);
+	}
+
+	return total;
+}
+#undef update_for_len
+
+/**
+ * aa_label_asprint - allocate a string buffer and print label into it
+ * @strp: buffer to write to. (MAY BE NULL if @size == 0)
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to view (NOT NULL)
+ * @mode: whether to include the mode string
+ * @gfp: kernel memory allocation type
+ *
+ * Returns: size of name written or would be written if larger than
+ *          available buffer
+ */
+int aa_label_asprint(char **strp, struct aa_namespace *ns,
+		     struct aa_label *label, bool mode, gfp_t gfp)
+{
+	int size;
+
+	AA_WARN(!strp);
+	AA_WARN(!ns);
+	AA_WARN(!label);
+
+	size = aa_label_snprint(NULL, 0, ns, label, mode);
+	if (size < 0)
+		return size;
+
+	*strp = kmalloc(size + 1, gfp);
+	if (!*strp)
+		return -ENOMEM;
+	return aa_label_snprint(*strp, size + 1, ns, label, mode);
+}
+
+void aa_label_audit(struct audit_buffer *ab, struct aa_namespace *ns,
+		    struct aa_label *label, bool mode, gfp_t gfp)
+{
+	const char *str;
+	char *name = NULL;
+	int len;
+
+	AA_WARN(!ab);
+	AA_WARN(!ns);
+	AA_WARN(!label);
+
+	if (label_name_visible(ns, label)) {
+		str = (char *) label->hname;
+		len = strlen(str);
+	} else {
+		labelstats_inc(audit_name_alloc);
+		len  = aa_label_asprint(&name, ns, label, mode, gfp);
+		if (len == -1) {
+			labelstats_inc(audit_name_fail);
+			printk("<label error>");
+			return;
+		}
+		str = name;
+	}
+
+	if (audit_string_contains_control(str, len))
+		audit_log_n_hex(ab, str, len);
+	else
+		audit_log_n_string(ab, str, len);
+
+	if (name)
+		kfree(name);
+}
+
+void aa_label_seq_print(struct seq_file *f, struct aa_namespace *ns,
+			struct aa_label *label, bool mode, gfp_t gfp)
+{
+	AA_WARN(!f);
+	AA_WARN(!ns);
+	AA_WARN(!label);
+
+	if (!label_name_visible(ns, label)) {
+		char *str;
+		int len;
+
+		labelstats_inc(seq_print_name_alloc);
+		len = aa_label_asprint(&str, ns, label, mode, gfp);
+		if (len == -1) {
+			labelstats_inc(seq_print_name_fail);
+			printk("<label error>");
+			return;
+		}
+		seq_printf(f, "%s", str);
+		kfree(str);
+	} else
+		seq_printf(f, "%s", label->hname);
+}
+
+void aa_label_printk(struct aa_namespace *ns, struct aa_label *label, bool mode,
+		     gfp_t gfp)
+{
+	char *str;
+	int len;
+
+	AA_WARN(!ns);
+	AA_WARN(!label);
+
+	if (!label_name_visible(ns, label)) {
+		labelstats_inc(printk_name_alloc);
+		len = aa_label_asprint(&str, ns, label, mode, gfp);
+		if (len == -1) {
+			labelstats_inc(printk_name_fail);
+			printk("<label error>");
+			return;
+		}
+		printk("%s", str);
+		kfree(str);
+	} else
+		printk("%s", label->hname);
+}
+
+
+static int label_count_str_entries(const char *str)
+{
+	const char *split;
+	int count = 1;
+
+	AA_WARN(!str);
+
+	for (split = strstr(str, "//&"); split; split = strstr(str, "//&")) {
+		count++;
+		str = split + 3;
+	}
+
+	return count;
+}
+
+/**
+ * aa_sort_and_merge_profiles - canonical sort and merge a list of profiles
+ * @n: number of refcounted profiles in the list (@n > 0)
+ * @ps: list of profiles to sort and merge
+ *
+ * Returns: the number of duplicates eliminated == references put
+ */
+static int aa_sort_and_merge_profiles(int n, struct aa_profile **ps)
+{
+	int i, dups = 0;
+
+	AA_WARN(n < 1);
+	AA_WARN(!ps);
+
+	/* label lists are usually small so just use insertion sort */
+	for (i = 1; i < n; i++) {
+		struct aa_profile *tmp = ps[i];
+		int pos, j;
+
+		for (pos = i - 1 - dups; pos >= 0; pos--) {
+			int res = profile_cmp(ps[pos], tmp);
+			if (res == 0) {
+				aa_put_profile(tmp);
+				dups++;
+				continue;
+			} else if (res < 0)
+				break;
+		}
+		pos++;
+
+		for (j = i - dups; j > pos; j--)
+			ps[j] = ps[j - 1];
+
+		ps[pos] = tmp;
+	}
+
+	return dups;
+}
+
+/**
+ * aa_label_parse - parse, validate and convert a text string to a label
+ * @base: base namespace to use for lookups (NOT NULL)
+ * @str: null terminated text string (NOT NULL)
+ * @gfp: allocation type
+ *
+ * Returns: the matching refcounted label if present
+ *     else ERRPTR
+ */
+struct aa_label *aa_label_parse(struct aa_namespace *base, char *str, gfp_t gfp)
+{
+	struct aa_profile *profile;
+	struct aa_label *l, *label;
+	int i, len, unconf;
+	char *split;
+
+	AA_WARN(!base);
+	AA_WARN(!str);
+
+	len = label_count_str_entries(str);
+	label = aa_label_alloc(len, gfp);
+	if (!label)
+		return ERR_PTR(-ENOMEM);
+
+	for (split = strstr(str, "//&"), i = 0; split && i < len; i++) {
+		struct aa_namespace *ns;
+		char *name, *ns_name;
+
+		*split = 0;
+		name = aa_split_fqname(str, &ns_name);
+		if (ns_name) {
+			ns = aa_find_namespace(base, ns_name);
+			if (!ns)
+				goto fail;
+		} else
+			ns = aa_get_namespace(base);
+
+		profile = aa_lookupn_profile(ns, name, split - str);
+		aa_put_namespace(ns);
+		if (!profile)
+			goto fail;
+		label->ent[i] = profile;
+
+		str = split + 3;
+		split = strstr(str, "//&");
+	}
+
+	i = aa_sort_and_merge_profiles(len, &label->ent[0]);
+	label->size -= i;
+
+	unconf = 1;
+	label_for_each(i, label, profile) {
+		if (!profile_unconfined(profile)) {
+			unconf = 0;
+			break;
+		}
+	}
+
+	if (unconf)
+		label->flags = FLAG_UNCONFINED;
+
+	l = aa_label_find(labels_set(label), label);
+	aa_put_label(label);
+	return l;
+
+fail:
+	aa_label_free(label);
+	return ERR_PTR(-ENOENT);
+}
+/**
  * aa_label_insert - insert label @l into @ls or return existing label
  * @ls - labelset to insert @l into
  * @l - label to insert
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 268eece..98a1c9a 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -93,6 +93,9 @@
 /* root profile namespace */
 struct aa_namespace *root_ns;
 
+/* Note: mode names must be unique in the first character because of
+ *       modechrs used to print modes on compound labels on some interfaces
+ */
 const char *const aa_profile_mode_names[] = {
 	"enforce",
 	"complain",
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c
index 82666b0..515c5e2 100644
--- a/security/apparmor/procattr.c
+++ b/security/apparmor/procattr.c
@@ -35,50 +35,27 @@
  */
 int aa_getprocattr(struct aa_label *label, char **string)
 {
-	char *str;
-	int len = 0, mode_len = 0, ns_len = 0, name_len;
-	const char *mode_str = aa_profile_mode_names[labels_profile(label)->mode];
-	const char *ns_name = NULL;
 	struct aa_namespace *ns = labels_ns(label);
 	struct aa_namespace *current_ns = labels_ns(__aa_current_label());
-	bool unconfined;
-	char *s;
+	int len;
 
-	if (!aa_ns_visible(current_ns, ns))
-		return -EACCES;
+        if (!aa_ns_visible(current_ns, ns))
+                return -EACCES;
 
-	ns_name = aa_ns_name(current_ns, ns);
-	ns_len = strlen(ns_name);
-
-	/* if the visible ns_name is > 0 increase size for : :// seperator */
-	if (ns_len)
-		ns_len += 4;
-
-	/* 'unconfined' profile don't have a mode string appended */
-	unconfined = labels_profile(label) == labels_ns(label)->unconfined;
-	if (!unconfined)
-		mode_len = strlen(mode_str) + 3;	/* + 3 for _() */
-
-	name_len = strlen(label->hname);
-	len = mode_len + ns_len + name_len + 1;	    /* + 1 for \n */
-	s = str = kmalloc(len + 1, GFP_KERNEL);	    /* + 1 \0 */
-	if (!str)
+	len = aa_label_snprint(NULL, 0, current_ns, label, true);
+	if (len < 0)
+		return len;
+	*string = kmalloc(len + 2, GFP_KERNEL);
+	if (!*string)
 		return -ENOMEM;
 
-	if (ns_len) {
-		/* skip over prefix current_ns->base.hname and separating // */
-		sprintf(s, ":%s://", ns_name);
-		s += ns_len;
-	}
-	if (unconfined)
-		/* mode string not being appended */
-		sprintf(s, "%s\n", label->hname);
-	else
-		sprintf(s, "%s (%s)\n", label->hname, mode_str);
-	*string = str;
-
-	/* NOTE: len does not include \0 of string, not saved as part of file */
-	return len;
+	len = aa_label_snprint(*string, len + 2, current_ns, label, true);
+	if (len < 0)
+		return len;
+	(*string)[len] = '\n';
+	(*string)[len + 1] = 0;
+
+	return len + 1;
 }
 
 /**
-- 
1.7.10.4




More information about the AppArmor mailing list