Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e025be0f authored by William Hua's avatar William Hua Committed by John Johansen
Browse files

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: default avatarWilliam Hua <william.hua@canonical.com>
Signed-off-by: default avatarJohn Johansen <john.johansen@canonical.com>
parent 12eb87d5
Loading
Loading
Loading
Loading
+139 −0
Original line number Diff line number Diff line
@@ -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),
+16 −0
Original line number Diff line number Diff line
@@ -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;
+1 −0
Original line number Diff line number Diff line
@@ -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"
+23 −0
Original line number Diff line number Diff line
@@ -194,6 +194,20 @@ void aa_free_proxy_kref(struct kref *kref)
	free_proxy(p);
}

/**
 * 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);
+66 −0
Original line number Diff line number Diff line
@@ -485,6 +485,30 @@ 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);

	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;