Signed-off-by: John Johansen <[email protected]>
---
 security/apparmor/include/label.h  |  20 +-
 security/apparmor/include/policy.h |   2 +
 security/apparmor/label.c          | 529 +++++++++++++++++++++++++++++++++++++
 security/apparmor/policy.c         |  22 ++
 security/apparmor/procattr.c       |  53 ++--
 5 files changed, 587 insertions(+), 39 deletions(-)

diff --git a/security/apparmor/include/label.h 
b/security/apparmor/include/label.h
index 7f62ede..f21dd40 100644
--- a/security/apparmor/include/label.h
+++ b/security/apparmor/include/label.h
@@ -197,7 +197,25 @@ bool aa_label_make_newest(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);
+int aa_label_acntsprint(char __counted **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/include/policy.h 
b/security/apparmor/include/policy.h
index dd007f0..ea585c4 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -226,6 +226,8 @@ 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_fqlookupn_profile(struct aa_namespace *ns, char *fqname,
+                                       int n);
 struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
 
 ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace);
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
index 0c67a80..338b6bd 100644
--- a/security/apparmor/label.c
+++ b/security/apparmor/label.c
@@ -517,6 +517,535 @@ 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 __counted *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_acntsprint(&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
+               aa_put_str(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
+ */
+static 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);
+}
+
+/**
+ * aa_label_acntsprint - allocate a __counted string buffer and print label
+ * @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_acntsprint(char __counted **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 = aa_str_alloc(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);
+
+       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++) {
+               *split = 0;
+               label->ent[i] = aa_fqlookupn_profile(base, str, split - str);
+               if (!label->ent[i])
+                       goto fail;
+               str = split + 3;
+               split = strstr(str, "//&");
+       }
+       label->ent[i] = aa_fqlookupn_profile(base, str, strlen(str));
+       if (!label->ent[i])
+               goto fail;
+
+       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 d77a5c5..331bd02 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",
@@ -897,6 +900,25 @@ struct aa_profile *aa_lookup_profile(struct aa_namespace 
*ns, const char *hname)
        return aa_lookupn_profile(ns, hname, strlen(hname));
 }
 
+struct aa_profile *aa_fqlookupn_profile(struct aa_namespace *base, char 
*fqname,
+                                       int n)
+{
+       struct aa_profile *profile;
+       struct aa_namespace *ns;
+       char *name, *ns_name;
+
+       name = aa_split_fqname(fqname, &ns_name);
+       if (ns_name) {
+               ns = aa_find_namespace(base, ns_name);
+               if (!ns)
+                       return NULL;
+       } else
+               ns = aa_get_namespace(base);
+       profile = aa_lookupn_profile(ns, name, n - (name - fqname));
+       aa_put_namespace(ns);
+
+       return profile;
+}
 
 /**
  * replacement_allowed - test to see if replacement is allowed
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.8.1.2


-- 
AppArmor mailing list
[email protected]
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/apparmor

Reply via email to