This changes 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). At the default compression levels, zstd's execution time was 16% that of zlib with a size of 111%. At maximum compression levels, zstd's execution time was 187% that of zlib with a size of 88%. This gives users options for either improving performance or decreasing memory usage over zlib. Performance testing was done in the kernel against the default set of profiles loaded by a fresh install of Ubuntu jammy desktop. Signed-off-by: Jon Tourville <[email protected]> --- security/apparmor/Kconfig | 4 +- security/apparmor/apparmorfs.c | 63 ++++++++---------- security/apparmor/lsm.c | 10 +-- security/apparmor/policy_unpack.c | 104 +++++++++++------------------- 4 files changed, 70 insertions(+), 111 deletions(-) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 348ed6cfa08a..522546d52c4d 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -6,8 +6,8 @@ config SECURITY_APPARMOR select SECURITY_PATH select SECURITYFS select SECURITY_NETWORK - select ZLIB_INFLATE - select ZLIB_DEFLATE + select ZSTD_COMPRESS + select ZSTD_DECOMPRESS default n help This enables the AppArmor security module. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 0797edb2fb3d..bb6cb1abf0d3 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -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> @@ -1292,46 +1292,35 @@ 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(const char *src, size_t slen, char *dst, size_t dlen) { - int error; - struct z_stream_s strm; + const size_t wksp_len = zstd_dctx_workspace_bound(); + zstd_dctx *ctx; + void *wksp; + size_t out_len; + int ret = 0; - if (aa_g_rawdata_compression_level == 0) { - if (dlen < slen) - return -EINVAL; - memcpy(dst, src, slen); - return 0; + wksp = kvzalloc(wksp_len, GFP_KERNEL); + if (!wksp) { + ret = -ENOMEM; + goto cleanup; } - 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; + ctx = zstd_init_dctx(wksp, wksp_len); + if (!ctx) { + ret = -EINVAL; + 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; + out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen); + if (zstd_is_error(out_len)) { + ret = -EINVAL; + goto cleanup; + } - zlib_inflateEnd(&strm); -fail_inflate_init: - kvfree(strm.workspace); - return error; +cleanup: + kvfree(wksp); + return ret; } static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, @@ -1373,9 +1362,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; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 0d6585056f3d..715c10fe6d3e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -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> @@ -1331,7 +1331,7 @@ 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; +int aa_g_rawdata_compression_level = ZSTD_CLEVEL_DEFAULT; module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level, aacompressionlevel, 0400); @@ -1513,9 +1513,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; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 0acca6f2a93f..a974d02e49e6 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -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" @@ -1049,85 +1049,55 @@ 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) { - 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; + const zstd_parameters params = + zstd_get_params(aa_g_rawdata_compression_level, slen); + const size_t wksp_len = zstd_cctx_workspace_bound(¶ms.cParams); + void *wksp = NULL; + zstd_cctx *ctx = NULL; + size_t out_len = zstd_compress_bound(slen); + void *out = NULL; + int ret = 0; + + 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); - } - } 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; + out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, ¶ms); + if (zstd_is_error(out_len)) { + ret = -EINVAL; + goto cleanup; } - *dst = dstbuf; - *dlen = strm.total_out; + *dst = out; + *dlen = out_len; -fail_stg_alloc: - zlib_deflateEnd(&strm); -fail_deflate_init: - kvfree(strm.workspace); - return error; +cleanup: + if (ret) { + kvfree(out); + } -fail_deflate: - kvfree(stgbuf); - goto fail_stg_alloc; + kvfree(wksp); + return ret; } static int compress_loaddata(struct aa_loaddata *data) { - AA_BUG(data->compressed_size > 0); /* @@ -1136,8 +1106,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; -- 2.34.1
