Commit 63c16c3a authored by Chris Coulson's avatar Chris Coulson Committed by John Johansen
Browse files

apparmor: Initial implementation of raw policy blob compression



This adds an initial implementation of raw policy blob compression,
using deflate. Compression level can be controlled via a new sysctl,
"apparmor.rawdata_compression_level", which can be set to a value
between 0 (no compression) and 9 (highest compression).

Signed-off-by: default avatarChris Coulson <chris.coulson@canonical.com>
Signed-off-by: default avatarJohn Johansen <john.johansen@canonical.com>
parent 582549e3
Loading
Loading
Loading
Loading
+124 −6
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <linux/rcupdate.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/zlib.h>
#include <uapi/linux/major.h>
#include <uapi/linux/magic.h>

@@ -68,6 +69,35 @@
 * support fns
 */

struct rawdata_f_data {
	struct aa_loaddata *loaddata;
};

#define RAWDATA_F_DATA_BUF(p) (char *)(p + 1)

static void rawdata_f_data_free(struct rawdata_f_data *private)
{
	if (!private)
		return;

	aa_put_loaddata(private->loaddata);
	kvfree(private);
}

static struct rawdata_f_data *rawdata_f_data_alloc(size_t size)
{
	struct rawdata_f_data *ret;

	if (size > SIZE_MAX - sizeof(*ret))
		return ERR_PTR(-EINVAL);

	ret = kvzalloc(sizeof(*ret) + size, GFP_KERNEL);
	if (!ret)
		return ERR_PTR(-ENOMEM);

	return ret;
}

/**
 * aa_mangle_name - mangle a profile name to std profile layout form
 * @name: profile name to mangle  (NOT NULL)
@@ -1275,36 +1305,117 @@ static int seq_rawdata_hash_show(struct seq_file *seq, void *v)
	return 0;
}

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

	seq_printf(seq, "%zu\n", data->compressed_size);

	return 0;
}

SEQ_RAWDATA_FOPS(abi);
SEQ_RAWDATA_FOPS(revision);
SEQ_RAWDATA_FOPS(hash);
SEQ_RAWDATA_FOPS(compressed_size);

static int deflate_decompress(char *src, size_t slen, char *dst, size_t dlen)
{
	int error;
	struct z_stream_s strm;

	if (aa_g_rawdata_compression_level == 0) {
		if (dlen < slen)
			return -EINVAL;
		memcpy(dst, src, slen);
		return 0;
	}

	memset(&strm, 0, sizeof(strm));

	strm.workspace = kvzalloc(zlib_inflate_workspacesize(), GFP_KERNEL);
	if (!strm.workspace)
		return -ENOMEM;

	strm.next_in = src;
	strm.avail_in = slen;

	error = zlib_inflateInit(&strm);
	if (error != Z_OK) {
		error = -ENOMEM;
		goto fail_inflate_init;
	}

	strm.next_out = dst;
	strm.avail_out = dlen;

	error = zlib_inflate(&strm, Z_FINISH);
	if (error != Z_STREAM_END)
		error = -EINVAL;
	else
		error = 0;

	zlib_inflateEnd(&strm);
fail_inflate_init:
	kvfree(strm.workspace);
	return error;
}

static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
			    loff_t *ppos)
{
	struct aa_loaddata *rawdata = file->private_data;
	struct rawdata_f_data *private = file->private_data;

	return simple_read_from_buffer(buf, size, ppos, rawdata->data,
				       rawdata->size);
	return simple_read_from_buffer(buf, size, ppos,
				       RAWDATA_F_DATA_BUF(private),
				       private->loaddata->size);
}

static int rawdata_release(struct inode *inode, struct file *file)
{
	aa_put_loaddata(file->private_data);
	rawdata_f_data_free(file->private_data);

	return 0;
}

static int rawdata_open(struct inode *inode, struct file *file)
{
	int error;
	struct aa_loaddata *loaddata;
	struct rawdata_f_data *private;

	if (!policy_view_capable(NULL))
		return -EACCES;
	file->private_data = __aa_get_loaddata(inode->i_private);
	if (!file->private_data)

	loaddata = __aa_get_loaddata(inode->i_private);
	if (!loaddata)
		/* lost race: this entry is being reaped */
		return -ENOENT;

	private = rawdata_f_data_alloc(loaddata->size);
	if (IS_ERR(private)) {
		error = PTR_ERR(private);
		goto fail_private_alloc;
	}

	private->loaddata = loaddata;

	error = deflate_decompress(loaddata->data, loaddata->compressed_size,
				   RAWDATA_F_DATA_BUF(private),
				   loaddata->size);
	if (error)
		goto fail_decompress;

	file->private_data = private;
	return 0;

