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

Commit 5d5182ca authored by John Johansen's avatar John Johansen
Browse files

apparmor: move to per loaddata files, instead of replicating in profiles



The loaddata sets cover more than just a single profile and should
be tracked at the ns level. Move the load data files under the namespace
and reference the files from the profiles via a symlink.

Signed-off-by: default avatarJohn Johansen <john.johansen@canonical.com>
Reviewed-by: default avatarSeth Arnold <seth.arnold@canonical.com>
Reviewed-by: default avatarKees Cook <keescook@chromium.org>
parent 6623ec7c
Loading
Loading
Loading
Loading
+232 −62
Original line number Diff line number Diff line
@@ -98,14 +98,11 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf,
		return ERR_PTR(-ESPIPE);

	/* freed by caller to simple_write_to_buffer */
	data = kvmalloc(sizeof(*data) + alloc_size, GFP_KERNEL);
	if (data == NULL)
		return ERR_PTR(-ENOMEM);
	kref_init(&data->count);
	data->size = copy_size;
	data->hash = NULL;
	data->abi = 0;
	data = aa_loaddata_alloc(alloc_size);
	if (IS_ERR(data))
		return data;

	data->size = copy_size;
	if (copy_from_user(data->data, userbuf, copy_size)) {
		kvfree(data);
		return ERR_PTR(-EFAULT);
@@ -213,6 +210,11 @@ static const struct file_operations aa_fs_profile_remove = {
	.llseek = default_llseek,
};

void __aa_bump_ns_revision(struct aa_ns *ns)
{
	ns->revision++;
}

/**
 * query_data - queries a policy and writes its data to buf
 * @buf: the resulting data is stored here (NOT NULL)
@@ -559,68 +561,88 @@ static const struct file_operations aa_fs_ns_name = {
	.release	= single_release,
};

static int rawdata_release(struct inode *inode, struct file *file)

/* policy/raw_data/ * file ops */

#define SEQ_RAWDATA_FOPS(NAME)						      \
static int seq_rawdata_ ##NAME ##_open(struct inode *inode, struct file *file)\
{									      \
	return seq_rawdata_open(inode, file, seq_rawdata_ ##NAME ##_show);    \
}									      \
									      \
static const struct file_operations seq_rawdata_ ##NAME ##_fops = {	      \
	.owner		= THIS_MODULE,					      \
	.open		= seq_rawdata_ ##NAME ##_open,			      \
	.read		= seq_read,					      \
	.llseek		= seq_lseek,					      \
	.release	= seq_rawdata_release,				      \
}									      \

static int seq_rawdata_open(struct inode *inode, struct file *file,
			    int (*show)(struct seq_file *, void *))
{
	/* TODO: switch to loaddata when profile switched to symlink */
	aa_put_loaddata(file->private_data);
	struct aa_loaddata *data = __aa_get_loaddata(inode->i_private);
	int error;

	return 0;
	if (!data)
		/* lost race this ent is being reaped */
		return -ENOENT;

	error = single_open(file, show, data);
	if (error) {
		AA_BUG(file->private_data &&
		       ((struct seq_file *)file->private_data)->private);
		aa_put_loaddata(data);
	}

static int aa_fs_seq_raw_abi_show(struct seq_file *seq, void *v)
	return error;
}

static int seq_rawdata_release(struct inode *inode, struct file *file)
{
	struct aa_proxy *proxy = seq->private;
	struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
	struct seq_file *seq = (struct seq_file *) file->private_data;

	if (profile->rawdata->abi)
		seq_printf(seq, "v%d\n", profile->rawdata->abi);
	if (seq)
		aa_put_loaddata(seq->private);

	aa_put_profile(profile);
	return single_release(inode, file);
}

static int seq_rawdata_abi_show(struct seq_file *seq, void *v)
{
	struct aa_loaddata *data = seq->private;

	seq_printf(seq, "v%d\n", data->abi);

	return 0;
}

static int aa_fs_seq_raw_abi_open(struct inode *inode, struct file *file)
static int seq_rawdata_revision_show(struct seq_file *seq, void *v)
{
	return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_abi_show);
}
	struct aa_loaddata *data = seq->private;

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,
};
	seq_printf(seq, "%ld\n", data->revision);

static int aa_fs_seq_raw_hash_show(struct seq_file *seq, void *v)
	return 0;
}

static int seq_rawdata_hash_show(struct seq_file *seq, void *v)
{
	struct aa_proxy *proxy = seq->private;
	struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile);
	struct aa_loaddata *data = seq->private;
	unsigned int i, size = aa_hash_size();

	if (profile->rawdata->hash) {
	if (data->hash) {
		for (i = 0; i < size; i++)
			seq_printf(seq, "%.2x", profile->rawdata->hash[i]);
			seq_printf(seq, "%.2x", data->hash[i]);
		seq_putc(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,
};
SEQ_RAWDATA_FOPS(abi);
SEQ_RAWDATA_FOPS(revision);
SEQ_RAWDATA_FOPS(hash);

static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
			    loff_t *ppos)
@@ -631,27 +653,120 @@ static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
				       rawdata->size);
}

