apparmor: audit policy ns specified in policy load
Verify that profiles in a load set specify the same policy ns and audit the name of the policy ns that policy is being loaded for. Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
parent
5ac8c355ae
commit
04dc715e24
@ -23,6 +23,7 @@ struct aa_load_ent {
|
|||||||
struct aa_profile *new;
|
struct aa_profile *new;
|
||||||
struct aa_profile *old;
|
struct aa_profile *old;
|
||||||
struct aa_profile *rename;
|
struct aa_profile *rename;
|
||||||
|
const char *ns_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
void aa_load_ent_free(struct aa_load_ent *ent);
|
void aa_load_ent_free(struct aa_load_ent *ent);
|
||||||
|
@ -819,7 +819,7 @@ ssize_t aa_replace_profiles(struct aa_ns *view, bool noreplace,
|
|||||||
struct aa_ns *ns = NULL;
|
struct aa_ns *ns = NULL;
|
||||||
struct aa_load_ent *ent, *tmp;
|
struct aa_load_ent *ent, *tmp;
|
||||||
int op = OP_PROF_REPL;
|
int op = OP_PROF_REPL;
|
||||||
ssize_t error;
|
ssize_t count, error;
|
||||||
LIST_HEAD(lh);
|
LIST_HEAD(lh);
|
||||||
|
|
||||||
/* released below */
|
/* released below */
|
||||||
@ -827,14 +827,40 @@ ssize_t aa_replace_profiles(struct aa_ns *view, bool noreplace,
|
|||||||
if (error)
|
if (error)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
/* released below */
|
/* ensure that profiles are all for the same ns
|
||||||
ns = aa_prepare_ns(view, ns_name);
|
* TODO: update locking to remove this constaint. All profiles in
|
||||||
if (!ns) {
|
* the load set must succeed as a set or the load will
|
||||||
error = audit_policy(__aa_current_profile(), op, GFP_KERNEL,
|
* fail. Sort ent list and take ns locks in hierarchy order
|
||||||
NULL, ns_name,
|
*/
|
||||||
"failed to prepare namespace", -ENOMEM);
|
count = 0;
|
||||||
goto free;
|
list_for_each_entry(ent, &lh, list) {
|
||||||
|
if (ns_name) {
|
||||||
|
if (ent->ns_name &&
|
||||||
|
strcmp(ent->ns_name, ns_name) != 0) {
|
||||||
|
info = "policy load has mixed namespaces";
|
||||||
|
error = -EACCES;
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
|
} else if (ent->ns_name) {
|
||||||
|
if (count) {
|
||||||
|
info = "policy load has mixed namespaces";
|
||||||
|
error = -EACCES;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
ns_name = ent->ns_name;
|
||||||
|
} else
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (ns_name) {
|
||||||
|
ns = aa_prepare_ns(view, ns_name);
|
||||||
|
if (IS_ERR(ns)) {
|
||||||
|
info = "failed to prepare namespace";
|
||||||
|
error = PTR_ERR(ns);
|
||||||
|
ns = NULL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
ns = aa_get_ns(view);
|
||||||
|
|
||||||
mutex_lock(&ns->lock);
|
mutex_lock(&ns->lock);
|
||||||
/* setup parent and ns info */
|
/* setup parent and ns info */
|
||||||
@ -964,7 +990,8 @@ ssize_t aa_replace_profiles(struct aa_ns *view, bool noreplace,
|
|||||||
|
|
||||||
/* audit cause of failure */
|
/* audit cause of failure */
|
||||||
op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
|
op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
|
||||||
audit_policy(__aa_current_profile(), op, GFP_KERNEL, NULL,
|
fail:
|
||||||
|
audit_policy(__aa_current_profile(), op, GFP_KERNEL, ns_name,
|
||||||
ent->new->base.hname, info, error);
|
ent->new->base.hname, info, error);
|
||||||
/* audit status that rest of profiles in the atomic set failed too */
|
/* audit status that rest of profiles in the atomic set failed too */
|
||||||
info = "valid profile in failed atomic policy load";
|
info = "valid profile in failed atomic policy load";
|
||||||
@ -975,10 +1002,9 @@ ssize_t aa_replace_profiles(struct aa_ns *view, bool noreplace,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
|
op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
|
||||||
audit_policy(__aa_current_profile(), op, GFP_KERNEL, NULL,
|
audit_policy(__aa_current_profile(), op, GFP_KERNEL, ns_name,
|
||||||
tmp->new->base.hname, info, error);
|
tmp->new->base.hname, info, error);
|
||||||
}
|
}
|
||||||
free:
|
|
||||||
list_for_each_entry_safe(ent, tmp, &lh, list) {
|
list_for_each_entry_safe(ent, tmp, &lh, list) {
|
||||||
list_del_init(&ent->list);
|
list_del_init(&ent->list);
|
||||||
aa_load_ent_free(ent);
|
aa_load_ent_free(ent);
|
||||||
@ -1005,6 +1031,7 @@ ssize_t aa_remove_profiles(struct aa_ns *view, char *fqname, size_t size)
|
|||||||
struct aa_ns *root = NULL, *ns = NULL;
|
struct aa_ns *root = NULL, *ns = NULL;
|
||||||
struct aa_profile *profile = NULL;
|
struct aa_profile *profile = NULL;
|
||||||
const char *name = fqname, *info = NULL;
|
const char *name = fqname, *info = NULL;
|
||||||
|
char *ns_name = NULL;
|
||||||
ssize_t error = 0;
|
ssize_t error = 0;
|
||||||
|
|
||||||
if (*fqname == 0) {
|
if (*fqname == 0) {
|
||||||
@ -1016,7 +1043,6 @@ ssize_t aa_remove_profiles(struct aa_ns *view, char *fqname, size_t size)
|
|||||||
root = view;
|
root = view;
|
||||||
|
|
||||||
if (fqname[0] == ':') {
|
if (fqname[0] == ':') {
|
||||||
char *ns_name;
|
|
||||||
name = aa_split_fqname(fqname, &ns_name);
|
name = aa_split_fqname(fqname, &ns_name);
|
||||||
/* released below */
|
/* released below */
|
||||||
ns = aa_find_ns(root, ns_name);
|
ns = aa_find_ns(root, ns_name);
|
||||||
@ -1050,7 +1076,7 @@ ssize_t aa_remove_profiles(struct aa_ns *view, char *fqname, size_t size)
|
|||||||
|
|
||||||
/* don't fail removal if audit fails */
|
/* don't fail removal if audit fails */
|
||||||
(void) audit_policy(__aa_current_profile(), OP_PROF_RM, GFP_KERNEL,
|
(void) audit_policy(__aa_current_profile(), OP_PROF_RM, GFP_KERNEL,
|
||||||
NULL, name, info, error);
|
ns_name, name, info, error);
|
||||||
aa_put_ns(ns);
|
aa_put_ns(ns);
|
||||||
aa_put_profile(profile);
|
aa_put_profile(profile);
|
||||||
return size;
|
return size;
|
||||||
@ -1061,6 +1087,6 @@ ssize_t aa_remove_profiles(struct aa_ns *view, char *fqname, size_t size)
|
|||||||
|
|
||||||
fail:
|
fail:
|
||||||
(void) audit_policy(__aa_current_profile(), OP_PROF_RM, GFP_KERNEL,
|
(void) audit_policy(__aa_current_profile(), OP_PROF_RM, GFP_KERNEL,
|
||||||
NULL, name, info, error);
|
ns_name, name, info, error);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
|
|||||||
/**
|
/**
|
||||||
* audit_iface - do audit message for policy unpacking/load/replace/remove
|
* audit_iface - do audit message for policy unpacking/load/replace/remove
|
||||||
* @new: profile if it has been allocated (MAYBE NULL)
|
* @new: profile if it has been allocated (MAYBE NULL)
|
||||||
|
* @ns_name: name of the ns the profile is to be loaded to (MAY BE NULL)
|
||||||
* @name: name of the profile being manipulated (MAYBE NULL)
|
* @name: name of the profile being manipulated (MAYBE NULL)
|
||||||
* @info: any extra info about the failure (MAYBE NULL)
|
* @info: any extra info about the failure (MAYBE NULL)
|
||||||
* @e: buffer position info
|
* @e: buffer position info
|
||||||
@ -98,14 +99,16 @@ static void audit_cb(struct audit_buffer *ab, void *va)
|
|||||||
*
|
*
|
||||||
* Returns: %0 or error
|
* Returns: %0 or error
|
||||||
*/
|
*/
|
||||||
static int audit_iface(struct aa_profile *new, const char *name,
|
static int audit_iface(struct aa_profile *new, const char *ns_name,
|
||||||
const char *info, struct aa_ext *e, int error)
|
const char *name, const char *info, struct aa_ext *e,
|
||||||
|
int error)
|
||||||
{
|
{
|
||||||
struct aa_profile *profile = __aa_current_profile();
|
struct aa_profile *profile = __aa_current_profile();
|
||||||
struct common_audit_data sa;
|
struct common_audit_data sa;
|
||||||
struct apparmor_audit_data aad = {0,};
|
struct apparmor_audit_data aad = {0,};
|
||||||
sa.type = LSM_AUDIT_DATA_NONE;
|
sa.type = LSM_AUDIT_DATA_NONE;
|
||||||
sa.aad = &aad;
|
sa.aad = &aad;
|
||||||
|
aad.iface.ns = ns_name;
|
||||||
if (e)
|
if (e)
|
||||||
aad.iface.pos = e->pos - e->start;
|
aad.iface.pos = e->pos - e->start;
|
||||||
aad.iface.target = new;
|
aad.iface.target = new;
|
||||||
@ -486,19 +489,32 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
|
|||||||
*
|
*
|
||||||
* NOTE: unpack profile sets audit struct if there is a failure
|
* NOTE: unpack profile sets audit struct if there is a failure
|
||||||
*/
|
*/
|
||||||
static struct aa_profile *unpack_profile(struct aa_ext *e)
|
static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||||
{
|
{
|
||||||
struct aa_profile *profile = NULL;
|
struct aa_profile *profile = NULL;
|
||||||
const char *name = NULL;
|
const char *tmpname, *tmpns = NULL, *name = NULL;
|
||||||
|
size_t ns_len;
|
||||||
int i, error = -EPROTO;
|
int i, error = -EPROTO;
|
||||||
kernel_cap_t tmpcap;
|
kernel_cap_t tmpcap;
|
||||||
u32 tmp;
|
u32 tmp;
|
||||||
|
|
||||||
|
*ns_name = NULL;
|
||||||
|
|
||||||
/* check that we have the right struct being passed */
|
/* check that we have the right struct being passed */
|
||||||
if (!unpack_nameX(e, AA_STRUCT, "profile"))
|
if (!unpack_nameX(e, AA_STRUCT, "profile"))
|
||||||
goto fail;
|
goto fail;
|
||||||
if (!unpack_str(e, &name, NULL))
|
if (!unpack_str(e, &name, NULL))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
if (*name == '\0')
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len);
|
||||||
|
if (tmpns) {
|
||||||
|
*ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL);
|
||||||
|
if (!*ns_name)
|
||||||
|
goto fail;
|
||||||
|
name = tmpname;
|
||||||
|
}
|
||||||
|
|
||||||
profile = aa_alloc_profile(name, GFP_KERNEL);
|
profile = aa_alloc_profile(name, GFP_KERNEL);
|
||||||
if (!profile)
|
if (!profile)
|
||||||
@ -646,7 +662,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
|
|||||||
name = NULL;
|
name = NULL;
|
||||||
else if (!name)
|
else if (!name)
|
||||||
name = "unknown";
|
name = "unknown";
|
||||||
audit_iface(profile, name, "failed to unpack profile", e, error);
|
audit_iface(profile, NULL, name, "failed to unpack profile", e,
|
||||||
|
error);
|
||||||
aa_free_profile(profile);
|
aa_free_profile(profile);
|
||||||
|
|
||||||
return ERR_PTR(error);
|
return ERR_PTR(error);
|
||||||
@ -669,7 +686,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
|
|||||||
/* get the interface version */
|
/* get the interface version */
|
||||||
if (!unpack_u32(e, &e->version, "version")) {
|
if (!unpack_u32(e, &e->version, "version")) {
|
||||||
if (required) {
|
if (required) {
|
||||||
audit_iface(NULL, NULL, "invalid profile format",
|
audit_iface(NULL, NULL, NULL, "invalid profile format",
|
||||||
e, error);
|
e, error);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@ -680,15 +697,21 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
|
|||||||
* Mask off everything that is not kernel abi version
|
* Mask off everything that is not kernel abi version
|
||||||
*/
|
*/
|
||||||
if (VERSION_LT(e->version, v5) && VERSION_GT(e->version, v7)) {
|
if (VERSION_LT(e->version, v5) && VERSION_GT(e->version, v7)) {
|
||||||
audit_iface(NULL, NULL, "unsupported interface version",
|
audit_iface(NULL, NULL, NULL, "unsupported interface version",
|
||||||
e, error);
|
e, error);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* read the namespace if present */
|
/* read the namespace if present */
|
||||||
if (unpack_str(e, &name, "namespace")) {
|
if (unpack_str(e, &name, "namespace")) {
|
||||||
|
if (*name == '\0') {
|
||||||
|
audit_iface(NULL, NULL, NULL, "invalid namespace name",
|
||||||
|
e, error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
if (*ns && strcmp(*ns, name))
|
if (*ns && strcmp(*ns, name))
|
||||||
audit_iface(NULL, NULL, "invalid ns change", e, error);
|
audit_iface(NULL, NULL, NULL, "invalid ns change", e,
|
||||||
|
error);
|
||||||
else if (!*ns)
|
else if (!*ns)
|
||||||
*ns = name;
|
*ns = name;
|
||||||
}
|
}
|
||||||
@ -730,7 +753,7 @@ static int verify_profile(struct aa_profile *profile)
|
|||||||
if (profile->file.dfa &&
|
if (profile->file.dfa &&
|
||||||
!verify_dfa_xindex(profile->file.dfa,
|
!verify_dfa_xindex(profile->file.dfa,
|
||||||
profile->file.trans.size)) {
|
profile->file.trans.size)) {
|
||||||
audit_iface(profile, NULL, "Invalid named transition",
|
audit_iface(profile, NULL, NULL, "Invalid named transition",
|
||||||
NULL, -EPROTO);
|
NULL, -EPROTO);
|
||||||
return -EPROTO;
|
return -EPROTO;
|
||||||
}
|
}
|
||||||
@ -744,6 +767,7 @@ void aa_load_ent_free(struct aa_load_ent *ent)
|
|||||||
aa_put_profile(ent->rename);
|
aa_put_profile(ent->rename);
|
||||||
aa_put_profile(ent->old);
|
aa_put_profile(ent->old);
|
||||||
aa_put_profile(ent->new);
|
aa_put_profile(ent->new);
|
||||||
|
kfree(ent->ns_name);
|
||||||
kzfree(ent);
|
kzfree(ent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -782,13 +806,14 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
|
|||||||
|
|
||||||
*ns = NULL;
|
*ns = NULL;
|
||||||
while (e.pos < e.end) {
|
while (e.pos < e.end) {
|
||||||
|
char *ns_name = NULL;
|
||||||
void *start;
|
void *start;
|
||||||
error = verify_header(&e, e.pos == e.start, ns);
|
error = verify_header(&e, e.pos == e.start, ns);
|
||||||
if (error)
|
if (error)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
start = e.pos;
|
start = e.pos;
|
||||||
profile = unpack_profile(&e);
|
profile = unpack_profile(&e, &ns_name);
|
||||||
if (IS_ERR(profile)) {
|
if (IS_ERR(profile)) {
|
||||||
error = PTR_ERR(profile);
|
error = PTR_ERR(profile);
|
||||||
goto fail;
|
goto fail;
|
||||||
@ -810,6 +835,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ent->new = profile;
|
ent->new = profile;
|
||||||
|
ent->ns_name = ns_name;
|
||||||
list_add_tail(&ent->list, lh);
|
list_add_tail(&ent->list, lh);
|
||||||
}
|
}
|
||||||
udata->abi = e.version & K_ABI_MASK;
|
udata->abi = e.version & K_ABI_MASK;
|
||||||
|
Loading…
Reference in New Issue
Block a user