mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 22:51:35 +00:00
d8889d49e4
Now that file contexts have been moved into file, and task context fns() and data have been split from the context, only the cred context remains in context.h so rename to cred.h to better reflect what it deals with. Signed-off-by: John Johansen <john.johansen@canonical.com>
2137 lines
51 KiB
C
2137 lines
51 KiB
C
/*
|
|
* 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/cred.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));
|
|
RCU_INIT_POINTER(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);
|
|
lockdep_assert_held_exclusive(&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);
|
|
lockdep_assert_held_exclusive(&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);
|
|
lockdep_assert_held_exclusive(&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);
|
|
lockdep_assert_held_exclusive(&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.hname);
|
|
}
|
|
|
|
/**
|
|
* label_compound_match - find perms for full compound label
|
|
* @profile: profile to find perms for
|
|
* @label: label to check access permissions for
|
|
* @start: state to start match in
|
|
* @subns: whether to do permission checks on components in a subns
|
|
* @request: permissions to request
|
|
* @perms: perms struct to set
|
|
*
|
|
* Returns: 0 on success else ERROR
|
|
*
|
|
* For the label A//&B//&C this does the perm match for A//&B//&C
|
|
* @perms should be preinitialized with allperms OR a previous permission
|
|
* check to be stacked.
|
|
*/
|
|
static int label_compound_match(struct aa_profile *profile,
|
|
struct aa_label *label,
|
|
unsigned int state, bool subns, u32 request,
|
|
struct aa_perms *perms)
|
|
{
|
|
struct aa_profile *tp;
|
|
struct label_it i;
|
|
|
|
/* find first subcomponent that is visible */
|
|
label_for_each(i, label, tp) {
|
|
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
|
continue;
|
|
state = match_component(profile, tp, state);
|
|
if (!state)
|
|
goto fail;
|
|
goto next;
|
|
}
|
|
|
|
/* no component visible */
|
|
*perms = allperms;
|
|
return 0;
|
|
|
|
next:
|
|
label_for_each_cont(i, label, tp) {
|
|
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
|
continue;
|
|
state = aa_dfa_match(profile->policy.dfa, state, "//&");
|
|
state = match_component(profile, tp, state);
|
|
if (!state)
|
|
goto fail;
|
|
}
|
|
aa_compute_perms(profile->policy.dfa, state, perms);
|
|
aa_apply_modes_to_perms(profile, perms);
|
|
if ((perms->allow & request) != request)
|
|
return -EACCES;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
*perms = nullperms;
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* label_components_match - find perms for all subcomponents of a label
|
|
* @profile: profile to find perms for
|
|
* @label: label to check access permissions for
|
|
* @start: state to start match in
|
|
* @subns: whether to do permission checks on components in a subns
|
|
* @request: permissions to request
|
|
* @perms: an initialized perms struct to add accumulation to
|
|
*
|
|
* Returns: 0 on success else ERROR
|
|
*
|
|
* For the label A//&B//&C this does the perm match for each of A and B and C
|
|
* @perms should be preinitialized with allperms OR a previous permission
|
|
* check to be stacked.
|
|
*/
|
|
static int label_components_match(struct aa_profile *profile,
|
|
struct aa_label *label, unsigned int start,
|
|
bool subns, u32 request,
|
|
struct aa_perms *perms)
|
|
{
|
|
struct aa_profile *tp;
|
|
struct label_it i;
|
|
struct aa_perms tmp;
|
|
unsigned int state = 0;
|
|
|
|
/* find first subcomponent to test */
|
|
label_for_each(i, label, tp) {
|
|
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
|
continue;
|
|
state = match_component(profile, tp, start);
|
|
if (!state)
|
|
goto fail;
|
|
goto next;
|
|
}
|
|
|
|
/* no subcomponents visible - no change in perms */
|
|
return 0;
|
|
|
|
next:
|
|
aa_compute_perms(profile->policy.dfa, state, &tmp);
|
|
aa_apply_modes_to_perms(profile, &tmp);
|
|
aa_perms_accum(perms, &tmp);
|
|
label_for_each_cont(i, label, tp) {
|
|
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
|
continue;
|
|
state = match_component(profile, tp, start);
|
|
if (!state)
|
|
goto fail;
|
|
aa_compute_perms(profile->policy.dfa, state, &tmp);
|
|
aa_apply_modes_to_perms(profile, &tmp);
|
|
aa_perms_accum(perms, &tmp);
|
|
}
|
|
|
|
if ((perms->allow & request) != request)
|
|
return -EACCES;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
*perms = nullperms;
|
|
return -EACCES;
|
|
}
|
|
|
|
/**
|
|
* aa_label_match - do a multi-component label match
|
|
* @profile: profile to match against (NOT NULL)
|
|
* @label: label to match (NOT NULL)
|
|
* @state: state to start in
|
|
* @subns: whether to match subns components
|
|
* @request: permission request
|
|
* @perms: Returns computed perms (NOT NULL)
|
|
*
|
|
* Returns: the state the match finished in, may be the none matching state
|
|
*/
|
|
int aa_label_match(struct aa_profile *profile, struct aa_label *label,
|
|
unsigned int state, bool subns, u32 request,
|
|
struct aa_perms *perms)
|
|
{
|
|
int error = label_compound_match(profile, label, state, subns, request,
|
|
perms);
|
|
if (!error)
|
|
return error;
|
|
|
|
*perms = allperms;
|
|
return label_components_match(profile, label, state, subns, request,
|
|
perms);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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_ns *ns, struct aa_label *label, gfp_t gfp)
|
|
{
|
|
struct aa_labelset *ls;
|
|
unsigned long flags;
|
|
char __counted *name;
|
|
bool res = false;
|
|
|
|
AA_BUG(!ns);
|
|
AA_BUG(!label);
|
|
|
|
if (label->hname || labels_ns(label) != ns)
|
|
return res;
|
|
|
|
if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, 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 use_label_hname(struct aa_ns *ns, struct aa_label *label,
|
|
int flags)
|
|
{
|
|
if (label->hname && (!ns || labels_ns(label) == ns) &&
|
|
!(flags & ~FLAG_SHOW_MODE))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* helper macro for snprint routines */
|
|
#define update_for_len(total, len, size, str) \
|
|
do { \
|
|
AA_BUG(len < 0); \
|
|
total += len; \
|
|
len = min(len, size); \
|
|
size -= len; \
|
|
str += len; \
|
|
} while (0)
|
|
|
|
/**
|
|
* aa_profile_snxprint - print a profile name to a buffer
|
|
* @str: buffer to write to. (MAY BE NULL if @size == 0)
|
|
* @size: size of buffer
|
|
* @view: namespace profile is being viewed from
|
|
* @profile: profile to view (NOT NULL)
|
|
* @flags: whether to include the mode string
|
|
* @prev_ns: last ns printed when used in compound print
|
|
*
|
|
* 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
|
|
*/
|
|
static int aa_profile_snxprint(char *str, size_t size, struct aa_ns *view,
|
|
struct aa_profile *profile, int flags,
|
|
struct aa_ns **prev_ns)
|
|
{
|
|
const char *ns_name = NULL;
|
|
|
|
AA_BUG(!str && size != 0);
|
|
AA_BUG(!profile);
|
|
|
|
if (!view)
|
|
view = profiles_ns(profile);
|
|
|
|
if (view != profile->ns &&
|
|
(!prev_ns || (*prev_ns != profile->ns))) {
|
|
if (prev_ns)
|
|
*prev_ns = profile->ns;
|
|
ns_name = aa_ns_name(view, profile->ns,
|
|
flags & FLAG_VIEW_SUBNS);
|
|
if (ns_name == aa_hidden_ns_name) {
|
|
if (flags & FLAG_HIDDEN_UNCONFINED)
|
|
return snprintf(str, size, "%s", "unconfined");
|
|
return snprintf(str, size, "%s", ns_name);
|
|
}
|
|
}
|
|
|
|
if ((flags & FLAG_SHOW_MODE) && profile != profile->ns->unconfined) {
|
|
const char *modestr = aa_profile_mode_names[profile->mode];
|
|
|
|
if (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 (ns_name)
|
|
return snprintf(str, size, ":%s:%s", ns_name,
|
|
profile->base.hname);
|
|
return snprintf(str, size, "%s", profile->base.hname);
|
|
}
|
|
|
|
static const char *label_modename(struct aa_ns *ns, struct aa_label *label,
|
|
int flags)
|
|
{
|
|
struct aa_profile *profile;
|
|
struct label_it i;
|
|
int mode = -1, count = 0;
|
|
|
|
label_for_each(i, label, profile) {
|
|
if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) {
|
|
if (profile->mode == APPARMOR_UNCONFINED)
|
|
/* special case unconfined so stacks with
|
|
* unconfined don't report as mixed. ie.
|
|
* profile_foo//&:ns1:unconfined (mixed)
|
|
*/
|
|
continue;
|
|
count++;
|
|
if (mode == -1)
|
|
mode = profile->mode;
|
|
else if (mode != profile->mode)
|
|
return "mixed";
|
|
}
|
|
}
|
|
|
|
if (count == 0)
|
|
return "-";
|
|
if (mode == -1)
|
|
/* everything was unconfined */
|
|
mode = APPARMOR_UNCONFINED;
|
|
|
|
return aa_profile_mode_names[mode];
|
|
}
|
|
|
|
/* if any visible label is not unconfined the display_mode returns true */
|
|
static inline bool display_mode(struct aa_ns *ns, struct aa_label *label,
|
|
int flags)
|
|
{
|
|
if ((flags & FLAG_SHOW_MODE)) {
|
|
struct aa_profile *profile;
|
|
struct label_it i;
|
|
|
|
label_for_each(i, label, profile) {
|
|
if (aa_ns_visible(ns, profile->ns,
|
|
flags & FLAG_VIEW_SUBNS) &&
|
|
profile != profile->ns->unconfined)
|
|
return true;
|
|
}
|
|
/* only ns->unconfined in set of profiles in ns */
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* aa_label_snxprint - 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
|
|
* @label: label to view (NOT NULL)
|
|
* @flags: 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 visible "---" will be used.
|
|
*/
|
|
int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns,
|
|
struct aa_label *label, int flags)
|
|
{
|
|
struct aa_profile *profile;
|
|
struct aa_ns *prev_ns = NULL;
|
|
struct label_it i;
|
|
int count = 0, total = 0;
|
|
size_t len;
|
|
|
|
AA_BUG(!str && size != 0);
|
|
AA_BUG(!label);
|
|
|
|
if (flags & FLAG_ABS_ROOT) {
|
|
ns = root_ns;
|
|
len = snprintf(str, size, "=");
|
|
update_for_len(total, len, size, str);
|
|
} else if (!ns) {
|
|
ns = labels_ns(label);
|
|
}
|
|
|
|
label_for_each(i, label, profile) {
|
|
if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) {
|
|
if (count > 0) {
|
|
len = snprintf(str, size, "//&");
|
|
update_for_len(total, len, size, str);
|
|
}
|
|
len = aa_profile_snxprint(str, size, ns, profile,
|
|
flags & FLAG_VIEW_SUBNS,
|
|
&prev_ns);
|
|
update_for_len(total, len, size, str);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count == 0) {
|
|
if (flags & FLAG_HIDDEN_UNCONFINED)
|
|
return snprintf(str, size, "%s", "unconfined");
|
|
return snprintf(str, size, "%s", aa_hidden_ns_name);
|
|
}
|
|
|
|
/* count == 1 && ... is for backwards compat where the mode
|
|
* is not displayed for 'unconfined' in the current ns
|
|
*/
|
|
if (display_mode(ns, label, flags)) {
|
|
len = snprintf(str, size, " (%s)",
|
|
label_modename(ns, label, flags));
|
|
update_for_len(total, len, size, str);
|
|
}
|
|
|
|
return total;
|
|
}
|
|
#undef update_for_len
|
|
|
|
/**
|
|
* aa_label_asxprint - allocate a string buffer and print label into it
|
|
* @strp: Returns - the allocated buffer with the label name. (NOT NULL)
|
|
* @ns: namespace profile is being viewed from
|
|
* @label: label to view (NOT NULL)
|
|
* @flags: flags controlling what label info is printed
|
|
* @gfp: kernel memory allocation type
|
|
*
|
|
* Returns: size of name written or would be written if larger than
|
|
* available buffer
|
|
*/
|
|
int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label,
|
|
int flags, gfp_t gfp)
|
|
{
|
|
int size;
|
|
|
|
AA_BUG(!strp);
|
|
AA_BUG(!label);
|
|
|
|
size = aa_label_snxprint(NULL, 0, ns, label, flags);
|
|
if (size < 0)
|
|
return size;
|
|
|
|
*strp = kmalloc(size + 1, gfp);
|
|
if (!*strp)
|
|
return -ENOMEM;
|
|
return aa_label_snxprint(*strp, size + 1, ns, label, flags);
|
|
}
|
|
|
|
/**
|
|
* aa_label_acntsxprint - 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
|
|
* @label: label to view (NOT NULL)
|
|
* @flags: flags controlling what label info is printed
|
|
* @gfp: kernel memory allocation type
|
|
*
|
|
* Returns: size of name written or would be written if larger than
|
|
* available buffer
|
|
*/
|
|
int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns,
|
|
struct aa_label *label, int flags, gfp_t gfp)
|
|
{
|
|
int size;
|
|
|
|
AA_BUG(!strp);
|
|
AA_BUG(!label);
|
|
|
|
size = aa_label_snxprint(NULL, 0, ns, label, flags);
|
|
if (size < 0)
|
|
return size;
|
|
|
|
*strp = aa_str_alloc(size + 1, gfp);
|
|
if (!*strp)
|
|
return -ENOMEM;
|
|
return aa_label_snxprint(*strp, size + 1, ns, label, flags);
|
|
}
|
|
|
|
|
|
void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,
|
|
struct aa_label *label, int flags, gfp_t gfp)
|
|
{
|
|
const char *str;
|
|
char *name = NULL;
|
|
int len;
|
|
|
|
AA_BUG(!ab);
|
|
AA_BUG(!label);
|
|
|
|
if (!use_label_hname(ns, label, flags) ||
|
|
display_mode(ns, label, flags)) {
|
|
len = aa_label_asxprint(&name, ns, label, flags, gfp);
|
|
if (len == -1) {
|
|
AA_DEBUG("label print error");
|
|
return;
|
|
}
|
|
str = name;
|
|
} else {
|
|
str = (char *) label->hname;
|
|
len = strlen(str);
|
|
}
|
|
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_xprint(struct seq_file *f, struct aa_ns *ns,
|
|
struct aa_label *label, int flags, gfp_t gfp)
|
|
{
|
|
AA_BUG(!f);
|
|
AA_BUG(!label);
|
|
|
|
if (!use_label_hname(ns, label, flags)) {
|
|
char *str;
|
|
int len;
|
|
|
|
len = aa_label_asxprint(&str, ns, label, flags, gfp);
|
|
if (len == -1) {
|
|
AA_DEBUG("label print error");
|
|
return;
|
|
}
|
|
seq_printf(f, "%s", str);
|
|
kfree(str);
|
|
} else if (display_mode(ns, label, flags))
|
|
seq_printf(f, "%s (%s)", label->hname,
|
|
label_modename(ns, label, flags));
|
|
else
|
|
seq_printf(f, "%s", label->hname);
|
|
}
|
|
|
|
void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
|
|
gfp_t gfp)
|
|
{
|
|
AA_BUG(!label);
|
|
|
|
if (!use_label_hname(ns, label, flags)) {
|
|
char *str;
|
|
int len;
|
|
|
|
len = aa_label_asxprint(&str, ns, label, flags, gfp);
|
|
if (len == -1) {
|
|
AA_DEBUG("label print error");
|
|
return;
|
|
}
|
|
pr_info("%s", str);
|
|
kfree(str);
|
|
} else if (display_mode(ns, label, flags))
|
|
pr_info("%s (%s)", label->hname,
|
|
label_modename(ns, label, flags));
|
|
else
|
|
pr_info("%s", label->hname);
|
|
}
|
|
|
|
void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp)
|
|
{
|
|
struct aa_ns *ns = aa_get_current_ns();
|
|
|
|
aa_label_xaudit(ab, ns, label, FLAG_VIEW_SUBNS, gfp);
|
|
aa_put_ns(ns);
|
|
}
|
|
|
|
void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp)
|
|
{
|
|
struct aa_ns *ns = aa_get_current_ns();
|
|
|
|
aa_label_seq_xprint(f, ns, label, FLAG_VIEW_SUBNS, gfp);
|
|
aa_put_ns(ns);
|
|
}
|
|
|
|
void aa_label_printk(struct aa_label *label, gfp_t gfp)
|
|
{
|
|
struct aa_ns *ns = aa_get_current_ns();
|
|
|
|
aa_label_xprintk(ns, label, FLAG_VIEW_SUBNS, gfp);
|
|
aa_put_ns(ns);
|
|
}
|
|
|
|
static int label_count_strn_entries(const char *str, size_t n)
|
|
{
|
|
const char *end = str + n;
|
|
const char *split;
|
|
int count = 1;
|
|
|
|
AA_BUG(!str);
|
|
|
|
for (split = aa_label_strn_split(str, end - str);
|
|
split;
|
|
split = aa_label_strn_split(str, end - str)) {
|
|
count++;
|
|
str = split + 3;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* ensure stacks with components like
|
|
* :ns:A//&B
|
|
* have :ns: applied to both 'A' and 'B' by making the lookup relative
|
|
* to the base if the lookup specifies an ns, else making the stacked lookup
|
|
* relative to the last embedded ns in the string.
|
|
*/
|
|
static struct aa_profile *fqlookupn_profile(struct aa_label *base,
|
|
struct aa_label *currentbase,
|
|
const char *str, size_t n)
|
|
{
|
|
const char *first = skipn_spaces(str, n);
|
|
|
|
if (first && *first == ':')
|
|
return aa_fqlookupn_profile(base, str, n);
|
|
|
|
return aa_fqlookupn_profile(currentbase, str, n);
|
|
}
|
|
|
|
/**
|
|
* aa_label_strn_parse - parse, validate and convert a text string to a label
|
|
* @base: base label to use for lookups (NOT NULL)
|
|
* @str: null terminated text string (NOT NULL)
|
|
* @n: length of str to parse, will stop at \0 if encountered before n
|
|
* @gfp: allocation type
|
|
* @create: true if should create compound labels if they don't exist
|
|
* @force_stack: true if should stack even if no leading &
|
|
*
|
|
* Returns: the matching refcounted label if present
|
|
* else ERRPTR
|
|
*/
|
|
struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str,
|
|
size_t n, gfp_t gfp, bool create,
|
|
bool force_stack)
|
|
{
|
|
DEFINE_VEC(profile, vec);
|
|
struct aa_label *label, *currbase = base;
|
|
int i, len, stack = 0, error;
|
|
const char *end = str + n;
|
|
const char *split;
|
|
|
|
AA_BUG(!base);
|
|
AA_BUG(!str);
|
|
|
|
str = skipn_spaces(str, n);
|
|
if (str == NULL || (*str == '=' && base != &root_ns->unconfined->label))
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
len = label_count_strn_entries(str, end - str);
|
|
if (*str == '&' || force_stack) {
|
|
/* stack on top of base */
|
|
stack = base->size;
|
|
len += stack;
|
|
if (*str == '&')
|
|
str++;
|
|
}
|
|
|
|
error = vec_setup(profile, vec, len, gfp);
|
|
if (error)
|
|
return ERR_PTR(error);
|
|
|
|
for (i = 0; i < stack; i++)
|
|
vec[i] = aa_get_profile(base->vec[i]);
|
|
|
|
for (split = aa_label_strn_split(str, end - str), i = stack;
|
|
split && i < len; i++) {
|
|
vec[i] = fqlookupn_profile(base, currbase, str, split - str);
|
|
if (!vec[i])
|
|
goto fail;
|
|
/*
|
|
* if component specified a new ns it becomes the new base
|
|
* so that subsequent lookups are relative to it
|
|
*/
|
|
if (vec[i]->ns != labels_ns(currbase))
|
|
currbase = &vec[i]->label;
|
|
str = split + 3;
|
|
split = aa_label_strn_split(str, end - str);
|
|
}
|
|
/* last element doesn't have a split */
|
|
if (i < len) {
|
|
vec[i] = fqlookupn_profile(base, currbase, str, end - str);
|
|
if (!vec[i])
|
|
goto fail;
|
|
}
|
|
if (len == 1)
|
|
/* no need to free vec as len < LOCAL_VEC_ENTRIES */
|
|
return &vec[0]->label;
|
|
|
|
len -= aa_vec_unique(vec, len, VEC_FLAG_TERMINATE);
|
|
/* TODO: deal with reference labels */
|
|
if (len == 1) {
|
|
label = aa_get_label(&vec[0]->label);
|
|
goto out;
|
|
}
|
|
|
|
if (create)
|
|
label = aa_vec_find_or_create_label(vec, len, gfp);
|
|
else
|
|
label = vec_find(vec, len);
|
|
if (!label)
|
|
goto fail;
|
|
|
|
out:
|
|
/* use adjusted len from after vec_unique, not original */
|
|
vec_cleanup(profile, vec, len);
|
|
return label;
|
|
|
|
fail:
|
|
label = ERR_PTR(-ENOENT);
|
|
goto out;
|
|
}
|
|
|
|
struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
|
|
gfp_t gfp, bool create, bool force_stack)
|
|
{
|
|
return aa_label_strn_parse(base, str, strlen(str), gfp, create,
|
|
force_stack);
|
|
}
|
|
|
|
/**
|
|
* 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_BUG(!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);
|
|
|
|
if (labels_ns(this) != root_ns)
|
|
__label_remove(this,
|
|
ns_unconfined(labels_ns(this)->parent));
|
|
else
|
|
__label_remove(this, NULL);
|
|
}
|
|
write_unlock_irqrestore(&ls->lock, flags);
|
|
}
|
|
|
|
/*
|
|
* @ls: labelset to init (NOT NULL)
|
|
*/
|
|
void aa_labelset_init(struct aa_labelset *ls)
|
|
{
|
|
AA_BUG(!ls);
|
|
|
|
rwlock_init(&ls->lock);
|
|
ls->root = RB_ROOT;
|
|
}
|
|
|
|
static struct aa_label *labelset_next_stale(struct aa_labelset *ls)
|
|
{
|
|
struct aa_label *label;
|
|
struct rb_node *node;
|
|
unsigned long flags;
|
|
|
|
AA_BUG(!ls);
|
|
|
|
read_lock_irqsave(&ls->lock, flags);
|
|
|
|
__labelset_for_each(ls, node) {
|
|
label = rb_entry(node, struct aa_label, node);
|
|
if ((label_is_stale(label) ||
|
|
vec_is_stale(label->vec, label->size)) &&
|
|
__aa_get_label(label))
|
|
goto out;
|
|
|
|
}
|
|
label = NULL;
|
|
|
|
out:
|
|
read_unlock_irqrestore(&ls->lock, flags);
|
|
|
|
return label;
|
|
}
|
|
|
|
/**
|
|
* __label_update - insert updated version of @label into labelset
|
|
* @label - the label to update/repace
|
|
*
|
|
* Returns: new label that is up to date
|
|
* else NULL on failure
|
|
*
|
|
* Requires: @ns lock be held
|
|
*
|
|
* Note: worst case is the stale @label does not get updated and has
|
|
* to be updated at a later time.
|
|
*/
|
|
static struct aa_label *__label_update(struct aa_label *label)
|
|
{
|
|
struct aa_label *new, *tmp;
|
|
struct aa_labelset *ls;
|
|
unsigned long flags;
|
|
int i, invcount = 0;
|
|
|
|
AA_BUG(!label);
|
|
AA_BUG(!mutex_is_locked(&labels_ns(label)->lock));
|
|
|
|
new = aa_label_alloc(label->size, label->proxy, GFP_KERNEL);
|
|
if (!new)
|
|
return NULL;
|
|
|
|
/*
|
|
* while holding the ns_lock will stop profile replacement, removal,
|
|
* and label updates, label merging and removal can be occurring
|
|
*/
|
|
ls = labels_set(label);
|
|
write_lock_irqsave(&ls->lock, flags);
|
|
for (i = 0; i < label->size; i++) {
|
|
AA_BUG(!label->vec[i]);
|
|
new->vec[i] = aa_get_newest_profile(label->vec[i]);
|
|
AA_BUG(!new->vec[i]);
|
|
AA_BUG(!new->vec[i]->label.proxy);
|
|
AA_BUG(!new->vec[i]->label.proxy->label);
|
|
if (new->vec[i]->label.proxy != label->vec[i]->label.proxy)
|
|
invcount++;
|
|
}
|
|
|
|
/* updated stale label by being removed/renamed from labelset */
|
|
if (invcount) {
|
|
new->size -= aa_vec_unique(&new->vec[0], new->size,
|
|
VEC_FLAG_TERMINATE);
|
|
/* TODO: deal with reference labels */
|
|
if (new->size == 1) {
|
|
tmp = aa_get_label(&new->vec[0]->label);
|
|
AA_BUG(tmp == label);
|
|
goto remove;
|
|
}
|
|
if (labels_set(label) != labels_set(new)) {
|
|
write_unlock_irqrestore(&ls->lock, flags);
|
|
tmp = aa_label_insert(labels_set(new), new);
|
|
write_lock_irqsave(&ls->lock, flags);
|
|
goto remove;
|
|
}
|
|
} else
|
|
AA_BUG(labels_ns(label) != labels_ns(new));
|
|
|
|
tmp = __label_insert(labels_set(label), new, true);
|
|
remove:
|
|
/* ensure label is removed, and redirected correctly */
|
|
__label_remove(label, tmp);
|
|
write_unlock_irqrestore(&ls->lock, flags);
|
|
label_free_or_put_new(tmp, new);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
/**
|
|
* __labelset_update - update labels in @ns
|
|
* @ns: namespace to update labels in (NOT NULL)
|
|
*
|
|
* Requires: @ns lock be held
|
|
*
|
|
* Walk the labelset ensuring that all labels are up to date and valid
|
|
* Any label that has a stale component is marked stale and replaced and
|
|
* by an updated version.
|
|
*
|
|
* If failures happen due to memory pressures then stale labels will
|
|
* be left in place until the next pass.
|
|
*/
|
|
static void __labelset_update(struct aa_ns *ns)
|
|
{
|
|
struct aa_label *label;
|
|
|
|
AA_BUG(!ns);
|
|
AA_BUG(!mutex_is_locked(&ns->lock));
|
|
|
|
do {
|
|
label = labelset_next_stale(&ns->labels);
|
|
if (label) {
|
|
struct aa_label *l = __label_update(label);
|
|
|
|
aa_put_label(l);
|
|
aa_put_label(label);
|
|
}
|
|
} while (label);
|
|
}
|
|
|
|
/**
|
|
* __aa_labelset_udate_subtree - update all labels with a stale component
|
|
* @ns: ns to start update at (NOT NULL)
|
|
*
|
|
* Requires: @ns lock be held
|
|
*
|
|
* Invalidates labels based on @p in @ns and any children namespaces.
|
|
*/
|
|
void __aa_labelset_update_subtree(struct aa_ns *ns)
|
|
{
|
|
struct aa_ns *child;
|
|
|
|
AA_BUG(!ns);
|
|
AA_BUG(!mutex_is_locked(&ns->lock));
|
|
|
|
__labelset_update(ns);
|
|
|
|
list_for_each_entry(child, &ns->sub_ns, base.list) {
|
|
mutex_lock_nested(&child->lock, child->level);
|
|
__aa_labelset_update_subtree(child);
|
|
mutex_unlock(&child->lock);
|
|
}
|
|
}
|