static int rawdata_open(struct inode *inode, struct file *file)
static int rawdata_release(struct inode *inode, struct file *file)
{
	struct aa_proxy *proxy = inode->i_private;
	struct aa_profile *profile;
	aa_put_loaddata(file->private_data);

	return 0;
}

static int rawdata_open(struct inode *inode, struct file *file)
{
	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);
	file->private_data = __aa_get_loaddata(inode->i_private);
	if (!file->private_data)
		/* lost race: this entry is being reaped */
		return -ENOENT;

	return 0;
}

static const struct file_operations aa_fs_rawdata_fops = {
static const struct file_operations rawdata_fops = {
	.open = rawdata_open,
	.read = rawdata_read,
	.llseek = generic_file_llseek,
	.release = rawdata_release,
};

static void remove_rawdata_dents(struct aa_loaddata *rawdata)
{
	int i;

	for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) {
		if (!IS_ERR_OR_NULL(rawdata->dents[i])) {
			/* no refcounts on i_private */
			securityfs_remove(rawdata->dents[i]);
			rawdata->dents[i] = NULL;
		}
	}
}

void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata)
{
	AA_BUG(rawdata->ns && !mutex_is_locked(&rawdata->ns->lock));

	if (rawdata->ns) {
		remove_rawdata_dents(rawdata);
		list_del_init(&rawdata->list);
		aa_put_ns(rawdata->ns);
		rawdata->ns = NULL;
	}
}

int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
{
	struct dentry *dent, *dir;

	AA_BUG(!ns);
	AA_BUG(!rawdata);
	AA_BUG(!mutex_is_locked(&ns->lock));
	AA_BUG(!ns_subdata_dir(ns));

	/*
	 * just use ns revision dir was originally created at. This is
	 * under ns->lock and if load is successful revision will be
	 * bumped and is guaranteed to be unique
	 */
	rawdata->name = kasprintf(GFP_KERNEL, "%ld", ns->revision);
	if (!rawdata->name)
		return -ENOMEM;

	dir = securityfs_create_dir(rawdata->name, ns_subdata_dir(ns));
	if (IS_ERR(dir))
		/* ->name freed when rawdata freed */
		return PTR_ERR(dir);
	rawdata->dents[AAFS_LOADDATA_DIR] = dir;

	dent = securityfs_create_file("abi", S_IFREG | 0444, dir, rawdata,
				      &seq_rawdata_abi_fops);
	if (IS_ERR(dent))
		goto fail;
	rawdata->dents[AAFS_LOADDATA_ABI] = dent;

	dent = securityfs_create_file("revision", S_IFREG | 0444, dir, rawdata,
				      &seq_rawdata_revision_fops);
	if (IS_ERR(dent))
		goto fail;
	rawdata->dents[AAFS_LOADDATA_REVISION] = dent;

	if (aa_g_hash_policy) {
		dent = securityfs_create_file("sha1", S_IFREG | 0444, dir,
					      rawdata, &seq_rawdata_hash_fops);
		if (IS_ERR(dent))
			goto fail;
		rawdata->dents[AAFS_LOADDATA_HASH] = dent;
	}

	dent = securityfs_create_file("raw_data", S_IFREG | 0444,
				      dir, rawdata, &rawdata_fops);
	if (IS_ERR(dent))
		goto fail;
	rawdata->dents[AAFS_LOADDATA_DATA] = dent;
	d_inode(dent)->i_size = rawdata->size;

	rawdata->ns = aa_get_ns(ns);
	list_add(&rawdata->list, &ns->rawdata_list);
	/* no refcount on inode rawdata */

	return 0;

fail:
	remove_rawdata_dents(rawdata);

	return PTR_ERR(dent);
}

/** fns to setup dynamic per profile/namespace files **/
void __aa_fs_profile_rmdir(struct aa_profile *profile)
{
@@ -703,7 +818,41 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name,
	return dent;
}

/* requires lock be held */
static int profile_depth(struct aa_profile *profile)
{
	int depth = 0;

	rcu_read_lock();
	for (depth = 0; profile; profile = rcu_access_pointer(profile->parent))
		depth++;
	rcu_read_unlock();

	return depth;
}

