Commit dd51c848 authored by John Johansen's avatar John Johansen

apparmor: provide base for multiple profiles to be replaced at once

previously profiles had to be loaded one at a time, which could result
in cases where a replacement of a set would partially succeed, and then fail
resulting in inconsistent policy.

Allow multiple profiles to replaced "atomically" so that the replacement
either succeeds or fails for the entire set of profiles.
Signed-off-by: default avatarJohn Johansen <john.johansen@canonical.com>
parent 9d910a3b
...@@ -199,6 +199,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { ...@@ -199,6 +199,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = {
}; };
static struct aa_fs_entry aa_fs_entry_policy[] = { static struct aa_fs_entry aa_fs_entry_policy[] = {
AA_FS_FILE_BOOLEAN("set_load", 1),
{} {}
}; };
......
...@@ -15,6 +15,18 @@ ...@@ -15,6 +15,18 @@
#ifndef __POLICY_INTERFACE_H #ifndef __POLICY_INTERFACE_H
#define __POLICY_INTERFACE_H #define __POLICY_INTERFACE_H
struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns); #include <linux/list.h>
struct aa_load_ent {
struct list_head list;
struct aa_profile *new;
struct aa_profile *old;
struct aa_profile *rename;
};
void aa_load_ent_free(struct aa_load_ent *ent);
struct aa_load_ent *aa_load_ent_alloc(void);
int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns);
#endif /* __POLICY_INTERFACE_H */ #endif /* __POLICY_INTERFACE_H */
...@@ -472,45 +472,6 @@ static void __list_remove_profile(struct aa_profile *profile) ...@@ -472,45 +472,6 @@ static void __list_remove_profile(struct aa_profile *profile)
aa_put_profile(profile); aa_put_profile(profile);
} }
/**
* __replace_profile - replace @old with @new on a list
* @old: profile to be replaced (NOT NULL)
* @new: profile to replace @old with (NOT NULL)
*
* Will duplicate and refcount elements that @new inherits from @old
* and will inherit @old children.
*
* refcount @new for list, put @old list refcount
*
* Requires: namespace list lock be held, or list not be shared
*/
static void __replace_profile(struct aa_profile *old, struct aa_profile *new)
{
struct aa_policy *policy;
struct aa_profile *child, *tmp;
if (old->parent)
policy = &old->parent->base;
else
policy = &old->ns->base;
/* released when @new is freed */
new->parent = aa_get_profile(old->parent);
new->ns = aa_get_namespace(old->ns);
__list_add_profile(&policy->profiles, new);
/* inherit children */
list_for_each_entry_safe(child, tmp, &old->base.profiles, base.list) {
aa_put_profile(child->parent);
child->parent = aa_get_profile(new);
/* list refcount transferred to @new*/
list_move(&child->base.list, &new->base.profiles);
}
/* released by free_profile */
old->replacedby = aa_get_profile(new);
__list_remove_profile(old);
}
static void __profile_list_release(struct list_head *head); static void __profile_list_release(struct list_head *head);
/** /**
...@@ -952,25 +913,6 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, ...@@ -952,25 +913,6 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,
return 0; return 0;
} }
/**
* __add_new_profile - simple wrapper around __list_add_profile
* @ns: namespace that profile is being added to (NOT NULL)
* @policy: the policy container to add the profile to (NOT NULL)
* @profile: profile to add (NOT NULL)
*
* add a profile to a list and do other required basic allocations
*/
static void __add_new_profile(struct aa_namespace *ns, struct aa_policy *policy,
struct aa_profile *profile)
{
if (policy != &ns->base)
/* released on profile replacement or free_profile */
profile->parent = aa_get_profile((struct aa_profile *) policy);
__list_add_profile(&policy->profiles, profile);
/* released on free_profile */
profile->ns = aa_get_namespace(ns);
}
/** /**
* aa_audit_policy - Do auditing of policy changes * aa_audit_policy - Do auditing of policy changes
* @op: policy operation being performed * @op: policy operation being performed
...@@ -1019,6 +961,109 @@ bool aa_may_manage_policy(int op) ...@@ -1019,6 +961,109 @@ bool aa_may_manage_policy(int op)
return 1; return 1;
} }
static struct aa_profile *__list_lookup_parent(struct list_head *lh,
struct aa_profile *profile)
{
const char *base = hname_tail(profile->base.hname);
long len = base - profile->base.hname;
struct aa_load_ent *ent;
/* parent won't have trailing // so remove from len */
if (len <= 2)
return NULL;
len -= 2;
list_for_each_entry(ent, lh, list) {
if (ent->new == profile)
continue;
if (strncmp(ent->new->base.hname, profile->base.hname, len) ==
0 && ent->new->base.hname[len] == 0)
return ent->new;
}
return NULL;
}
/**
* __replace_profile - replace @old with @new on a list
* @old: profile to be replaced (NOT NULL)
* @new: profile to replace @old with (NOT NULL)
*
* Will duplicate and refcount elements that @new inherits from @old
* and will inherit @old children.
*
* refcount @new for list, put @old list refcount
*
* Requires: namespace list lock be held, or list not be shared
*/
static void __replace_profile(struct aa_profile *old, struct aa_profile *new)
{
struct aa_profile *child, *tmp;
if (!list_empty(&old->base.profiles)) {
LIST_HEAD(lh);
list_splice_init(&old->base.profiles, &lh);
list_for_each_entry_safe(child, tmp, &lh, base.list) {
struct aa_profile *p;
list_del_init(&child->base.list);
p = __find_child(&new->base.profiles, child->base.name);
if (p) {
/* @p replaces @child */
__replace_profile(child, p);
continue;
}
/* inherit @child and its children */
/* TODO: update hname of inherited children */
/* list refcount transferred to @new */
list_add(&child->base.list, &new->base.profiles);
aa_put_profile(child->parent);
child->parent = aa_get_profile(new);
}
}
if (!new->parent)
new->parent = aa_get_profile(old->parent);
/* released by free_profile */
old->replacedby = aa_get_profile(new);
if (list_empty(&new->base.list)) {
/* new is not on a list already */
list_replace_init(&old->base.list, &new->base.list);
aa_get_profile(new);
aa_put_profile(old);
} else
__list_remove_profile(old);
}
/**
* __lookup_replace - lookup replacement information for a profile
* @ns - namespace the lookup occurs in
* @hname - name of profile to lookup
* @noreplace - true if not replacing an existing profile
* @p - Returns: profile to be replaced
* @info - Returns: info string on why lookup failed
*
* Returns: profile to replace (no ref) on success else ptr error
*/
static int __lookup_replace(struct aa_namespace *ns, const char *hname,
bool noreplace, struct aa_profile **p,
const char **info)
{
*p = aa_get_profile(__lookup_profile(&ns->base, hname));
if (*p) {
int error = replacement_allowed(*p, noreplace, info);
if (error) {
*info = "profile can not be replaced";
return error;
}
}
return 0;
}
/** /**
* aa_replace_profiles - replace profile(s) on the profile list * aa_replace_profiles - replace profile(s) on the profile list
* @udata: serialized data stream (NOT NULL) * @udata: serialized data stream (NOT NULL)
...@@ -1033,21 +1078,17 @@ bool aa_may_manage_policy(int op) ...@@ -1033,21 +1078,17 @@ bool aa_may_manage_policy(int op)
*/ */
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
{ {
struct aa_policy *policy;
struct aa_profile *old_profile = NULL, *new_profile = NULL;
struct aa_profile *rename_profile = NULL;
struct aa_namespace *ns = NULL;
const char *ns_name, *name = NULL, *info = NULL; const char *ns_name, *name = NULL, *info = NULL;
struct aa_namespace *ns = NULL;
struct aa_load_ent *ent, *tmp;
int op = OP_PROF_REPL; int op = OP_PROF_REPL;
ssize_t error; ssize_t error;
LIST_HEAD(lh);
/* released below */ /* released below */
new_profile = aa_unpack(udata, size, &ns_name); error = aa_unpack(udata, size, &lh, &ns_name);
if (IS_ERR(new_profile)) { if (error)
error = PTR_ERR(new_profile); goto out;
new_profile = NULL;
goto fail;
}
/* released below */ /* released below */
ns = aa_prepare_namespace(ns_name); ns = aa_prepare_namespace(ns_name);
...@@ -1058,71 +1099,96 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1058,71 +1099,96 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
goto fail; goto fail;
} }
name = new_profile->base.hname;
write_lock(&ns->lock); write_lock(&ns->lock);
/* no ref on policy only use inside lock */ /* setup parent and ns info */
policy = __lookup_parent(ns, new_profile->base.hname); list_for_each_entry(ent, &lh, list) {
struct aa_policy *policy;
if (!policy) {
info = "parent does not exist"; name = ent->new->base.hname;
error = -ENOENT; error = __lookup_replace(ns, ent->new->base.hname, noreplace,
goto audit; &ent->old, &info);
} if (error)
goto fail_lock;
old_profile = __find_child(&policy->profiles, new_profile->base.name);
/* released below */ if (ent->new->rename) {
aa_get_profile(old_profile); error = __lookup_replace(ns, ent->new->rename,
noreplace, &ent->rename,
if (new_profile->rename) { &info);
rename_profile = __lookup_profile(&ns->base, if (error)
new_profile->rename); goto fail_lock;
/* released below */
aa_get_profile(rename_profile);
if (!rename_profile) {
info = "profile to rename does not exist";
name = new_profile->rename;
error = -ENOENT;
goto audit;
} }
}
error = replacement_allowed(old_profile, noreplace, &info);
if (error)
goto audit;
error = replacement_allowed(rename_profile, noreplace, &info); /* released when @new is freed */
if (error) ent->new->ns = aa_get_namespace(ns);
goto audit;
if (ent->old || ent->rename)
audit: continue;
if (!old_profile && !rename_profile)
op = OP_PROF_LOAD; /* no ref on policy only use inside lock */
policy = __lookup_parent(ns, ent->new->base.hname);
if (!policy) {
struct aa_profile *p;
p = __list_lookup_parent(&lh, ent->new);
if (!p) {
error = -ENOENT;
info = "parent does not exist";
name = ent->new->base.hname;
goto fail_lock;
}
ent->new->parent = aa_get_profile(p);
} else if (policy != &ns->base)
/* released on profile replacement or free_profile */
ent->new->parent = aa_get_profile((struct aa_profile *)
policy);
}
error = audit_policy(op, GFP_ATOMIC, name, info, error); /* do actual replacement */
list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list);
op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL;
audit_policy(op, GFP_ATOMIC, ent->new->base.name, NULL, error);
if (ent->old) {
__replace_profile(ent->old, ent->new);
if (ent->rename)
__replace_profile(ent->rename, ent->new);
} else if (ent->rename) {
__replace_profile(ent->rename, ent->new);
} else if (ent->new->parent) {
struct aa_profile *parent;
parent = aa_newest_version(ent->new->parent);
/* parent replaced in this atomic set? */
if (parent != ent->new->parent) {
aa_get_profile(parent);
aa_put_profile(ent->new->parent);
ent->new->parent = parent;
}
__list_add_profile(&parent->base.profiles, ent->new);
} else
__list_add_profile(&ns->base.profiles, ent->new);
if (!error) { aa_load_ent_free(ent);
if (rename_profile)
__replace_profile(rename_profile, new_profile);
if (old_profile)
__replace_profile(old_profile, new_profile);
if (!(old_profile || rename_profile))
__add_new_profile(ns, policy, new_profile);
} }
write_unlock(&ns->lock); write_unlock(&ns->lock);
out: out:
aa_put_namespace(ns); aa_put_namespace(ns);
aa_put_profile(rename_profile);
aa_put_profile(old_profile);
aa_put_profile(new_profile);
if (error) if (error)
return error; return error;
return size; return size;
fail_lock:
write_unlock(&ns->lock);
fail: fail:
error = audit_policy(op, GFP_KERNEL, name, info, error); error = audit_policy(op, GFP_KERNEL, name, info, error);
list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list);
aa_load_ent_free(ent);
}
goto out; goto out;
} }
......
...@@ -333,8 +333,10 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e) ...@@ -333,8 +333,10 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
/* /*
* The dfa is aligned with in the blob to 8 bytes * The dfa is aligned with in the blob to 8 bytes
* from the beginning of the stream. * from the beginning of the stream.
* alignment adjust needed by dfa unpack
*/ */
size_t sz = blob - (char *)e->start; size_t sz = blob - (char *) e->start -
((e->pos - e->start) & 7);
size_t pad = ALIGN(sz, 8) - sz; size_t pad = ALIGN(sz, 8) - sz;
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
TO_ACCEPT2_FLAG(YYTD_DATA32); TO_ACCEPT2_FLAG(YYTD_DATA32);
...@@ -622,29 +624,41 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) ...@@ -622,29 +624,41 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
/** /**
* verify_head - unpack serialized stream header * verify_head - unpack serialized stream header
* @e: serialized data read head (NOT NULL) * @e: serialized data read head (NOT NULL)
* @required: whether the header is required or optional
* @ns: Returns - namespace if one is specified else NULL (NOT NULL) * @ns: Returns - namespace if one is specified else NULL (NOT NULL)
* *
* Returns: error or 0 if header is good * Returns: error or 0 if header is good
*/ */
static int verify_header(struct aa_ext *e, const char **ns) static int verify_header(struct aa_ext *e, int required, const char **ns)
{ {
int error = -EPROTONOSUPPORT; int error = -EPROTONOSUPPORT;
const char *name = NULL;
*ns = NULL;
/* get the interface version */ /* get the interface version */
if (!unpack_u32(e, &e->version, "version")) { if (!unpack_u32(e, &e->version, "version")) {
audit_iface(NULL, NULL, "invalid profile format", e, error); if (required) {
return error; audit_iface(NULL, NULL, "invalid profile format", e,
} error);
return error;
}
/* check that the interface version is currently supported */ /* check that the interface version is currently supported */
if (e->version != 5) { if (e->version != 5) {
audit_iface(NULL, NULL, "unsupported interface version", e, audit_iface(NULL, NULL, "unsupported interface version",
error); e, error);
return error; return error;
}
} }
/* read the namespace if present */ /* read the namespace if present */
if (!unpack_str(e, ns, "namespace")) if (unpack_str(e, &name, "namespace")) {
*ns = NULL; if (*ns && strcmp(*ns, name))
audit_iface(NULL, NULL, "invalid ns change", e, error);
else if (!*ns)
*ns = name;
}
return 0; return 0;
} }
...@@ -693,18 +707,40 @@ static int verify_profile(struct aa_profile *profile) ...@@ -693,18 +707,40 @@ static int verify_profile(struct aa_profile *profile)
return 0; return 0;
} }
void aa_load_ent_free(struct aa_load_ent *ent)
{
if (ent) {
aa_put_profile(ent->rename);
aa_put_profile(ent->old);
aa_put_profile(ent->new);
kzfree(ent);
}
}
struct aa_load_ent *aa_load_ent_alloc(void)
{
struct aa_load_ent *ent = kzalloc(sizeof(*ent), GFP_KERNEL);
if (ent)
INIT_LIST_HEAD(&ent->list);
return ent;
}
/** /**
* aa_unpack - unpack packed binary profile data loaded from user space * aa_unpack - unpack packed binary profile(s) data loaded from user space
* @udata: user data copied to kmem (NOT NULL) * @udata: user data copied to kmem (NOT NULL)
* @size: the size of the user data * @size: the size of the user data
* @lh: list to place unpacked profiles in a aa_repl_ws
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL) * @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
* *
* Unpack user data and return refcounted allocated profile or ERR_PTR * Unpack user data and return refcounted allocated profile(s) stored in
* @lh in order of discovery, with the list chain stored in base.list
* or error
* *
* Returns: profile else error pointer if fails to unpack * Returns: profile(s) on @lh else error pointer if fails to unpack
*/ */
struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns) int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
{ {
struct aa_load_ent *tmp, *ent;
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
int error; int error;
struct aa_ext e = { struct aa_ext e = {
...@@ -713,20 +749,42 @@ struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns) ...@@ -713,20 +749,42 @@ struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns)
.pos = udata, .pos = udata,
}; };
error = verify_header(&e, ns); *ns = NULL;
if (error) while (e.pos < e.end) {
return ERR_PTR(error); error = verify_header(&e, e.pos == e.start, ns);
if (error)
goto fail;
profile = unpack_profile(&e); profile = unpack_profile(&e);
if (IS_ERR(profile)) if (IS_ERR(profile)) {
return profile; error = PTR_ERR(profile);
goto fail;
}
error = verify_profile(profile);
if (error) {
aa_put_profile(profile);
goto fail;
}
ent = aa_load_ent_alloc();
if (!ent) {
error = -ENOMEM;
aa_put_profile(profile);
goto fail;
}
error = verify_profile(profile); ent->new = profile;
if (error) { list_add_tail(&ent->list, lh);
aa_put_profile(profile);
profile = ERR_PTR(error);
} }
/* return refcount */ return 0;
return profile;
fail:
list_for_each_entry_safe(ent, tmp, lh, list) {
list_del_init(&ent->list);
aa_load_ent_free(ent);
}
return error;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment