apparmor: allow introspecting the loaded policy pre internal transform
authorJohn Johansen <john.johansen@canonical.com>
Mon, 16 Jan 2017 08:42:55 +0000 (00:42 -0800)
committerJohn Johansen <john.johansen@canonical.com>
Mon, 16 Jan 2017 09:18:42 +0000 (01:18 -0800)
Store loaded policy and allow introspecting it through apparmorfs. This
has several uses from debugging, policy validation, and policy checkpoint
and restore for containers.

Signed-off-by: John Johansen <john.johansen@canonical.com>
security/apparmor/apparmorfs.c
security/apparmor/crypto.c
security/apparmor/include/apparmorfs.h
security/apparmor/include/crypto.h
security/apparmor/include/policy.h
security/apparmor/include/policy_unpack.h
security/apparmor/policy.c
security/apparmor/policy_unpack.c

index cc6ee1e..2e6790c 100644 (file)
@@ -33,6 +33,7 @@
 #include "include/policy.h"
 #include "include/policy_ns.h"
 #include "include/resource.h"
+#include "include/policy_unpack.h"
 
 /**
  * aa_mangle_name - mangle a profile name to std profile layout form
@@ -84,11 +85,13 @@ static int mangle_name(const char *name, char *target)
  * Returns: kernel buffer containing copy of user buffer data or an
  *          ERR_PTR on failure.
  */
-static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
-                                      size_t alloc_size, size_t copy_size,
-                                      loff_t *pos)
+static struct aa_loaddata *aa_simple_write_to_buffer(int op,
+                                                    const char __user *userbuf,
+                                                    size_t alloc_size,
+                                                    size_t copy_size,
+                                                    loff_t *pos)
 {
-       char *data;
+       struct aa_loaddata *data;
 
        BUG_ON(copy_size > alloc_size);
 
@@ -96,19 +99,16 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
                /* only writes from pos 0, that is complete writes */
                return ERR_PTR(-ESPIPE);
 
-       /*
-        * Don't allow profile load/replace/remove from profiles that don't
-        * have CAP_MAC_ADMIN
-        */
-       if (!aa_may_manage_policy(__aa_current_profile(), NULL, op))
-               return ERR_PTR(-EACCES);
-
        /* freed by caller to simple_write_to_buffer */
-       data = kvmalloc(alloc_size);
+       data = kvmalloc(sizeof(*data) + alloc_size);
        if (data == NULL)
                return ERR_PTR(-ENOMEM);
+       kref_init(&data->count);
+       data->size = copy_size;
+       data->hash = NULL;
+       data->abi = 0;
 
-       if (copy_from_user(data, userbuf, copy_size)) {
+       if (copy_from_user(data->data, userbuf, copy_size)) {
                kvfree(data);
                return ERR_PTR(-EFAULT);
        }
@@ -116,26 +116,38 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
        return data;
 }
 
-
-/* .load file hook fn to load policy */
-static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
-                           loff_t *pos)
+static ssize_t policy_update(int binop, const char __user *buf, size_t size,
+                            loff_t *pos)
 {
-       char *data;
        ssize_t error;
+       struct aa_loaddata *data;
+       struct aa_profile *profile = aa_current_profile();
+       int op = binop == PROF_ADD ? OP_PROF_LOAD : OP_PROF_REPL;
+       /* high level check about policy management - fine grained in
+        * below after unpack
+        */
+       error = aa_may_manage_policy(profile, profile->ns, op);
+       if (error)
+               return error;
 
-       data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos);
-
+       data = aa_simple_write_to_buffer(op, buf, size, size, pos);
        error = PTR_ERR(data);
        if (!IS_ERR(data)) {
-               error = aa_replace_profiles(__aa_current_profile()->ns, data,
-                                           size, PROF_ADD);
-               kvfree(data);
+               error = aa_replace_profiles(profile->ns, binop, data);
+               aa_put_loaddata(data);
        }
 
        return error;
 }
 
+static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
+                           loff_t *pos)
+{
+       int error = policy_update(PROF_ADD, buf, size, pos);
+
+       return error;
+}
+
 static const struct file_operations aa_fs_profile_load = {
        .write = profile_load,
        .llseek = default_llseek,
@@ -145,16 +157,7 @@ static const struct file_operations aa_fs_profile_load = {
 static ssize_t profile_replace(struct file *f, const char __user *buf,
                               size_t size, loff_t *pos)
 {
-       char *data;
-       ssize_t error;
-
-       data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos);
-       error = PTR_ERR(data);
-       if (!IS_ERR(data)) {
-               error = aa_replace_profiles(__aa_current_profile()->ns, data,
-                                           size, PROF_REPLACE);
-               kvfree(data);
-       }
+       int error = policy_update(PROF_REPLACE, buf, size, pos);
 
        return error;
 }
@@ -164,27 +167,35 @@ static const struct file_operations aa_fs_profile_replace = {
        .llseek = default_llseek,
 };
 
-/* .remove file hook fn to remove loaded policy */
 static ssize_t profile_remove(struct file *f, const char __user *buf,
                              size_t size, loff_t *pos)
 {
-       char *data;
+       struct aa_loaddata *data;
+       struct aa_profile *profile;
        ssize_t error;
 
+       profile = aa_current_profile();
+       /* high level check about policy management - fine grained in
+        * below after unpack
+        */
+       error = aa_may_manage_policy(profile, profile->ns, OP_PROF_RM);
+       if (error)
+               goto out;
+
        /*
         * aa_remove_profile needs a null terminated string so 1 extra
         * byte is allocated and the copied data is null terminated.
         */
-       data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos);
+       data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size,
+                                        pos);
 
        error = PTR_ERR(data);
        if (!IS_ERR(data)) {
-               data[size] = 0;
-               error = aa_remove_profiles(__aa_current_profile()->ns, data,
-                                          size);
-               kvfree(data);
+               data->data[size] = 0;
+               error = aa_remove_profiles(profile->ns, data->data, size);
+               aa_put_loaddata(data);
        }
-
+ out:
        return error;
 }
 
@@ -401,6 +412,100 @@ static const struct file_operations aa_fs_ns_name = {
        .release        = single_release,
 };
 
+static int rawdata_release(struct inode *inode, struct file *file)
+{
+       /* TODO: switch to loaddata when profile switched to symlink */
+       aa_put_loaddata(file->private_data);
+
+       return 0;
+}
+
+static int aa_fs_seq_raw_abi_show(struct seq_file *seq, void *v)
+{
+       struct aa_proxy *proxy = seq->private;
+       struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
+
+       if (profile->rawdata->abi) {
+               seq_printf(seq, "v%d", profile->rawdata->abi);
+               seq_puts(seq, "\n");
+       }
+       aa_put_profile(profile);
+
+       return 0;
+}
+
+static int aa_fs_seq_raw_abi_open(struct inode *inode, struct file *file)
+{
+       return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_abi_show);
+}
+
+static const struct file_operations aa_fs_seq_raw_abi_fops = {
+       .owner          = THIS_MODULE,
+       .open           = aa_fs_seq_raw_abi_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = aa_fs_seq_profile_release,
+};
+
+static int aa_fs_seq_raw_hash_show(struct seq_file *seq, void *v)
+{
+       struct aa_proxy *proxy = seq->private;
+       struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
+       unsigned int i, size = aa_hash_size();
+
+       if (profile->rawdata->hash) {
+               for (i = 0; i < size; i++)
+                       seq_printf(seq, "%.2x", profile->rawdata->hash[i]);
+               seq_puts(seq, "\n");
+       }
+       aa_put_profile(profile);
+
+       return 0;
+}
+
+static int aa_fs_seq_raw_hash_open(struct inode *inode, struct file *file)
+{
+       return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_hash_show);
+}
+
+static const struct file_operations aa_fs_seq_raw_hash_fops = {
+       .owner          = THIS_MODULE,
+       .open           = aa_fs_seq_raw_hash_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = aa_fs_seq_profile_release,
+};
+
+static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
+                           loff_t *ppos)
+{
+       struct aa_loaddata *rawdata = file->private_data;
+
+       return simple_read_from_buffer(buf, size, ppos, rawdata->data,
+                                      rawdata->size);
+}
+
+static int rawdata_open(struct inode *inode, struct file *file)
+{
+       struct aa_proxy *proxy = inode->i_private;
+       struct aa_profile *profile;
+
+       if (!policy_view_capable(NULL))
+               return -EACCES;
+       profile = aa_get_profile_rcu(&proxy->profile);
+       file->private_data = aa_get_loaddata(profile->rawdata);
+       aa_put_profile(profile);
+
+       return 0;
+}
+
+static const struct file_operations aa_fs_rawdata_fops = {
+       .open = rawdata_open,
+       .read = rawdata_read,
+       .llseek = generic_file_llseek,
+       .release = rawdata_release,
+};
+
 /** fns to setup dynamic per profile/namespace files **/
 void __aa_fs_profile_rmdir(struct aa_profile *profile)
 {
@@ -512,6 +617,29 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
                profile->dents[AAFS_PROF_HASH] = dent;
        }
 
+       if (profile->rawdata) {
+               dent = create_profile_file(dir, "raw_sha1", profile,
+                                          &aa_fs_seq_raw_hash_fops);
+               if (IS_ERR(dent))
+                       goto fail;
+               profile->dents[AAFS_PROF_RAW_HASH] = dent;
+
+               dent = create_profile_file(dir, "raw_abi", profile,
+                                          &aa_fs_seq_raw_abi_fops);
+               if (IS_ERR(dent))
+                       goto fail;
+               profile->dents[AAFS_PROF_RAW_ABI] = dent;
+
+               dent = securityfs_create_file("raw_data", S_IFREG | 0444, dir,
+                                             profile->proxy,
+                                             &aa_fs_rawdata_fops);
+               if (IS_ERR(dent))
+                       goto fail;
+               profile->dents[AAFS_PROF_RAW_DATA] = dent;
+               d_inode(dent)->i_size = profile->rawdata->size;
+               aa_get_proxy(profile->proxy);
+       }
+
        list_for_each_entry(child, &profile->base.profiles, base.list) {
                error = __aa_fs_profile_mkdir(child, prof_child_dir(profile));
                if (error)
@@ -817,6 +945,9 @@ static const struct seq_operations aa_fs_profiles_op = {
 
 static int profiles_open(struct inode *inode, struct file *file)
 {
+       if (!policy_view_capable(NULL))
+               return -EACCES;
+
        return seq_open(file, &aa_fs_profiles_op);
 }
 
index b75dab0..de8dc78 100644 (file)
@@ -29,6 +29,43 @@ unsigned int aa_hash_size(void)
        return apparmor_hash_size;
 }
 
+char *aa_calc_hash(void *data, size_t len)
+{
+       struct {
+               struct shash_desc shash;
+               char ctx[crypto_shash_descsize(apparmor_tfm)];
+       } desc;
+       char *hash = NULL;
+       int error = -ENOMEM;
+
+       if (!apparmor_tfm)
+               return NULL;
+
+       hash = kzalloc(apparmor_hash_size, GFP_KERNEL);
+       if (!hash)
+               goto fail;
+
+       desc.shash.tfm = apparmor_tfm;
+       desc.shash.flags = 0;
+
+       error = crypto_shash_init(&desc.shash);
+       if (error)
+               goto fail;
+       error = crypto_shash_update(&desc.shash, (u8 *) data, len);
+       if (error)
+               goto fail;
+       error = crypto_shash_final(&desc.shash, hash);
+       if (error)
+               goto fail;
+
+       return hash;
+
+fail:
+       kfree(hash);
+
+       return ERR_PTR(error);
+}
+
 int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
                         size_t len)
 {
@@ -37,7 +74,7 @@ int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
                char ctx[crypto_shash_descsize(apparmor_tfm)];
        } desc;
        int error = -ENOMEM;
-       u32 le32_version = cpu_to_le32(version);
+       __le32 le32_version = cpu_to_le32(version);
 
        if (!aa_g_hash_policy)
                return 0;
index eeeae5b..a593e75 100644 (file)
@@ -70,6 +70,7 @@ enum aafs_ns_type {
        AAFS_NS_DIR,
        AAFS_NS_PROFS,
        AAFS_NS_NS,
+       AAFS_NS_RAW_DATA,
        AAFS_NS_COUNT,
        AAFS_NS_MAX_COUNT,
        AAFS_NS_SIZE,
@@ -85,12 +86,16 @@ enum aafs_prof_type {
        AAFS_PROF_MODE,
        AAFS_PROF_ATTACH,
        AAFS_PROF_HASH,
+       AAFS_PROF_RAW_DATA,
+       AAFS_PROF_RAW_HASH,
+       AAFS_PROF_RAW_ABI,
        AAFS_PROF_SIZEOF,
 };
 
 #define ns_dir(X) ((X)->dents[AAFS_NS_DIR])
 #define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS])
 #define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS])
+#define ns_subdata_dir(X) ((X)->dents[AAFS_NS_RAW_DATA])
 
 #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
 #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
index dc418e5..c1469f8 100644 (file)
 
 #ifdef CONFIG_SECURITY_APPARMOR_HASH
 unsigned int aa_hash_size(void);
+char *aa_calc_hash(void *data, size_t len);
 int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
                         size_t len);
 #else
+static inline char *aa_calc_hash(void *data, size_t len)
+{
+       return NULL;
+}
 static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version,
                                       void *start, size_t len)
 {
index 95641e2..fbbc867 100644 (file)
@@ -161,6 +161,7 @@ struct aa_profile {
        struct aa_caps caps;
        struct aa_rlimit rlimits;
 
+       struct aa_loaddata *rawdata;
        unsigned char *hash;
        char *dirname;
        struct dentry *dents[AAFS_PROF_SIZEOF];
@@ -187,8 +188,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base,
                                        const char *fqname, size_t n);
 struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name);
 
-ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size,
-                           bool noreplace);
+ssize_t aa_replace_profiles(struct aa_ns *view, bool noreplace,
+                           struct aa_loaddata *udata);
 ssize_t aa_remove_profiles(struct aa_ns *view, char *name, size_t size);
 void __aa_profile_list_release(struct list_head *head);
 
index c214fb8..7b675b6 100644 (file)
@@ -16,6 +16,7 @@
 #define __POLICY_INTERFACE_H
 
 #include <linux/list.h>
+#include <linux/kref.h>
 
 struct aa_load_ent {
        struct list_head list;
@@ -34,6 +35,30 @@ struct aa_load_ent *aa_load_ent_alloc(void);
 #define PACKED_MODE_KILL       2
 #define PACKED_MODE_UNCONFINED 3
 
-int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns);
+/* struct aa_loaddata - buffer of policy load data set */
+struct aa_loaddata {
+       struct kref count;
+       size_t size;
+       int abi;
+       unsigned char *hash;
+       char data[];
+};
+
+int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);
+
+static inline struct aa_loaddata *
+aa_get_loaddata(struct aa_loaddata *data)
+{
+       if (data)
+               kref_get(&(data->count));
+       return data;
+}
+
+void aa_loaddata_kref(struct kref *kref);
+static inline void aa_put_loaddata(struct aa_loaddata *data)
+{
+       if (data)
+               kref_put(&data->count, aa_loaddata_kref);
+}
 
 #endif /* __POLICY_INTERFACE_H */
index 3c5c0b2..ff29b60 100644 (file)
@@ -228,6 +228,7 @@ void aa_free_profile(struct aa_profile *profile)
        aa_put_proxy(profile->proxy);
 
        kzfree(profile->hash);
+       aa_put_loaddata(profile->rawdata);
        kzfree(profile);
 }
 
@@ -802,10 +803,8 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname,
 /**
  * aa_replace_profiles - replace profile(s) on the profile list
  * @view: namespace load is viewed from
- * @profile: profile that is attempting to load/replace policy
- * @udata: serialized data stream  (NOT NULL)
- * @size: size of the serialized data stream
  * @noreplace: true if only doing addition, no replacement allowed
+ * @udata: serialized data stream  (NOT NULL)
  *
  * unpack and replace a profile on the profile list and uses of that profile
  * by any aa_task_cxt.  If the profile does not exist on the profile list
@@ -813,8 +812,8 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname,
  *
  * Returns: size of data consumed else error code on failure.
  */
-ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size,
-                           bool noreplace)
+ssize_t aa_replace_profiles(struct aa_ns *view, bool noreplace,
+                           struct aa_loaddata *udata)
 {
        const char *ns_name, *info = NULL;
        struct aa_ns *ns = NULL;
@@ -824,7 +823,7 @@ ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size,
        LIST_HEAD(lh);
 
        /* released below */
-       error = aa_unpack(udata, size, &lh, &ns_name);
+       error = aa_unpack(udata, &lh, &ns_name);
        if (error)
                goto out;
 
@@ -841,6 +840,7 @@ ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size,
        /* setup parent and ns info */
        list_for_each_entry(ent, &lh, list) {
                struct aa_policy *policy;
+               ent->new->rawdata = aa_get_loaddata(udata);
                error = __lookup_replace(ns, ent->new->base.hname, noreplace,
                                         &ent->old, &info);
                if (error)
@@ -957,7 +957,7 @@ out:
 
        if (error)
                return error;
-       return size;
+       return udata->size;
 
 fail_lock:
        mutex_unlock(&ns->lock);
index 51a7f9f..fb4ef84 100644 (file)
@@ -117,6 +117,16 @@ static int audit_iface(struct aa_profile *new, const char *name,
                        audit_cb);
 }
 
+void aa_loaddata_kref(struct kref *kref)
+{
+       struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count);
+
+       if (d) {
+               kzfree(d->hash);
+               kvfree(d);
+       }
+}
+
 /* test if read will be in packed data bounds */
 static bool inbounds(struct aa_ext *e, size_t size)
 {
@@ -749,7 +759,6 @@ struct aa_load_ent *aa_load_ent_alloc(void)
 /**
  * aa_unpack - unpack packed binary profile(s) data loaded from user space
  * @udata: user data copied to kmem  (NOT NULL)
- * @size: the size of the user data
  * @lh: list to place unpacked profiles in a aa_repl_ws
  * @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
  *
@@ -759,15 +768,16 @@ struct aa_load_ent *aa_load_ent_alloc(void)
  *
  * Returns: profile(s) on @lh else error pointer if fails to unpack
  */
-int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
+int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
+             const char **ns)
 {
        struct aa_load_ent *tmp, *ent;
        struct aa_profile *profile = NULL;
        int error;
        struct aa_ext e = {
-               .start = udata,
-               .end = udata + size,
-               .pos = udata,
+               .start = udata->data,
+               .end = udata->data + udata->size,
+               .pos = udata->data,
        };
 
        *ns = NULL;
@@ -802,7 +812,13 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
                ent->new = profile;
                list_add_tail(&ent->list, lh);
        }
-
+       udata->abi = e.version & K_ABI_MASK;
+       udata->hash = aa_calc_hash(udata->data, udata->size);
+       if (IS_ERR(udata->hash)) {
+               error = PTR_ERR(udata->hash);
+               udata->hash = NULL;
+               goto fail;
+       }
        return 0;
 
 fail_profile: