diff options
author | Matthew Garrett <mjg59@google.com> | 2018-02-08 21:37:19 +0100 |
---|---|---|
committer | John Johansen <john.johansen@canonical.com> | 2018-02-09 20:30:02 +0100 |
commit | 8e51f9087f4024d20f70f4d9831e1f45d8088331 (patch) | |
tree | a466a6dfa9ffd57b9919b89931bc18fbddb1517e | |
parent | apparmor: cleanup: simplify code to get ns symlink name (diff) | |
download | linux-8e51f9087f4024d20f70f4d9831e1f45d8088331.tar.xz linux-8e51f9087f4024d20f70f4d9831e1f45d8088331.zip |
apparmor: Add support for attaching profiles via xattr, presence and value
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 <mjg59@google.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
-rw-r--r-- | security/apparmor/domain.c | 152 | ||||
-rw-r--r-- | security/apparmor/include/policy.h | 6 | ||||
-rw-r--r-- | security/apparmor/policy.c | 8 | ||||
-rw-r--r-- | security/apparmor/policy_unpack.c | 85 |
4 files changed, 217 insertions, 34 deletions
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 9d1936519cfd..6bcafe8d226d 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" @@ -302,7 +303,56 @@ static int change_profile_perms(struct aa_profile *profile, } /** + * 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,56 @@ 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 +444,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 +452,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 +515,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 +525,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 +553,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 +597,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 +623,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 +637,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 +647,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 17fe41a9cac3..02bde92ebb5c 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -148,6 +148,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 a8e096a88e62..7fee546ba10d 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -210,6 +210,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); @@ -227,6 +228,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 40c8dc617b13..98d019185e57 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -203,6 +203,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) @@ -522,6 +531,68 @@ fail: 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 = kcalloc(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 = kcalloc(size, sizeof(size_t), + GFP_KERNEL); + if (!profile->xattr_lens) + goto fail; + + profile->xattr_values = kcalloc(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; @@ -556,15 +627,6 @@ fail: 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; @@ -719,6 +781,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; |