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

Reply via email to