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: Jon Tourville <jon.tourville@canonical.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
Jon Tourville 2022-07-11 11:36:08 -05:00 committed by John Johansen
parent f47acc4b7c
commit f4d6b94b40
4 changed files with 81 additions and 102 deletions

View File

@ -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.

View File

@ -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;
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;
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;
wksp = kvzalloc(wksp_len, GFP_KERNEL);
if (!wksp) {
ret = -ENOMEM;
goto cleanup;
}
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;
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,9 +1369,9 @@ static int rawdata_open(struct inode *inode, struct file *file)
private->loaddata = loaddata;
error = deflate_decompress(loaddata->data, loaddata->compressed_size,
RAWDATA_F_DATA_BUF(private),
loaddata->size);
error = decompress_zstd(loaddata->data, loaddata->compressed_size,
RAWDATA_F_DATA_BUF(private),
loaddata->size);
if (error)
goto fail_decompress;

View File

@ -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;

View File

@ -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);
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;
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;
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;
error = zlib_deflate(&strm, Z_FINISH);
if (error != Z_STREAM_END) {
error = -EINVAL;
goto fail_deflate;
ctx = zstd_init_cctx(wksp, wksp_len);
if (!ctx) {
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);
out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, &params);
if (zstd_is_error(out_len)) {
ret = -EINVAL;
goto cleanup;
}
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);
if (!dstbuf) {
error = -ENOMEM;
goto fail_deflate;
*dst = krealloc(out, out_len, GFP_KERNEL);
}
*dst = dstbuf;
*dlen = strm.total_out;
if (!*dst) {
ret = -ENOMEM;
goto cleanup;
}
fail_stg_alloc:
zlib_deflateEnd(&strm);
fail_deflate_init:
kvfree(strm.workspace);
return error;
*dlen = out_len;
fail_deflate:
kvfree(stgbuf);
goto fail_stg_alloc;
cleanup:
if (ret) {
kvfree(out);
*dst = NULL;
}
kvfree(wksp);
return ret;
#else
*dlen = slen;
return 0;
@ -1142,7 +1134,6 @@ fail_deflate:
static int compress_loaddata(struct aa_loaddata *data)
{
AA_BUG(data->compressed_size > 0);
/*
@ -1151,8 +1142,8 @@ 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,
&data->compressed_size);
int error = compress_zstd(udata, data->size, &data->data,
&data->compressed_size);
if (error)
return error;