static int gen_symlink_name(char *buffer, size_t bsize, int depth,
			    const char *dirname, const char *fname)
{
	int error;

	for (; depth > 0; depth--) {
		if (bsize < 7)
			return -ENAMETOOLONG;
		strcpy(buffer, "../../");
		buffer += 6;
		bsize -= 6;
	}

	error = snprintf(buffer, bsize, "raw_data/%s/%s", dirname, fname);
	if (error >= bsize || error < 0)
		return -ENAMETOOLONG;

	return 0;
}

/*
 * Requires: @profile->ns->lock held
 */
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
{
	struct aa_profile *child;
@@ -766,26 +915,35 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
	}

	if (profile->rawdata) {
		dent = create_profile_file(dir, "raw_sha1", profile,
					   &aa_fs_seq_raw_hash_fops);
		char target[64];
		int depth = profile_depth(profile);

		error = gen_symlink_name(target, sizeof(target), depth,
					 profile->rawdata->name, "sha1");
		if (error < 0)
			goto fail2;
		dent = securityfs_create_symlink("raw_sha1", dir, target, NULL);
		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);
		error = gen_symlink_name(target, sizeof(target), depth,
					 profile->rawdata->name, "abi");
		if (error < 0)
			goto fail2;
		dent = securityfs_create_symlink("raw_abi", dir, target, NULL);
		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);
		error = gen_symlink_name(target, sizeof(target), depth,
					 profile->rawdata->name, "raw_data");
		if (error < 0)
			goto fail2;
		dent = securityfs_create_symlink("raw_data", dir, target, NULL);
		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) {
@@ -805,6 +963,16 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
	return error;
}

static void __aa_fs_list_remove_rawdata(struct aa_ns *ns)
{
	struct aa_loaddata *ent, *tmp;

	AA_BUG(!mutex_is_locked(&ns->lock));

	list_for_each_entry_safe(ent, tmp, &ns->rawdata_list, list)
		__aa_fs_remove_rawdata(ent);
}

void __aa_fs_ns_rmdir(struct aa_ns *ns)
{
	struct aa_ns *sub;
@@ -823,6 +991,8 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns)
		mutex_unlock(&sub->lock);
	}

	__aa_fs_list_remove_rawdata(ns);

	if (ns_subns_dir(ns)) {
		sub = d_inode(ns_subns_dir(ns))->i_private;
		aa_put_ns(sub);
+5 −0
Original line number Diff line number Diff line
@@ -106,6 +106,7 @@ enum aafs_prof_type {
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])

void __aa_bump_ns_revision(struct aa_ns *ns);
void __aa_fs_profile_rmdir(struct aa_profile *profile);
void __aa_fs_profile_migrate_dents(struct aa_profile *old,
				   struct aa_profile *new);
@@ -114,4 +115,8 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns);
int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent,
		     const char *name);

struct aa_loaddata;
void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata);
int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata);

#endif /* __AA_APPARMORFS_H */
+3 −0
Original line number Diff line number Diff line
@@ -68,6 +68,9 @@ struct aa_ns {
	atomic_t uniq_null;
	long uniq_id;
	int level;
	long revision;

	struct list_head rawdata_list;

	struct dentry *dents[AAFS_NS_SIZEOF];
};
+64 −4
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@

#include <linux/list.h>
#include <linux/kref.h>
#include <linux/dcache.h>
#include <linux/workqueue.h>

struct aa_load_ent {
	struct list_head list;
@@ -36,26 +38,84 @@ struct aa_load_ent *aa_load_ent_alloc(void);
#define PACKED_MODE_KILL	2
#define PACKED_MODE_UNCONFINED	3

/* struct aa_loaddata - buffer of policy load data set */
struct aa_ns;

enum {
	AAFS_LOADDATA_ABI = 0,
	AAFS_LOADDATA_REVISION,
	AAFS_LOADDATA_HASH,
	AAFS_LOADDATA_DATA,
	AAFS_LOADDATA_DIR,		/* must be last actual entry */
	AAFS_LOADDATA_NDENTS		/* count of entries */
};

/*
 * struct aa_loaddata - buffer of policy raw_data set
 *
 * there is no loaddata ref for being on ns list, nor a ref from
 * d_inode(@dentry) when grab a ref from these, @ns->lock must be held
 * && __aa_get_loaddata() needs to be used, and the return value
 * checked, if NULL the loaddata is already being reaped and should be
 * considered dead.
 */
struct aa_loaddata {
	struct kref count;
	struct list_head list;
	struct work_struct work;
	struct dentry *dents[AAFS_LOADDATA_NDENTS];
	struct aa_ns *ns;
	char *name;
	size_t size;
	long revision;			/* the ns policy revision this caused */
	int abi;
	unsigned char *hash;

	char data[];
};

int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);

/**
 * __aa_get_loaddata - get a reference count to uncounted data reference
 * @data: reference to get a count on
 *
 * Returns: pointer to reference OR NULL if race is lost and reference is
 *          being repeated.
 * Requires: @data->ns->lock held, and the return code MUST be checked
 *
 * Use only from inode->i_private and @data->list found references
 */
static inline struct aa_loaddata *
aa_get_loaddata(struct aa_loaddata *data)
__aa_get_loaddata(struct aa_loaddata *data)
{
	if (data)
		kref_get(&(data->count));
	if (data && kref_get_unless_zero(&(data->count)))
		return data;

	return NULL;
}

/**
 * aa_get_loaddata - get a reference count from a counted data reference
 * @data: reference to get a count on
 *
 * Returns: point to reference
 * Requires: @data to have a valid reference count on it. It is a bug
 *           if the race to reap can be encountered when it is used.
 */
static inline struct aa_loaddata *
aa_get_loaddata(struct aa_loaddata *data)
{
	struct aa_loaddata *tmp = __aa_get_loaddata(data);

	AA_BUG(data && !tmp);

	return tmp;
}

void __aa_loaddata_update(struct aa_loaddata *data, long revision);
bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r);
void aa_loaddata_kref(struct kref *kref);
struct aa_loaddata *aa_loaddata_alloc(size_t size);
static inline void aa_put_loaddata(struct aa_loaddata *data)
{
	if (data)
+45 −1
Original line number Diff line number Diff line
@@ -840,10 +840,13 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
	const char *ns_name, *info = NULL;
	struct aa_ns *ns = NULL;
	struct aa_load_ent *ent, *tmp;
	struct aa_loaddata *rawdata_ent;
	const char *op = OP_PROF_REPL;
	ssize_t count, error;

	LIST_HEAD(lh);

	aa_get_loaddata(udata);
	/* released below */
	error = aa_unpack(udata, &lh, &ns_name);
	if (error)
@@ -887,9 +890,24 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
		ns = aa_get_ns(view);

	mutex_lock(&ns->lock);
	/* check for duplicate rawdata blobs: space and file dedup */
	list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) {
		if (aa_rawdata_eq(rawdata_ent, udata)) {
			struct aa_loaddata *tmp;

			tmp = __aa_get_loaddata(rawdata_ent);
			/* check we didn't fail the race */
			if (tmp) {
				aa_put_loaddata(udata);
				udata = tmp;
				break;
			}
		}
	}
	/* 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);
@@ -929,6 +947,14 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
	}

	/* create new fs entries for introspection if needed */
	if (!udata->dents[AAFS_LOADDATA_DIR]) {
		error = __aa_fs_create_rawdata(ns, udata);
		if (error) {
			info = "failed to create raw_data dir and files";
			ent = NULL;
			goto fail_lock;
		}
	}
	list_for_each_entry(ent, &lh, list) {
		if (ent->old) {
			/* inherit old interface files */
@@ -955,10 +981,24 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
	}

	/* Done with checks that may fail - do actual replacement */
	__aa_bump_ns_revision(ns);
	__aa_loaddata_update(udata, ns->revision);
	list_for_each_entry_safe(ent, tmp, &lh, list) {
		list_del_init(&ent->list);
		op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL;

		if (ent->old && ent->old->rawdata == ent->new->rawdata) {
			/* dedup actual profile replacement */
			audit_policy(profile, op, ns_name, ent->new->base.hname,
				     "same as current profile, skipping",
				     error);
			goto skip;
		}

		/*
		 * TODO: finer dedup based on profile range in data. Load set
		 * can differ but profile may remain unchanged
		 */
		audit_policy(profile, op, NULL, ent->new->base.hname,
			     NULL, error);

@@ -998,12 +1038,14 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
					   aa_get_profile(ent->new));
			__list_add_profile(&ns->base.profiles, ent->new);
		}
	skip:
		aa_load_ent_free(ent);
	}
	mutex_unlock(&ns->lock);

out:
	aa_put_ns(ns);
	aa_put_loaddata(udata);

	if (error)
		return error;
@@ -1013,7 +1055,7 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile,
	mutex_unlock(&ns->lock);

	/* audit cause of failure */
	op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
	op = (ent && !ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
fail:
	audit_policy(profile, op, ns_name, ent ? ent->new->base.hname : NULL,
		     info, error);
@@ -1085,6 +1127,7 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj,
		/* remove namespace - can only happen if fqname[0] == ':' */
		mutex_lock(&ns->parent->lock);
		__aa_remove_ns(ns);
		__aa_bump_ns_revision(ns);
		mutex_unlock(&ns->parent->lock);
	} else {
		/* remove profile */
@@ -1097,6 +1140,7 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj,
		}
		name = profile->base.hname;
		__remove_profile(profile);
		__aa_bump_ns_revision(ns);
		mutex_unlock(&ns->lock);
	}

Loading