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>
---

Updated to copy the policy elements and then free them on cleanup

 security/apparmor/domain.c         | 150 ++++++++++++++++++++++++++++++-------
 security/apparmor/include/policy.h |   6 ++
 security/apparmor/policy.c         |   8 ++
 security/apparmor/policy_unpack.c  |  87 ++++++++++++++++++---
 4 files changed, 215 insertions(+), 36 deletions(-)

diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 04ba9d0718ea..fec5f83a25df 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,6 +302,54 @@ 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
  * @name - to match against  (NOT NULL)
@@ -316,11 +365,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,25 +379,53 @@ static struct aa_profile *__attach_match(const char *name,
                    &profile->label == ns_unconfined(profile->ns))
                        continue;
 
-               if (profile->xmatch) {
-                       if (profile->xmatch_len == len) {
-                               conflict = true;
-                               continue;
-                       } else 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) {
-                                       candidate = profile;
-                                       len = profile->xmatch_len;
-                                       conflict = false;
+               /* 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 && 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) {
+                               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;
                                }
+
+                               /* 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;
        }
@@ -369,13 +447,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;
@@ -431,6 +510,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)
@@ -440,6 +520,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)
@@ -467,11 +548,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;
@@ -511,6 +592,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 = {};
@@ -535,8 +618,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;
@@ -549,7 +632,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;
@@ -558,6 +642,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 b0b58848c248..c872806683d5 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 59a1a25b7d43..7427eb86cef9 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -164,7 +164,7 @@ static void do_loaddata_free(struct work_struct *work)
        }
 
        kzfree(d->hash);
-       kfree(d->name);
+       kzfree(d->name);
        kvfree(d);
 }
 
@@ -196,6 +196,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)
@@ -515,6 +524,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;
@@ -549,15 +620,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;
@@ -712,6 +774,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.15.1.424.g9478a66081-goog


-- 
AppArmor mailing list
AppArmor@lists.ubuntu.com
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/apparmor

Reply via email to