fail_decompress:
	rawdata_f_data_free(private);
	return error;

fail_private_alloc:
	aa_put_loaddata(loaddata);
	return error;
}

static const struct file_operations rawdata_fops = {
@@ -1383,6 +1494,13 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
		rawdata->dents[AAFS_LOADDATA_HASH] = dent;
	}

	dent = aafs_create_file("compressed_size", S_IFREG | 0444, dir,
				rawdata,
				&seq_rawdata_compressed_size_fops);
	if (IS_ERR(dent))
		goto fail;
	rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent;

	dent = aafs_create_file("raw_data", S_IFREG | 0444,
				      dir, rawdata, &rawdata_fops);
	if (IS_ERR(dent))
+1 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ extern enum audit_mode aa_g_audit;
extern bool aa_g_audit_header;
extern bool aa_g_debug;
extern bool aa_g_hash_policy;
extern int aa_g_rawdata_compression_level;
extern bool aa_g_lock_policy;
extern bool aa_g_logsyscall;
extern bool aa_g_paranoid_load;
+7 −1
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ enum {
	AAFS_LOADDATA_REVISION,
	AAFS_LOADDATA_HASH,
	AAFS_LOADDATA_DATA,
	AAFS_LOADDATA_COMPRESSED_SIZE,
	AAFS_LOADDATA_DIR,		/* must be last actual entry */
	AAFS_LOADDATA_NDENTS		/* count of entries */
};
@@ -65,11 +66,16 @@ struct aa_loaddata {
	struct dentry *dents[AAFS_LOADDATA_NDENTS];
	struct aa_ns *ns;
	char *name;
	size_t size;
	size_t size;			/* the original size of the payload */
	size_t compressed_size;		/* the compressed size of the payload */
	long revision;			/* the ns policy revision this caused */
	int abi;
	unsigned char *hash;

	/* Pointer to payload. If @compressed_size > 0, then this is the
	 * compressed version of the payload, else it is the uncompressed
	 * version (with the size indicated by @size).
	 */
	char *data;
};

+47 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
#include <linux/user_namespace.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
#include <linux/zlib.h>
#include <net/sock.h>
#include <uapi/linux/mount.h>

@@ -1266,6 +1267,16 @@ static const struct kernel_param_ops param_ops_aauint = {
	.get = param_get_aauint
};

static int param_set_aacompressionlevel(const char *val,
					const struct kernel_param *kp);
static int param_get_aacompressionlevel(char *buffer,
					const struct kernel_param *kp);
#define param_check_aacompressionlevel param_check_int
static const struct kernel_param_ops param_ops_aacompressionlevel = {
	.set = param_set_aacompressionlevel,
	.get = param_get_aacompressionlevel
};

static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp);
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp);
#define param_check_aalockpolicy param_check_bool
@@ -1296,6 +1307,11 @@ bool aa_g_hash_policy = IS_ENABLED(CONFIG_SECURITY_APPARMOR_HASH_DEFAULT);
module_param_named(hash_policy, aa_g_hash_policy, aabool, S_IRUSR | S_IWUSR);
#endif

/* policy loaddata compression level */
int aa_g_rawdata_compression_level = Z_DEFAULT_COMPRESSION;
module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level,
		   aacompressionlevel, 0400);

/* Debug mode */
bool aa_g_debug = IS_ENABLED(CONFIG_SECURITY_APPARMOR_DEBUG_MESSAGES);
module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);
@@ -1460,6 +1476,37 @@ static int param_get_aaintbool(char *buffer, const struct kernel_param *kp)
	return param_get_bool(buffer, &kp_local);
}

static int param_set_aacompressionlevel(const char *val,
					const struct kernel_param *kp)
{
	int error;

	if (!apparmor_enabled)
		return -EINVAL;
	if (apparmor_initialized)
		return -EPERM;

	error = param_set_int(val, kp);

	aa_g_rawdata_compression_level = clamp(aa_g_rawdata_compression_level,
					       Z_NO_COMPRESSION,
					       Z_BEST_COMPRESSION);
	pr_info("AppArmor: policy rawdata compression level set to %u\n",
		aa_g_rawdata_compression_level);

	return error;
}

static int param_get_aacompressionlevel(char *buffer,
					const struct kernel_param *kp)
{
	if (!apparmor_enabled)
		return -EINVAL;
	if (apparmor_initialized && !policy_view_capable(NULL))
		return -EPERM;
	return param_get_int(buffer, kp);
}

static int param_get_audit(char *buffer, const struct kernel_param *kp)
{
	if (!apparmor_enabled)
+106 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include <asm/unaligned.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/zlib.h>

#include "include/apparmor.h"
#include "include/audit.h"
@@ -143,9 +144,11 @@ bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r)
{
	if (l->size != r->size)
		return false;
	if (l->compressed_size != r->compressed_size)
		return false;
	if (aa_g_hash_policy && memcmp(l->hash, r->hash, aa_hash_size()) != 0)
		return false;
	return memcmp(l->data, r->data, r->size) == 0;
	return memcmp(l->data, r->data, r->compressed_size ?: r->size) == 0;
}

/*
@@ -1012,6 +1015,105 @@ struct aa_load_ent *aa_load_ent_alloc(void)
	return ent;
}

static int deflate_compress(const char *src, size_t slen, char **dst,
			    size_t *dlen)
{
	int error;
	struct z_stream_s strm;
	void *stgbuf, *dstbuf;
	size_t stglen = deflateBound(slen);

	memset(&strm, 0, sizeof(strm));

	if (stglen < slen)
		return -EFBIG;

	strm.workspace = kvzalloc(zlib_deflate_workspacesize(MAX_WBITS,
							     MAX_MEM_LEVEL),
				  GFP_KERNEL);
	if (!strm.workspace)
		return -ENOMEM;

	error = zlib_deflateInit(&strm, aa_g_rawdata_compression_level);
	if (error != Z_OK) {
		error = -ENOMEM;
		goto fail_deflate_init;
	}

	stgbuf = kvzalloc(stglen, GFP_KERNEL);
	if (!stgbuf) {
		error = -ENOMEM;
		goto fail_stg_alloc;
	}

	strm.next_in = src;
	strm.avail_in = slen;
	strm.next_out = stgbuf;
	strm.avail_out = stglen;

	error = zlib_deflate(&strm, Z_FINISH);
	if (error != Z_STREAM_END) {
		error = -EINVAL;
		goto fail_deflate;
	}
	error = 0;

	if (is_vmalloc_addr(stgbuf)) {
		dstbuf = kvzalloc(strm.total_out, GFP_KERNEL);
		if (dstbuf) {
			memcpy(dstbuf, stgbuf, strm.total_out);
			vfree(stgbuf);
		}
	} else
		/*
		 * If the staging buffer was kmalloc'd, then using krealloc is
		 * probably going to be faster. The destination buffer will
		 * always be smaller, so it's just shrunk, avoiding a memcpy
		 */
		dstbuf = krealloc(stgbuf, strm.total_out, GFP_KERNEL);

	if (!dstbuf) {
		error = -ENOMEM;
		goto fail_deflate;
	}

	*dst = dstbuf;
	*dlen = strm.total_out;

fail_stg_alloc:
	zlib_deflateEnd(&strm);
fail_deflate_init:
	kvfree(strm.workspace);
	return error;

fail_deflate:
	kvfree(stgbuf);
	goto fail_stg_alloc;
}

static int compress_loaddata(struct aa_loaddata *data)
{

	AA_BUG(data->compressed_size > 0);

	/*
	 * Shortcut the no compression case, else we increase the amount of
	 * storage required by a small amount
	 */
	if (aa_g_rawdata_compression_level != 0) {
		void *udata = data->data;
		int error = deflate_compress(udata, data->size, &data->data,
					     &data->compressed_size);
		if (error)
			return error;

		kvfree(udata);
	} else
		data->compressed_size = data->size;

	return 0;
}

/**
 * aa_unpack - unpack packed binary profile(s) data loaded from user space
 * @udata: user data copied to kmem  (NOT NULL)
@@ -1080,6 +1182,9 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
			goto fail;
		}
	}
	error = compress_loaddata(udata);
	if (error)
		goto fail;
	return 0;

fail_profile: