diff options
Diffstat (limited to 'security/apparmor/label.c')
-rw-r--r-- | security/apparmor/label.c | 2120 |
1 files changed, 2120 insertions, 0 deletions
diff --git a/security/apparmor/label.c b/security/apparmor/label.c new file mode 100644 index 000000000000..e052eaba1cf6 --- /dev/null +++ b/security/apparmor/label.c @@ -0,0 +1,2120 @@ +/* + * AppArmor security module + * + * This file contains AppArmor label definitions + * + * Copyright 2017 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 <linux/sort.h> + +#include "include/apparmor.h" +#include "include/context.h" +#include "include/label.h" +#include "include/policy.h" +#include "include/secid.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 + * secids - a pinned secid will keep a refcount of the label it is + * referencing + * objects - inode, files, sockets, ... + * + * Labels are not ref counted by the label set, so they maybe removed and + * freed when no longer in use. + * + */ + +#define PROXY_POISON 97 +#define LABEL_POISON 100 + +static void free_proxy(struct aa_proxy *proxy) +{ + if (proxy) { + /* p->label will not updated any more as p is dead */ + aa_put_label(rcu_dereference_protected(proxy->label, true)); + memset(proxy, 0, sizeof(*proxy)); + proxy->label = (struct aa_label *) PROXY_POISON; + kfree(proxy); + } +} + +void aa_proxy_kref(struct kref *kref) +{ + struct aa_proxy *proxy = container_of(kref, struct aa_proxy, count); + + free_proxy(proxy); +} + +struct aa_proxy *aa_alloc_proxy(struct aa_label *label, gfp_t gfp) +{ + struct aa_proxy *new; + + new = kzalloc(sizeof(struct aa_proxy), gfp); + if (new) { + kref_init(&new->count); + rcu_assign_pointer(new->label, aa_get_label(label)); + } + return new; +} + +/* requires profile list write lock held */ +void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new) +{ + struct aa_label *tmp; + + AA_BUG(!orig); + AA_BUG(!new); + AA_BUG(!write_is_locked(&labels_set(orig)->lock)); + + tmp = rcu_dereference_protected(orig->proxy->label, + &labels_ns(orig)->lock); + rcu_assign_pointer(orig->proxy->label, aa_get_label(new)); + orig->flags |= FLAG_STALE; + aa_put_label(tmp); +} + +static void __proxy_share(struct aa_label *old, struct aa_label *new) +{ + struct aa_proxy *proxy = new->proxy; + + new->proxy = aa_get_proxy(old->proxy); + __aa_proxy_redirect(old, new); + aa_put_proxy(proxy); +} + + +/** + * ns_cmp - compare ns for label set ordering + * @a: ns to compare (NOT NULL) + * @b: ns to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int ns_cmp(struct aa_ns *a, struct aa_ns *b) +{ + int res; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!a->base.hname); + AA_BUG(!b->base.hname); + + if (a == b) + return 0; + + res = a->level - b->level; + if (res) + return res; + + return strcmp(a->base.hname, b->base.hname); +} + +/** + * 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_BUG(!a); + AA_BUG(!b); + AA_BUG(!a->ns); + AA_BUG(!b->ns); + AA_BUG(!a->base.hname); + AA_BUG(!b->base.hname); + + if (a == b || a->base.hname == b->base.hname) + return 0; + res = ns_cmp(a->ns, b->ns); + if (res) + return res; + + return strcmp(a->base.hname, b->base.hname); +} + +/** + * vec_cmp - label comparision for set ordering + * @a: label to compare (NOT NULL) + * @vec: vector of profiles to compare (NOT NULL) + * @n: length of @vec + * + * Returns: <0 if a < vec + * ==0 if a == vec + * >0 if a > vec + */ +static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn) +{ + int i; + + AA_BUG(!a); + AA_BUG(!*a); + AA_BUG(!b); + AA_BUG(!*b); + AA_BUG(an <= 0); + AA_BUG(bn <= 0); + + for (i = 0; i < an && i < bn; i++) { + int res = profile_cmp(a[i], b[i]); + + if (res != 0) + return res; + } + + return an - bn; +} + +static bool vec_is_stale(struct aa_profile **vec, int n) +{ + int i; + + AA_BUG(!vec); + + for (i = 0; i < n; i++) { + if (profile_is_stale(vec[i])) + return true; + } + + return false; +} + +static bool vec_unconfined(struct aa_profile **vec, int n) +{ + int i; + + AA_BUG(!vec); + + for (i = 0; i < n; i++) { + if (!profile_unconfined(vec[i])) + return false; + } + + return true; +} + +static int sort_cmp(const void *a, const void *b) +{ + return profile_cmp(*(struct aa_profile **)a, *(struct aa_profile **)b); +} + +/* + * assumes vec is sorted + * Assumes @vec has null terminator at vec[n], and will null terminate + * vec[n - dups] + */ +static inline int unique(struct aa_profile **vec, int n) +{ + int i, pos, dups = 0; + + AA_BUG(n < 1); + AA_BUG(!vec); + + pos = 0; + for (i = 1; i < n; i++) { + int res = profile_cmp(vec[pos], vec[i]); + + AA_BUG(res > 0, "vec not sorted"); + if (res == 0) { + /* drop duplicate */ + aa_put_profile(vec[i]); + dups++; + continue; + } + pos++; + if (dups) + vec[pos] = vec[i]; + } + + AA_BUG(dups < 0); + + return dups; +} + +/** + * aa_vec_unique - canonical sort and unique a list of profiles + * @n: number of refcounted profiles in the list (@n > 0) + * @vec: list of profiles to sort and merge + * + * Returns: the number of duplicates eliminated == references put + * + * If @flags & VEC_FLAG_TERMINATE @vec has null terminator at vec[n], and will + * null terminate vec[n - dups] + */ +int aa_vec_unique(struct aa_profile **vec, int n, int flags) +{ + int i, dups = 0; + + AA_BUG(n < 1); + AA_BUG(!vec); + + /* vecs are usually small and inorder, have a fallback for larger */ + if (n > 8) { + sort(vec, n, sizeof(struct aa_profile *), sort_cmp, NULL); + dups = unique(vec, n); + goto out; + } + + /* insertion sort + unique in one */ + for (i = 1; i < n; i++) { + struct aa_profile *tmp = vec[i]; + int pos, j; + + for (pos = i - 1 - dups; pos >= 0; pos--) { + int res = profile_cmp(vec[pos], tmp); + + if (res == 0) { + /* drop duplicate entry */ + aa_put_profile(tmp); + dups++; + goto continue_outer; + } else if (res < 0) + break; + } + /* pos is at entry < tmp, or index -1. Set to insert pos */ + pos++; + + for (j = i - dups; j > pos; j--) + vec[j] = vec[j - 1]; + vec[pos] = tmp; +continue_outer: + ; + } + + AA_BUG(dups < 0); + +out: + if (flags & VEC_FLAG_TERMINATE) + vec[n - dups] = NULL; + + return dups; +} + + +static void label_destroy(struct aa_label *label) +{ + struct aa_label *tmp; + + AA_BUG(!label); + + if (!label_isprofile(label)) { + struct aa_profile *profile; + struct label_it i; + + aa_put_str(label->hname); + + label_for_each(i, label, profile) { + aa_put_profile(profile); + label->vec[i.i] = (struct aa_profile *) + (LABEL_POISON + (long) i.i); + } + } + + if (rcu_dereference_protected(label->proxy->label, true) == label) + rcu_assign_pointer(label->proxy->label, NULL); + + aa_free_secid(label->secid); + + tmp = rcu_dereference_protected(label->proxy->label, true); + if (tmp == label) + rcu_assign_pointer(label->proxy->label, NULL); + + aa_put_proxy(label->proxy); + label->proxy = (struct aa_proxy *) PROXY_POISON + 1; +} + +void aa_label_free(struct aa_label *label) +{ + if (!label) + return; + + label_destroy(label); + kfree(label); +} + +static void label_free_switch(struct aa_label *label) +{ + if (label->flags & FLAG_NS_COUNT) + aa_free_ns(labels_ns(label)); + else if (label_isprofile(label)) + aa_free_profile(labels_profile(label)); + else + aa_label_free(label); +} + +static void label_free_rcu(struct rcu_head *head) +{ + struct aa_label *label = container_of(head, struct aa_label, rcu); + + if (label->flags & FLAG_IN_TREE) + (void) aa_label_remove(label); + label_free_switch(label); +} + +void aa_label_kref(struct kref *kref) +{ + struct aa_label *label = container_of(kref, struct aa_label, count); + struct aa_ns *ns = labels_ns(label); + + if (!ns) { + /* never live, no rcu callback needed, just using the fn */ + label_free_switch(label); + return; + } + /* TODO: update labels_profile macro so it works here */ + AA_BUG(label_isprofile(label) && + on_list_rcu(&label->vec[0]->base.profiles)); + AA_BUG(label_isprofile(label) && + on_list_rcu(&label->vec[0]->base.list)); + + /* TODO: if compound label and not stale add to reclaim cache */ + call_rcu(&label->rcu, label_free_rcu); +} + +static void label_free_or_put_new(struct aa_label *label, struct aa_label *new) +{ + if (label != new) + /* need to free directly to break circular ref with proxy */ + aa_label_free(new); + else + aa_put_label(new); +} + +bool aa_label_init(struct aa_label *label, int size) +{ + AA_BUG(!label); + AA_BUG(size < 1); + + label->secid = aa_alloc_secid(); + if (label->secid == AA_SECID_INVALID) + return false; + + label->size = size; /* doesn't include null */ + label->vec[size] = NULL; /* null terminate */ + 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 + * @proxy: proxy to use OR null if to allocate a new one + * @gfp: memory allocation type + * + * Returns: new label + * else NULL if failed + */ +struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) +{ + struct aa_label *new; + + AA_BUG(size < 1); + + /* + 1 for null terminator entry on vec */ + new = kzalloc(sizeof(*new) + sizeof(struct aa_profile *) * (size + 1), + gfp); + AA_DEBUG("%s (%p)\n", __func__, new); + if (!new) + goto fail; + + if (!aa_label_init(new, size)) + goto fail; + + if (!proxy) { + proxy = aa_alloc_proxy(new, gfp); + if (!proxy) + goto fail; + } else + aa_get_proxy(proxy); + /* just set new's proxy, don't redirect proxy here if it was passed in*/ + new->proxy = proxy; + + return new; + +fail: + kfree(new); + + return NULL; +} + + +/** + * 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) +{ + AA_BUG(!b); + + if (a == b) + return 0; + + return vec_cmp(a->vec, a->size, b->vec, b->size); +} + +/* helper fn for label_for_each_confined */ +int aa_label_next_confined(struct aa_label *label, int i) +{ + AA_BUG(!label); + AA_BUG(i < 0); + + for (; i < label->size; i++) { + if (!profile_unconfined(label->vec[i])) + return i; + } + + return i; +} + +/** + * aa_label_next_not_in_set - return the next profile of @sub not in @set + * @I: label iterator + * @set: label to test against + * @sub: label to if is subset of @set + * + * Returns: profile in @sub that is not in @set, with iterator set pos after + * else NULL if @sub is a subset of @set + */ +struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, + struct aa_label *set, + struct aa_label *sub) +{ + AA_BUG(!set); + AA_BUG(!I); + AA_BUG(I->i < 0); + AA_BUG(I->i > set->size); + AA_BUG(!sub); + AA_BUG(I->j < 0); + AA_BUG(I->j > sub->size); + + while (I->j < sub->size && I->i < set->size) { + int res = profile_cmp(sub->vec[I->j], set->vec[I->i]); + + if (res == 0) { + (I->j)++; + (I->i)++; + } else if (res > 0) + (I->i)++; + else + return sub->vec[(I->j)++]; + } + + if (I->j < sub->size) + return sub->vec[(I->j)++]; + + return NULL; +} + +/** + * aa_label_is_subset - test if @sub is a subset of @set + * @set: label to test against + * @sub: label to test if is subset of @set + * + * Returns: true if @sub is subset of @set + * else false + */ +bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) +{ + struct label_it i = { }; + + AA_BUG(!set); + AA_BUG(!sub); + + if (sub == set) + return true; + + return __aa_label_next_not_in_set(&i, set, sub) == NULL; +} + + + +/** + * __label_remove - remove @label from the label set + * @l: label to remove + * @new: label to redirect to + * + * Requires: labels_set(@label)->lock write_lock + * Returns: true if the label was in the tree and removed + */ +static bool __label_remove(struct aa_label *label, struct aa_label *new) +{ + struct aa_labelset *ls = labels_set(label); + + AA_BUG(!ls); + AA_BUG(!label); + AA_BUG(!write_is_locked(&ls->lock)); + + if (new) + __aa_proxy_redirect(label, new); + + if (!label_is_stale(label)) + __label_make_stale(label); + + if (label->flags & FLAG_IN_TREE) { + rb_erase(&label->node, &ls->root); + label->flags &= ~FLAG_IN_TREE; + return true; + } + + return false; +} + +/** + * __label_replace - replace @old with @new in label set + * @old: label to remove from label set + * @new: label to replace @old with + * + * Requires: labels_set(@old)->lock write_lock + * valid ref count be held on @new + * Returns: true if @old was in set and replaced by @new + * + * Note: current implementation requires label set be order in such a way + * that @new directly replaces @old position in the set (ie. + * using pointer comparison of the label address would not work) + */ +static bool __label_replace(struct aa_label *old, struct aa_label *new) +{ + struct aa_labelset *ls = labels_set(old); + + AA_BUG(!ls); + AA_BUG(!old); + AA_BUG(!new); + AA_BUG(!write_is_locked(&ls->lock)); + AA_BUG(new->flags & FLAG_IN_TREE); + + if (!label_is_stale(old)) + __label_make_stale(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; +} + +/** + * __label_insert - attempt to insert @l into a label set + * @ls: set of labels to insert @l into (NOT NULL) + * @label: new label to insert (NOT NULL) + * @replace: whether insertion should replace existing entry that is not stale + * + * Requires: @ls->lock + * caller to hold a valid ref on l + * if @replace is true l has a preallocated proxy associated + * Returns: @l if successful in inserting @l - with additional refcount + * else ref counted equivalent label that is already in the set, + * the else condition only happens if @replace is false + */ +static struct aa_label *__label_insert(struct aa_labelset *ls, + struct aa_label *label, bool replace) +{ + struct rb_node **new, *parent = NULL; + + AA_BUG(!ls); + AA_BUG(!label); + AA_BUG(labels_set(label) != ls); + AA_BUG(!write_is_locked(&ls->lock)); + AA_BUG(label->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(label, this); + + parent = *new; + if (result == 0) { + /* !__aa_get_label means queued for destruction, + * so replace in place, however the label has + * died before the replacement so do not share + * the proxy + */ + if (!replace && !label_is_stale(this)) { + if (__aa_get_label(this)) + return this; + } else + __proxy_share(this, label); + AA_BUG(!__label_replace(this, label)); + return aa_get_label(label); + } else if (result < 0) + new = &((*new)->rb_left); + else /* (result > 0) */ + new = &((*new)->rb_right); + } + + /* Add new node and rebalance tree. */ + rb_link_node(&label->node, parent, new); + rb_insert_color(&label->node, &ls->root); + label->flags |= FLAG_IN_TREE; + + return aa_get_label(label); +} + +/** + * __vec_find - find label that matches @vec in label set + * @vec: vec of profiles to find matching label for (NOT NULL) + * @n: length of @vec + * + * Requires: @vec_labelset(vec) lock held + * caller to hold a valid ref on l + * + * Returns: ref counted @label if matching label is in tree + * ref counted label that is equiv to @l in tree + * else NULL if @vec equiv is not in tree + */ +static struct aa_label *__vec_find(struct aa_profile **vec, int n) +{ + struct rb_node *node; + + AA_BUG(!vec); + AA_BUG(!*vec); + AA_BUG(n <= 0); + + node = vec_labelset(vec, n)->root.rb_node; + while (node) { + struct aa_label *this = rb_entry(node, struct aa_label, node); + int result = vec_cmp(this->vec, this->size, vec, n); + + if (result > 0) + node = node->rb_left; + else if (result < 0) + node = node->rb_right; + else + return __aa_get_label(this); + } + + return NULL; +} + +/** + * __label_find - find label @label in label set + * @label: label to find (NOT NULL) + * + * Requires: labels_set(@label)->lock held + * caller to hold a valid ref on l + * + * Returns: ref counted @label if @label is in tree OR + * ref counted label that is equiv to @label in tree + * else NULL if @label or equiv is not in tree + */ +static struct aa_label *__label_find(struct aa_label *label) +{ + AA_BUG(!label); + + return __vec_find(label->vec, label->size); +} + + +/** + * aa_label_remove - remove a label from the labelset + * @label: label to remove + * + * Returns: true if @label was removed from the tree + * else @label was not in tree so it could not be removed + */ +bool aa_label_remove(struct aa_label *label) +{ + struct aa_labelset *ls = labels_set(label); + unsigned long flags; + bool res; + + AA_BUG(!ls); + + write_lock_irqsave(&ls->lock, flags); + res = __label_remove(label, ns_unconfined(labels_ns(label))); + write_unlock_irqrestore(&ls->lock, flags); + + return res; +} + +/** + * aa_label_replace - replace a label @old with a new version @new + * @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_label *old, struct aa_label *new) +{ + unsigned long flags; + bool res; + + if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) { + write_lock_irqsave(&labels_set(old)->lock, flags); + if (old->proxy != new->proxy) + __proxy_share(old, new); + else + __aa_proxy_redirect(old, new); + res = __label_replace(old, new); + write_unlock_irqrestore(&labels_set(old)->lock, flags); + } else { + struct aa_label *l; + struct aa_labelset *ls = labels_set(old); + + write_lock_irqsave(&ls->lock, flags); + res = __label_remove(old, new); + if (labels_ns(old) != labels_ns(new)) { + write_unlock_irqrestore(&ls->lock, flags); + ls = labels_set(new); + write_lock_irqsave(&ls->lock, flags); + } + l = __label_insert(ls, new, true); + res = (l == new); + write_unlock_irqrestore(&ls->lock, flags); + aa_put_label(l); + } + + return res; +} + +/** + * vec_find - find label @l in label set + * @vec: array of profiles to find equiv label for (NOT NULL) + * @n: length of @vec + * + * Returns: refcounted label if @vec equiv is in tree + * else NULL if @vec equiv is not in tree + */ +static struct aa_label *vec_find(struct aa_profile **vec, int n) +{ + struct aa_labelset *ls; + struct aa_label *label; + unsigned long flags; + + AA_BUG(!vec); + AA_BUG(!*vec); + AA_BUG(n <= 0); + + ls = vec_labelset(vec, n); + read_lock_irqsave(&ls->lock, flags); + label = __vec_find(vec, n); + read_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/* requires sort and merge done first */ +static struct aa_label *vec_create_and_insert_label(struct aa_profile **vec, + int len, gfp_t gfp) +{ + struct aa_label *label = NULL; + struct aa_labelset *ls; + unsigned long flags; + struct aa_label *new; + int i; + + AA_BUG(!vec); + + if (len == 1) + return aa_get_label(&vec[0]->label); + + ls = labels_set(&vec[len - 1]->label); + + /* TODO: enable when read side is lockless + * check if label exists before taking locks + */ + new = aa_label_alloc(len, NULL, gfp); + if (!new) + return NULL; + + for (i = 0; i < len; i++) + new->vec[i] = aa_get_profile(vec[i]); + + write_lock_irqsave(&ls->lock, flags); + label = __label_insert(ls, new, false); + write_unlock_irqrestore(&ls->lock, flags); + label_free_or_put_new(label, new); + + return label; +} + +struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, + gfp_t gfp) +{ + struct aa_label *label = vec_find(vec, len); + + if (label) + return label; + + return vec_create_and_insert_label(vec, len, gfp); +} + +/** + * aa_label_find - find label @label in label set + * @label: label to find (NOT NULL) + * + * Requires: caller to hold a valid ref on l + * + * Returns: refcounted @label if @label is in tree + * refcounted label that is equiv to @label in tree + * else NULL if @label or equiv is not in tree + */ +struct aa_label *aa_label_find(struct aa_label *label) +{ + AA_BUG(!label); + + return vec_find(label->vec, label->size); +} + + +/** + * aa_label_insert - insert label @label into @ls or return existing label + * @ls - labelset to insert @label into + * @label - label to insert + * + * Requires: caller to hold a valid ref on @label + * + * Returns: ref counted @label if successful in inserting @label + * else ref counted equivalent label that is already in the set + */ +struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label) +{ + struct aa_label *l; + unsigned long flags; + + AA_BUG(!ls); + AA_BUG(!label); + + /* check if label exists before taking lock */ + if (!label_is_stale(label)) { + read_lock_irqsave(&ls->lock, flags); + l = __label_find(label); + read_unlock_irqrestore(&ls->lock, flags); + if (l) + return l; + } + + write_lock_irqsave(&ls->lock, flags); + l = __label_insert(ls, label, false); + write_unlock_irqrestore(&ls->lock, flags); + + return l; +} + + +/** + * aa_label_next_in_merge - find the next profile when merging @a and @b + * @I: label iterator + * @a: label to merge + * @b: label to merge + * + * Returns: next profile + * else null if no more profiles + */ +struct aa_profile *aa_label_next_in_merge(struct label_it *I, + struct aa_label *a, + struct aa_label *b) +{ + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!I); + AA_BUG(I->i < 0); + AA_BUG(I->i > a->size); + AA_BUG(I->j < 0); + AA_BUG(I->j > b->size); + + if (I->i < a->size) { + if (I->j < b->size) { + int res = profile_cmp(a->vec[I->i], b->vec[I->j]); + + if (res > 0) + return b->vec[(I->j)++]; + if (res == 0) + (I->j)++; + } + + return a->vec[(I->i)++]; + } + + if (I->j < b->size) + return b->vec[(I->j)++]; + + return NULL; +} + +/** + * label_merge_cmp - cmp of @a merging with @b against @z for set ordering + * @a: label to merge then compare (NOT NULL) + * @b: label to merge then compare (NOT NULL) + * @z: label to compare merge against (NOT NULL) + * + * Assumes: using the most recent versions of @a, @b, and @z + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int label_merge_cmp(struct aa_label *a, struct aa_label *b, + struct aa_label *z) +{ + struct aa_profile *p = NULL; + struct label_it i = { }; + int k; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!z); + + for (k = 0; + k < z->size && (p = aa_label_next_in_merge(&i, a, b)); + k++) { + int res = profile_cmp(p, z->vec[k]); + + if (res != 0) + return res; + } + + if (p) + return 1; + else if (k < z->size) + return -1; + return 0; +} + +/** + * label_merge_insert - create a new label by merging @a and @b + * @new: preallocated label to merge into (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: preallocated proxy + * + * Returns: ref counted label either @new if merge is unique + * @a if @b is a subset of @a + * @b if @a is a subset of @b + * + * NOTE: will not use @new if the merge results in @new == @a or @b + * + * Must be used within labelset write lock to avoid racing with + * setting labels stale. + */ +static struct aa_label *label_merge_insert(struct aa_label *new, + struct aa_label *a, + struct aa_label *b) +{ + struct aa_label *label; + struct aa_labelset *ls; + struct aa_profile *next; + struct label_it i; + unsigned long flags; + int k = 0, invcount = 0; + bool stale = false; + + AA_BUG(!a); + AA_BUG(a->size < 0); + AA_BUG(!b); + AA_BUG(b->size < 0); + AA_BUG(!new); + AA_BUG(new->size < a->size + b->size); + + label_for_each_in_merge(i, a, b, next) { + AA_BUG(!next); + if (profile_is_stale(next)) { + new->vec[k] = aa_get_newest_profile(next); + AA_BUG(!new->vec[k]->label.proxy); + AA_BUG(!new->vec[k]->label.proxy->label); + if (next->label.proxy != new->vec[k]->label.proxy) + invcount++; + k++; + stale = true; + } else + new->vec[k++] = aa_get_profile(next); + } + /* set to actual size which is <= allocated len */ + new->size = k; + new->vec[k] = NULL; + + if (invcount) { + new->size -= aa_vec_unique(&new->vec[0], new->size, + VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (new->size == 1) { + label = aa_get_label(&new->vec[0]->label); + return label; + } + } else if (!stale) { + /* + * merge could be same as a || b, note: it is not possible + * for new->size == a->size == b->size unless a == b + */ + if (k == a->size) + return aa_get_label(a); + else if (k == b->size) + return aa_get_label(b); + } + if (vec_unconfined(new->vec, new->size)) + new->flags |= FLAG_UNCONFINED; + ls = labels_set(new); + write_lock_irqsave(&ls->lock, flags); + label = __label_insert(labels_set(new), new, false); + write_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/** + * labelset_of_merge - find which labelset a merged label should be inserted + * @a: label to merge and insert + * @b: label to merge and insert + * + * Returns: labelset that the merged label should be inserted into + */ +static struct aa_labelset *labelset_of_merge(struct aa_label *a, + struct aa_label *b) +{ + struct aa_ns *nsa = labels_ns(a); + struct aa_ns *nsb = labels_ns(b); + + if (ns_cmp(nsa, nsb) <= 0) + return &nsa->labels; + return &nsb->labels; +} + +/** + * __label_find_merge - find label that is equiv to merge of @a and @b + * @ls: set of labels to search (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: ls->lock read_lock held + * + * Returns: ref counted label that is equiv to merge of @a and @b + * else NULL if merge of @a and @b is not in set + */ +static struct aa_label *__label_find_merge(struct aa_labelset *ls, + struct aa_label *a, + struct aa_label *b) +{ + struct rb_node *node; + + AA_BUG(!ls); + AA_BUG(!a); + AA_BUG(!b); + + if (a == b) + return __label_find(a); + + node = ls->root.rb_node; + while (node) { + struct aa_label *this = container_of(node, struct aa_label, + node); + int result = label_merge_cmp(a, b, this); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return __aa_get_label(this); + } + + return NULL; +} + + +/** + * aa_label_find_merge - find label that is equiv to merge of @a and @b + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: labels be fully constructed with a valid ns + * + * Returns: ref counted label that is equiv to merge of @a and @b + * else NULL if merge of @a and @b is not in set + */ +struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b) +{ + struct aa_labelset *ls; + struct aa_label *label, *ar = NULL, *br = NULL; + unsigned long flags; + + AA_BUG(!a); + AA_BUG(!b); + + if (label_is_stale(a)) + a = ar = aa_get_newest_label(a); + if (label_is_stale(b)) + b = br = aa_get_newest_label(b); + ls = labelset_of_merge(a, b); + read_lock_irqsave(&ls->lock, flags); + label = __label_find_merge(ls, a, b); + read_unlock_irqrestore(&ls->lock, flags); + aa_put_label(ar); + aa_put_label(br); + + return label; +} + +/** + * aa_label_merge - attempt to insert new merged label of @a and @b + * @ls: set of labels to insert label into (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * @gfp: memory allocation type + * + * Requires: caller to hold valid refs on @a and @b + * labels be fully constructed with a valid ns + * + * Returns: ref counted new label if successful in inserting merge of a & b + * else ref counted equivalent label that is already in the set. + * else NULL if could not create label (-ENOMEM) + */ +struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, + gfp_t gfp) +{ + struct aa_label *label = NULL; + + AA_BUG(!a); + AA_BUG(!b); + + if (a == b) + return aa_get_newest_label(a); + + /* TODO: enable when read side is lockless + * check if label exists before taking locks + if (!label_is_stale(a) && !label_is_stale(b)) + label = aa_label_find_merge(a, b); + */ + + if (!label) { + struct aa_label *new; + + a = aa_get_newest_label(a); + b = aa_get_newest_label(b); + + /* could use label_merge_len(a, b), but requires double + * comparison for small savings + */ + new = aa_label_alloc(a->size + b->size, NULL, gfp); + if (!new) + goto out; + + label = label_merge_insert(new, a, b); + label_free_or_put_new(label, new); +out: + aa_put_label(a); + aa_put_label(b); + } + + return label; +} + +static inline bool label_is_visible(struct aa_profile *profile, + struct aa_label *label) +{ + return aa_ns_visible(profile->ns, labels_ns(label), true); +} + +/* match a profile and its associated ns component if needed + * Assumes visibility test has already been done. + * If a subns profile is not to be matched should be prescreened with + * visibility test. + */ +static inline unsigned int match_component(struct aa_profile *profile, + struct aa_profile *tp, + unsigned int state) +{ + const char *ns_name; + + if (profile->ns == tp->ns) + return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); + + /* try matching with namespace name and then profile */ + ns_name = aa_ns_name(profile->ns, tp->ns, true); + state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); + state = aa_dfa_match(profile->policy.dfa, state, ns_name); + state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); + return aa_dfa_match(profile->policy.dfa, state, tp->base. |