diff options
author | William Hua <william.hua@canonical.com> | 2017-01-16 03:49:28 +0300 |
---|---|---|
committer | John Johansen <john.johansen@canonical.com> | 2017-01-16 12:18:51 +0300 |
commit | e025be0f26d5597b0a2bdfa65145a0171e77b614 (patch) | |
tree | 0f9300ae3893d6af776466d12fe22a739d01d75d /security | |
parent | 12eb87d50bfe234c3f964e9fb47bbd0135010c13 (diff) | |
download | linux-e025be0f26d5597b0a2bdfa65145a0171e77b614.tar.xz |
apparmor: support querying extended trusted helper extra data
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>
Diffstat (limited to 'security')
-rw-r--r-- | security/apparmor/apparmorfs.c | 139 | ||||
-rw-r--r-- | security/apparmor/include/policy.h | 16 | ||||
-rw-r--r-- | security/apparmor/lsm.c | 1 | ||||
-rw-r--r-- | security/apparmor/policy.c | 23 | ||||
-rw-r--r-- | security/apparmor/policy_unpack.c | 66 |
5 files changed, 245 insertions, 0 deletions
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 7613a28f157e..6834000640d7 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -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), diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index a5a997896836..93b1b1f440d3 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -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; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 6a5cf54cfa72..b1def70b94f1 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -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" diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index bc63cf7b606a..f2c4bb26b060 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -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); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 59c891ad1270..4f61d40e29b0 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -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, ¶ms)) + 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; |