Commit f4d6b94b authored by Jon Tourville's avatar Jon Tourville Committed by John Johansen
Browse files

apparmor: use zstd compression for profile data



Change the algorithm used by apparmor to compress profile data from
zlib to zstd, using the new zstd API introduced in 5.16.

Zstd provides a larger range of compression levels than zlib and
significantly better performance at the default level (for a relatively
small increase in compressed size).

The apparmor module parameter raw_data_compression_level is now clamped
to the minimum and maximum compression levels reported by the zstd
library. A compression level of 0 retains the previous behavior of
disabling policy compression instead of using zstd's behavior, which is
to use the default compression level.

Signed-off-by: default avatarJon Tourville <jon.tourville@canonical.com>
Signed-off-by: default avatarJohn Johansen <john.johansen@canonical.com>
parent f47acc4b
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -85,8 +85,8 @@ config SECURITY_APPARMOR_HASH_DEFAULT
config SECURITY_APPARMOR_EXPORT_BINARY
	bool "Allow exporting the raw binary policy"
	depends on SECURITY_APPARMOR_INTROSPECT_POLICY
	select ZLIB_INFLATE
	select ZLIB_DEFLATE
	select ZSTD_COMPRESS
	select ZSTD_DECOMPRESS
	default y
	help
	  This option allows reading back binary policy as it was loaded.
+24 −36
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@
#include <linux/fs.h>
#include <linux/fs_context.h>
#include <linux/poll.h>
#include <linux/zlib.h>
#include <linux/zstd.h>
#include <uapi/linux/major.h>
#include <uapi/linux/magic.h>

@@ -1297,42 +1297,30 @@ 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)
static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen)
{
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
	if (aa_g_rawdata_compression_level != 0) {
		int error = 0;
		struct z_stream_s strm;

		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;
	if (aa_g_rawdata_compression_level == 0) {
		const size_t wksp_len = zstd_dctx_workspace_bound();
		zstd_dctx *ctx;
		void *wksp;
		size_t out_len;
		int ret = 0;

		wksp = kvzalloc(wksp_len, GFP_KERNEL);
		if (!wksp) {
			ret = -ENOMEM;
			goto cleanup;
		}

		out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen);
		if (zstd_is_error(out_len)) {
			ret = -EINVAL;
			goto cleanup;
		}
cleanup:
		kvfree(wksp);
		return ret;
	}
#endif

@@ -1381,7 +1369,7 @@ static int rawdata_open(struct inode *inode, struct file *file)

	private->loaddata = loaddata;

	error = deflate_decompress(loaddata->data, loaddata->compressed_size,
	error = decompress_zstd(loaddata->data, loaddata->compressed_size,
				RAWDATA_F_DATA_BUF(private),
				loaddata->size);
	if (error)
+5 −5
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@
#include <linux/user_namespace.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
#include <linux/zlib.h>
#include <linux/zstd.h>
#include <net/sock.h>
#include <uapi/linux/mount.h>

@@ -1361,7 +1361,7 @@ module_param_named(export_binary, aa_g_export_binary, aabool, 0600);
#endif

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

@@ -1543,9 +1543,9 @@ static int param_set_aacompressionlevel(const char *val,
	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",
					       zstd_min_clevel(),
					       zstd_max_clevel());
	pr_info("AppArmor: policy rawdata compression level set to %d\n",
		aa_g_rawdata_compression_level);

	return error;
+50 −59
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@
#include <asm/unaligned.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/zlib.h>
#include <linux/zstd.h>

#include "include/apparmor.h"
#include "include/audit.h"
@@ -1059,81 +1059,73 @@ 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)
static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen)
{
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
	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;
	const zstd_parameters params =
		zstd_get_params(aa_g_rawdata_compression_level, slen);
	const size_t wksp_len = zstd_cctx_workspace_bound(&params.cParams);
	void *wksp = NULL;
	zstd_cctx *ctx = NULL;
	size_t out_len = zstd_compress_bound(slen);
	void *out = NULL;
	int ret = 0;

	error = zlib_deflateInit(&strm, aa_g_rawdata_compression_level);
	if (error != Z_OK) {
		error = -ENOMEM;
		goto fail_deflate_init;
	out = kvzalloc(out_len, GFP_KERNEL);
	if (!out) {
		ret = -ENOMEM;
		goto cleanup;
	}

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

	strm.next_in = src;
	strm.avail_in = slen;
	strm.next_out = stgbuf;
	strm.avail_out = stglen;
	ctx = zstd_init_cctx(wksp, wksp_len);
	if (!ctx) {
		ret = -EINVAL;
		goto cleanup;
	}

	error = zlib_deflate(&strm, Z_FINISH);
	if (error != Z_STREAM_END) {
		error = -EINVAL;
		goto fail_deflate;
	out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, &params);
	if (zstd_is_error(out_len)) {
		ret = -EINVAL;
		goto cleanup;
	}
	error = 0;

	if (is_vmalloc_addr(stgbuf)) {
		dstbuf = kvzalloc(strm.total_out, GFP_KERNEL);
		if (dstbuf) {
			memcpy(dstbuf, stgbuf, strm.total_out);
			kvfree(stgbuf);
	if (is_vmalloc_addr(out)) {
		*dst = kvzalloc(out_len, GFP_KERNEL);
		if (*dst) {
			memcpy(*dst, out, out_len);
			kvfree(out);
			out = NULL;
		}
	} else
	} 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);
		*dst = krealloc(out, out_len, GFP_KERNEL);
	}

	if (!dstbuf) {
		error = -ENOMEM;
		goto fail_deflate;
	if (!*dst) {
		ret = -ENOMEM;
		goto cleanup;
	}

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

fail_stg_alloc:
	zlib_deflateEnd(&strm);
fail_deflate_init:
	kvfree(strm.workspace);
	return error;
cleanup:
	if (ret) {
		kvfree(out);
		*dst = NULL;
	}

fail_deflate:
	kvfree(stgbuf);
	goto fail_stg_alloc;
	kvfree(wksp);
	return ret;
#else
	*dlen = slen;
	return 0;
@@ -1142,7 +1134,6 @@ static int deflate_compress(const char *src, size_t slen, char **dst,

static int compress_loaddata(struct aa_loaddata *data)
{

	AA_BUG(data->compressed_size > 0);

	/*
@@ -1151,7 +1142,7 @@ static int compress_loaddata(struct aa_loaddata *data)
	 */
	if (aa_g_rawdata_compression_level != 0) {
		void *udata = data->data;
		int error = deflate_compress(udata, data->size, &data->data,
		int error = compress_zstd(udata, data->size, &data->data,
					  &data->compressed_size);
		if (error)
			return error;