Make it possible to tie Apparmor profiles to the presence of one or more extended attributes, and optionally their values. An example usecase for this is to automatically transition to a more privileged Apparmor profile if an executable has a valid IMA signature, which can then be appraised by the IMA subsystem.
Signed-off-by: Matthew Garrett <mj...@google.com> Signed-off-by: John Johansen <john.johan...@canonical.com> --- security/apparmor/domain.c | 151 +++++++++++++++++++++++++++++++------ security/apparmor/include/policy.h | 6 ++ security/apparmor/policy.c | 8 ++ security/apparmor/policy_unpack.c | 85 ++++++++++++++++++--- 4 files changed, 216 insertions(+), 34 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 9d1936519cfd..ca8353f3e7a0 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -19,6 +19,7 @@ #include <linux/syscalls.h> #include <linux/tracehook.h> #include <linux/personality.h> +#include <linux/xattr.h> #include "include/audit.h" #include "include/apparmorfs.h" @@ -301,8 +302,57 @@ static int change_profile_perms(struct aa_profile *profile, return label_match(profile, target, stack, start, true, request, perms); } +/** + * aa_xattrs_match - check whether a file matches the xattrs defined in profile + * @bprm: binprm struct for the process to validate + * @profile: profile to match against (NOT NULL) + * + * Returns: number of extended attributes that matched, or < 0 on error + */ +static int aa_xattrs_match(const struct linux_binprm *bprm, + struct aa_profile *profile) +{ + int i; + size_t size; + struct dentry *d; + char *value = NULL; + int value_size = 0, ret = profile->xattr_count; + + if (!bprm || !profile->xattr_count) + return 0; + + d = bprm->file->f_path.dentry; + + for (i = 0; i < profile->xattr_count; i++) { + size = vfs_getxattr_alloc(d, profile->xattrs[i], &value, + value_size, GFP_KERNEL); + if (size < 0) { + ret = -EINVAL; + goto out; + } + + /* Check the xattr value, not just presence */ + if (profile->xattr_lens[i]) { + if (profile->xattr_lens[i] != size) { + ret = -EINVAL; + goto out; + } + + if (memcmp(value, profile->xattr_values[i], size)) { + ret = -EINVAL; + goto out; + } + } + } + +out: + kfree(value); + return ret; +} + /** * __attach_match_ - find an attachment match + * @bprm - binprm structure of transitioning task * @name - to match against (NOT NULL) * @head - profile list to walk (NOT NULL) * @info - info message if there was an error (NOT NULL) @@ -316,11 +366,12 @@ static int change_profile_perms(struct aa_profile *profile, * * Returns: profile or NULL if no match found */ -static struct aa_profile *__attach_match(const char *name, +static struct aa_profile *__attach_match(const struct linux_binprm *bprm, + const char *name, struct list_head *head, const char **info) { - int len = 0; + int len = 0, xattrs = 0; bool conflict = false; struct aa_profile *profile, *candidate = NULL; @@ -329,26 +380,55 @@ static struct aa_profile *__attach_match(const char *name, &profile->label == ns_unconfined(profile->ns)) continue; + /* Find the "best" matching profile. Profiles must + * match the path and extended attributes (if any) + * associated with the file. A more specific path + * match will be preferred over a less specific one, + * and a match with more matching extended attributes + * will be preferred over one with fewer. If the best + * match has both the same level of path specificity + * and the same number of matching extended attributes + * as another profile, signal a conflict and refuse to + * match. + */ if (profile->xmatch) { - if (profile->xmatch_len >= len) { - unsigned int state; - u32 perm; - - state = aa_dfa_match(profile->xmatch, - DFA_START, name); - perm = dfa_user_allow(profile->xmatch, state); - /* any accepting state means a valid match. */ - if (perm & MAY_EXEC) { - if (profile->xmatch_len == len) { + unsigned int state; + u32 perm; + if (profile->xmatch_len < len) + continue; + + state = aa_dfa_match(profile->xmatch, + DFA_START, name); + perm = dfa_user_allow(profile->xmatch, state); + /* any accepting state means a valid match. */ + if (perm & MAY_EXEC) { + int ret = aa_xattrs_match(bprm, profile); + + /* Fail matching if the xattrs don't match */ + if (ret < 0) + continue; + + /* The new match isn't more specific + * than the current best match + */ + if (profile->xmatch_len == len && + ret <= xattrs) { + /* Match is equivalent, so conflict */ + if (ret == xattrs) conflict = true; - continue; - } - candidate = profile; - len = profile->xmatch_len; - conflict = false; + continue; } + + /* Either the same length with more matching + * xattrs, or a longer match + */ + candidate = profile; + len = profile->xmatch_len; + xattrs = ret; + conflict = false; } - } else if (!strcmp(profile->base.name, name)) + } else if (!strcmp(profile->base.name, name) && + aa_xattrs_match(bprm, profile) >= 0) /* exact non-re match, no more searching required */ return profile; } @@ -363,6 +443,7 @@ static struct aa_profile *__attach_match(const char *name, /** * find_attach - do attachment search for unconfined processes + * @bprm - binprm structure of transitioning task * @ns: the current namespace (NOT NULL) * @list: list to search (NOT NULL) * @name: the executable name to match against (NOT NULL) @@ -370,13 +451,14 @@ static struct aa_profile *__attach_match(const char *name, * * Returns: label or NULL if no match found */ -static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list, +static struct aa_label *find_attach(const struct linux_binprm *bprm, + struct aa_ns *ns, struct list_head *list, const char *name, const char **info) { struct aa_profile *profile; rcu_read_lock(); - profile = aa_get_profile(__attach_match(name, list, info)); + profile = aa_get_profile(__attach_match(bprm, name, list, info)); rcu_read_unlock(); return profile ? &profile->label : NULL; @@ -432,6 +514,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, /** * x_to_label - get target label for a given xindex * @profile: current profile (NOT NULL) + * @bprm: binprm structure of transitioning task * @name: name to lookup (NOT NULL) * @xindex: index into x transition table * @lookupname: returns: name used in lookup if one was specified (NOT NULL) @@ -441,6 +524,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, * Returns: refcounted label or NULL if not found available */ static struct aa_label *x_to_label(struct aa_profile *profile, + const struct linux_binprm *bprm, const char *name, u32 xindex, const char **lookupname, const char **info) @@ -468,11 +552,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile, case AA_X_NAME: if (xindex & AA_X_CHILD) /* released by caller */ - new = find_attach(ns, &profile->base.profiles, + new = find_attach(bprm, ns, &profile->base.profiles, name, info); else /* released by caller */ - new = find_attach(ns, &ns->base.profiles, + new = find_attach(bprm, ns, &ns->base.profiles, name, info); *lookupname = name; break; @@ -512,6 +596,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile, bool *secure_exec) { struct aa_label *new = NULL; + struct aa_profile *component; + struct label_it i; const char *info = NULL, *name = NULL, *target = NULL; unsigned int state = profile->file.start; struct aa_perms perms = {}; @@ -536,8 +622,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile, } if (profile_unconfined(profile)) { - new = find_attach(profile->ns, &profile->ns->base.profiles, - name, &info); + new = find_attach(bprm, profile->ns, + &profile->ns->base.profiles, name, &info); if (new) { AA_DEBUG("unconfined attached to new label"); return new; @@ -550,7 +636,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile, state = aa_str_perms(profile->file.dfa, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ - new = x_to_label(profile, name, perms.xindex, &target, &info); + new = x_to_label(profile, bprm, name, perms.xindex, &target, + &info); if (new && new->proxy == profile->label.proxy && info) { /* hack ix fallback - improve how this is detected */ goto audit; @@ -559,6 +646,20 @@ static struct aa_label *profile_transition(struct aa_profile *profile, info = "profile transition not found"; /* remove MAY_EXEC to audit as failure */ perms.allow &= ~MAY_EXEC; + } else { + /* verify that each component's xattr requirements are + * met, and fail execution otherwise + */ + label_for_each(i, new, component) { + if (aa_xattrs_match(bprm, component) < 0) { + error = -EACCES; + info = "required xattrs not present"; + perms.allow &= ~MAY_EXEC; + aa_put_label(new); + new = NULL; + goto audit; + } + } } } else if (COMPLAIN_MODE(profile)) { /* no exec permission - learning mode */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 69440be8aeba..9eab920b68eb 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -150,6 +150,12 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + + int xattr_count; + char **xattrs; + size_t *xattr_lens; + char **xattr_values; + struct aa_rlimit rlimits; struct aa_loaddata *rawdata; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 6ad098aaa01e..437cc917a5eb 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -211,6 +211,7 @@ static void aa_free_data(void *ptr, void *arg) void aa_free_profile(struct aa_profile *profile) { struct rhashtable *rht; + int i; AA_DEBUG("%s(%p)\n", __func__, profile); @@ -228,6 +229,13 @@ void aa_free_profile(struct aa_profile *profile) aa_free_cap_rules(&profile->caps); aa_free_rlimit_rules(&profile->rlimits); + for (i=0; i<profile->xattr_count; i++) { + kzfree(profile->xattrs[i]); + kzfree(profile->xattr_values[i]); + } + kzfree(profile->xattrs); + kzfree(profile->xattr_lens); + kzfree(profile->xattr_values); kzfree(profile->dirname); aa_put_dfa(profile->xmatch); aa_put_dfa(profile->policy.dfa); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index a7578c4d91ee..81c52cd26126 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -205,6 +205,15 @@ static bool inbounds(struct aa_ext *e, size_t size) return (size <= e->end - e->pos); } +static void *kvmemdup(const void *src, size_t len) +{ + void *p = kvmalloc(len, GFP_KERNEL); + + if (p) + memcpy(p, src, len); + return p; +} + /** * aa_u16_chunck - test and do bounds checking for a u16 size based chunk * @e: serialized data read head (NOT NULL) @@ -524,6 +533,68 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) return 0; } +static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile) +{ + void *pos = e->pos; + + if (unpack_nameX(e, AA_STRUCT, "xattrs")) { + int i, size; + + size = unpack_array(e, NULL); + profile->xattr_count = size; + profile->xattrs = kmalloc_array(size, sizeof(char *), + GFP_KERNEL); + if (!profile->xattrs) + goto fail; + for (i = 0; i < size; i++) { + if (!unpack_strdup(e, &profile->xattrs[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + + if (unpack_nameX(e, AA_STRUCT, "xattr_values")) { + int i, size; + + size = unpack_array(e, NULL); + + /* Must be the same number of xattr values as xattrs */ + if (size != profile->xattr_count) + goto fail; + + profile->xattr_lens = kmalloc_array(size, sizeof(size_t), + GFP_KERNEL); + if (!profile->xattr_lens) + goto fail; + + profile->xattr_values = kmalloc_array(size, sizeof(char *), + GFP_KERNEL); + if (!profile->xattr_values) + goto fail; + + for (i = 0; i < size; i++) { + profile->xattr_lens[i] = unpack_blob(e, + &profile->xattr_values[i], NULL); + profile->xattr_values[i] = + kvmemdup(profile->xattr_values[i], + profile->xattr_lens[i]); + } + + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + return 1; + +fail: + e->pos = pos; + return 0; +} + static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) { void *pos = e->pos; @@ -558,15 +629,6 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) return 0; } -static void *kvmemdup(const void *src, size_t len) -{ - void *p = kvmalloc(len, GFP_KERNEL); - - if (p) - memcpy(p, src, len); - return p; -} - static u32 strhash(const void *data, u32 len, u32 seed) { const char * const *key = data; @@ -721,6 +783,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) goto fail; } + if (!unpack_xattrs(e, profile)) { + info = "failed to unpack profile xattrs"; + goto fail; + } + if (!unpack_rlimits(e, profile)) { info = "failed to unpack profile rlimits"; goto fail; -- 2.14.1 -- AppArmor mailing list AppArmor@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor