apparmor: support querying extended trusted helper extra data
authorWilliam Hua <william.hua@canonical.com>
Mon, 16 Jan 2017 00:49:28 +0000 (16:49 -0800)
committerJohn Johansen <john.johansen@canonical.com>
Mon, 16 Jan 2017 09:18:51 +0000 (01:18 -0800)
Allow a profile to carry extra data that can be queried via userspace.
This provides a means to store extra data in a profile that a trusted
helper can extract and use from live policy.

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

index 7613a28..6834000 100644 (file)
@@ -213,6 +213,144 @@ static const struct file_operations aa_fs_profile_remove = {
        .llseek = default_llseek,
 };
 
+/**
+ * query_data - queries a policy and writes its data to buf
+ * @buf: the resulting data is stored here (NOT NULL)
+ * @buf_len: size of buf
+ * @query: query string used to retrieve data
+ * @query_len: size of query including second NUL byte
+ *
+ * The buffers pointed to by buf and query may overlap. The query buffer is
+ * parsed before buf is written to.
+ *
+ * The query should look like "<LABEL>\0<KEY>\0", where <LABEL> is the name of
+ * the security confinement context and <KEY> is the name of the data to
+ * retrieve. <LABEL> and <KEY> must not be NUL-terminated.
+ *
+ * Don't expect the contents of buf to be preserved on failure.
+ *
+ * Returns: number of characters written to buf or -errno on failure
+ */
+static ssize_t query_data(char *buf, size_t buf_len,
+                         char *query, size_t query_len)
+{
+       char *out;
+       const char *key;
+       struct aa_profile *profile;
+       struct aa_data *data;
+       u32 bytes, blocks;
+       __le32 outle32;
+
+       if (!query_len)
+               return -EINVAL; /* need a query */
+
+       key = query + strnlen(query, query_len) + 1;
+       if (key + 1 >= query + query_len)
+               return -EINVAL; /* not enough space for a non-empty key */
+       if (key + strnlen(key, query + query_len - key) >= query + query_len)
+               return -EINVAL; /* must end with NUL */
+
+       if (buf_len < sizeof(bytes) + sizeof(blocks))
+               return -EINVAL; /* not enough space */
+
+       profile = aa_current_profile();
+
+       /* We are going to leave space for two numbers. The first is the total
+        * number of bytes we are writing after the first number. This is so
+        * users can read the full output without reallocation.
+        *
+        * The second number is the number of data blocks we're writing. An
+        * application might be confined by multiple policies having data in
+        * the same key.
+        */
+       memset(buf, 0, sizeof(bytes) + sizeof(blocks));
+       out = buf + sizeof(bytes) + sizeof(blocks);
+
+       blocks = 0;
+       if (profile->data) {
+               data = rhashtable_lookup_fast(profile->data, &key,
+                                             profile->data->p);
+
+               if (data) {
+                       if (out + sizeof(outle32) + data->size > buf + buf_len)
+                               return -EINVAL; /* not enough space */
+                       outle32 = __cpu_to_le32(data->size);
+                       memcpy(out, &outle32, sizeof(outle32));
+                       out += sizeof(outle32);
+                       memcpy(out, data->data, data->size);
+                       out += data->size;
+                       blocks++;
+               }
+       }
+
+       outle32 = __cpu_to_le32(out - buf - sizeof(bytes));
+       memcpy(buf, &outle32, sizeof(outle32));
+       outle32 = __cpu_to_le32(blocks);
+       memcpy(buf + sizeof(bytes), &outle32, sizeof(outle32));
+
+       return out - buf;
+}
+
+#define QUERY_CMD_DATA         "data\0"
+#define QUERY_CMD_DATA_LEN     5
+
+/**
+ * aa_write_access - generic permissions and data query
+ * @file: pointer to open apparmorfs/access file
+ * @ubuf: user buffer containing the complete query string (NOT NULL)
+ * @count: size of ubuf
+ * @ppos: position in the file (MUST BE ZERO)
+ *
+ * Allows for one permissions or data query per open(), write(), and read()
+ * sequence. The only queries currently supported are label-based queries for
+ * permissions or data.
+ *
+ * For permissions queries, ubuf must begin with "label\0", followed by the
+ * profile query specific format described in the query_label() function
+ * documentation.
+ *
+ * For data queries, ubuf must have the form "data\0<LABEL>\0<KEY>\0", where
+ * <LABEL> is the name of the security confinement context and <KEY> is the
+ * name of the data to retrieve.
+ *
+ * Returns: number of bytes written or -errno on failure
+ */
+static ssize_t aa_write_access(struct file *file, const char __user *ubuf,
+                              size_t count, loff_t *ppos)
+{
+       char *buf;
+       ssize_t len;
+
+       if (*ppos)
+               return -ESPIPE;
+
+       buf = simple_transaction_get(file, ubuf, count);
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       if (count > QUERY_CMD_DATA_LEN &&
+                  !memcmp(buf, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) {
+               len = query_data(buf, SIMPLE_TRANSACTION_LIMIT,
+                                buf + QUERY_CMD_DATA_LEN,
+                                count - QUERY_CMD_DATA_LEN);
+       } else
+               len = -EINVAL;
+
+       if (len < 0)
+               return len;
+
+       simple_transaction_set(file, len);
+
+       return count;
+}
+
+static const struct file_operations aa_fs_access = {
+       .write          = aa_write_access,
+       .read           = simple_transaction_read,
+       .release        = simple_transaction_release,
+       .llseek         = generic_file_llseek,
+};
+
 static int aa_fs_seq_show(struct seq_file *seq, void *v)
 {
        struct aa_fs_entry *fs_file = seq->private;
@@ -1078,6 +1216,7 @@ static struct aa_fs_entry aa_fs_entry_features[] = {
 };
 
 static struct aa_fs_entry aa_fs_entry_apparmor[] = {
+       AA_FS_FILE_FOPS(".access", 0640, &aa_fs_access),
        AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level),
        AA_FS_FILE_FOPS(".ns_name", 0640, &aa_fs_ns_name),
        AA_FS_FILE_FOPS("profiles", 0440, &aa_fs_profiles_fops),
index a5a9978..93b1b1f 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/capability.h>
 #include <linux/cred.h>
 #include <linux/kref.h>
+#include <linux/rhashtable.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/socket.h>
@@ -98,6 +99,19 @@ struct aa_proxy {
        struct aa_profile __rcu *profile;
 };
 
+/* struct aa_data - generic data structure
+ * key: name for retrieving this data
+ * size: size of data in bytes
+ * data: binary data
+ * head: reserved for rhashtable
+ */
+struct aa_data {
+       char *key;
+       u32 size;
+       char *data;
+       struct rhash_head head;
+};
+
 
 /* struct aa_profile - basic confinement data
  * @base - base components of the profile (name, refcount, lists, lock ...)
@@ -122,6 +136,7 @@ struct aa_proxy {
  *
  * @dents: dentries for the profiles file entries in apparmorfs
  * @dirname: name of the profile dir in apparmorfs
+ * @data: hashtable for free-form policy aa_data
  *
  * The AppArmor profile contains the basic confinement data.  Each profile
  * has a name, and exists in a namespace.  The @name and @exec_match are
@@ -165,6 +180,7 @@ struct aa_profile {
        unsigned char *hash;
        char *dirname;
        struct dentry *dents[AAFS_PROF_SIZEOF];
+       struct rhashtable *data;
 };
 
 extern enum profile_mode aa_g_profile_mode;
index 6a5cf54..b1def70 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/sysctl.h>
 #include <linux/audit.h>
 #include <linux/user_namespace.h>
+#include <linux/kmemleak.h>
 #include <net/sock.h>
 
 #include "include/apparmor.h"
index bc63cf7..f2c4bb2 100644 (file)
@@ -195,6 +195,20 @@ void aa_free_proxy_kref(struct kref *kref)
 }
 
 /**
+ * aa_free_data - free a data blob
+ * @ptr: data to free
+ * @arg: unused
+ */
+static void aa_free_data(void *ptr, void *arg)
+{
+       struct aa_data *data = ptr;
+
+       kzfree(data->data);
+       kzfree(data->key);
+       kzfree(data);
+}
+
+/**
  * aa_free_profile - free a profile
  * @profile: the profile to free  (MAYBE NULL)
  *
@@ -206,6 +220,8 @@ void aa_free_proxy_kref(struct kref *kref)
  */
 void aa_free_profile(struct aa_profile *profile)
 {
+       struct rhashtable *rht;
+
        AA_DEBUG("%s(%p)\n", __func__, profile);
 
        if (!profile)
@@ -227,6 +243,13 @@ void aa_free_profile(struct aa_profile *profile)
        aa_put_dfa(profile->policy.dfa);
        aa_put_proxy(profile->proxy);
 
+       if (profile->data) {
+               rht = profile->data;
+               profile->data = NULL;
+               rhashtable_free_and_destroy(rht, aa_free_data, NULL);
+               kzfree(rht);
+       }
+
        kzfree(profile->hash);
        aa_put_loaddata(profile->rawdata);
        kzfree(profile);
index 59c891a..4f61d40 100644 (file)
@@ -485,6 +485,30 @@ fail:
        return 0;
 }
 
+static void *kvmemdup(const void *src, size_t len)
+{
+       void *p = kvmalloc(len);
+
+       if (p)
+               memcpy(p, src, len);
+       return p;
+}
+
+static u32 strhash(const void *data, u32 len, u32 seed)
+{
+       const char * const *key = data;
+
+       return jhash(*key, strlen(*key), seed);
+}
+
+static int datacmp(struct rhashtable_compare_arg *arg, const void *obj)
+{
+       const struct aa_data *data = obj;
+       const char * const *key = arg->key;
+
+       return strcmp(data->key, *key);
+}
+
 /**
  * unpack_profile - unpack a serialized profile
  * @e: serialized data extent information (NOT NULL)
@@ -496,6 +520,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
        struct aa_profile *profile = NULL;
        const char *tmpname, *tmpns = NULL, *name = NULL;
        size_t ns_len;
+       struct rhashtable_params params = { 0 };
+       char *key = NULL;
+       struct aa_data *data;
        int i, error = -EPROTO;
        kernel_cap_t tmpcap;
        u32 tmp;
@@ -654,6 +681,45 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
        if (!unpack_trans_table(e, profile))
                goto fail;
 
+       if (unpack_nameX(e, AA_STRUCT, "data")) {
+               profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL);
+               if (!profile->data)
+                       goto fail;
+
+               params.nelem_hint = 3;
+               params.key_len = sizeof(void *);
+               params.key_offset = offsetof(struct aa_data, key);
+               params.head_offset = offsetof(struct aa_data, head);
+               params.hashfn = strhash;
+               params.obj_cmpfn = datacmp;
+
+               if (rhashtable_init(profile->data, &params))
+                       goto fail;
+
+               while (unpack_strdup(e, &key, NULL)) {
+                       data = kzalloc(sizeof(*data), GFP_KERNEL);
+                       if (!data) {
+                               kzfree(key);
+                               goto fail;
+                       }
+
+                       data->key = key;
+                       data->size = unpack_blob(e, &data->data, NULL);
+                       data->data = kvmemdup(data->data, data->size);
+                       if (data->size && !data->data) {
+                               kzfree(data->key);
+                               kzfree(data);
+                               goto fail;
+                       }
+
+                       rhashtable_insert_fast(profile->data, &data->head,
+                                              profile->data->p);
+               }
+
+               if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+                       goto fail;
+       }
+
        if (!unpack_nameX(e, AA_STRUCTEND, NULL))
                goto fail;