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

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
...@@ -85,8 +85,8 @@ config SECURITY_APPARMOR_HASH_DEFAULT ...@@ -85,8 +85,8 @@ config SECURITY_APPARMOR_HASH_DEFAULT
config SECURITY_APPARMOR_EXPORT_BINARY config SECURITY_APPARMOR_EXPORT_BINARY
bool "Allow exporting the raw binary policy" bool "Allow exporting the raw binary policy"
depends on SECURITY_APPARMOR_INTROSPECT_POLICY depends on SECURITY_APPARMOR_INTROSPECT_POLICY
select ZLIB_INFLATE select ZSTD_COMPRESS
select ZLIB_DEFLATE select ZSTD_DECOMPRESS
default y default y
help help
This option allows reading back binary policy as it was loaded. This option allows reading back binary policy as it was loaded.
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/fs_context.h> #include <linux/fs_context.h>
#include <linux/poll.h> #include <linux/poll.h>
#include <linux/zlib.h> #include <linux/zstd.h>
#include <uapi/linux/major.h> #include <uapi/linux/major.h>
#include <uapi/linux/magic.h> #include <uapi/linux/magic.h>
...@@ -1297,42 +1297,30 @@ SEQ_RAWDATA_FOPS(revision); ...@@ -1297,42 +1297,30 @@ SEQ_RAWDATA_FOPS(revision);
SEQ_RAWDATA_FOPS(hash); SEQ_RAWDATA_FOPS(hash);
SEQ_RAWDATA_FOPS(compressed_size); 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 #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
if (aa_g_rawdata_compression_level != 0) { if (aa_g_rawdata_compression_level == 0) {
int error = 0; const size_t wksp_len = zstd_dctx_workspace_bound();
struct z_stream_s strm; zstd_dctx *ctx;
void *wksp;
memset(&strm, 0, sizeof(strm)); size_t out_len;
int ret = 0;
strm.workspace = kvzalloc(zlib_inflate_workspacesize(), GFP_KERNEL);
if (!strm.workspace) wksp = kvzalloc(wksp_len, GFP_KERNEL);
return -ENOMEM; if (!wksp) {
ret = -ENOMEM;
strm.next_in = src; goto cleanup;
strm.avail_in = slen; }
error = zlib_inflateInit(&strm); out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen);
if (error != Z_OK) { if (zstd_is_error(out_len)) {
error = -ENOMEM; ret = -EINVAL;
goto fail_inflate_init; goto cleanup;
} }
cleanup:
strm.next_out = dst; kvfree(wksp);
strm.avail_out = dlen; return ret;
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;
} }
#endif #endif
...@@ -1381,7 +1369,7 @@ static int rawdata_open(struct inode *inode, struct file *file) ...@@ -1381,7 +1369,7 @@ static int rawdata_open(struct inode *inode, struct file *file)
private->loaddata = loaddata; private->loaddata = loaddata;
error = deflate_decompress(loaddata->data, loaddata->compressed_size, error = decompress_zstd(loaddata->data, loaddata->compressed_size,
RAWDATA_F_DATA_BUF(private), RAWDATA_F_DATA_BUF(private),
loaddata->size); loaddata->size);
if (error) if (error)
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
#include <linux/user_namespace.h> #include <linux/user_namespace.h>
#include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h> #include <linux/netfilter_ipv6.h>
#include <linux/zlib.h> #include <linux/zstd.h>
#include <net/sock.h> #include <net/sock.h>
#include <uapi/linux/mount.h> #include <uapi/linux/mount.h>
...@@ -1361,7 +1361,7 @@ module_param_named(export_binary, aa_g_export_binary, aabool, 0600); ...@@ -1361,7 +1361,7 @@ module_param_named(export_binary, aa_g_export_binary, aabool, 0600);
#endif #endif
/* policy loaddata compression level */ /* 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, module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level,
aacompressionlevel, 0400); aacompressionlevel, 0400);
...@@ -1543,9 +1543,9 @@ static int param_set_aacompressionlevel(const char *val, ...@@ -1543,9 +1543,9 @@ static int param_set_aacompressionlevel(const char *val,
error = param_set_int(val, kp); error = param_set_int(val, kp);
aa_g_rawdata_compression_level = clamp(aa_g_rawdata_compression_level, aa_g_rawdata_compression_level = clamp(aa_g_rawdata_compression_level,
Z_NO_COMPRESSION, zstd_min_clevel(),
Z_BEST_COMPRESSION); zstd_max_clevel());
pr_info("AppArmor: policy rawdata compression level set to %u\n", pr_info("AppArmor: policy rawdata compression level set to %d\n",
aa_g_rawdata_compression_level); aa_g_rawdata_compression_level);
return error; return error;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
#include <asm/unaligned.h> #include <asm/unaligned.h>
#include <linux/ctype.h> #include <linux/ctype.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/zlib.h> #include <linux/zstd.h>
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/audit.h" #include "include/audit.h"
...@@ -1059,81 +1059,73 @@ struct aa_load_ent *aa_load_ent_alloc(void) ...@@ -1059,81 +1059,73 @@ struct aa_load_ent *aa_load_ent_alloc(void)
return ent; return ent;
} }
static int deflate_compress(const char *src, size_t slen, char **dst, static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen)
size_t *dlen)
{ {
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
int error; const zstd_parameters params =
struct z_stream_s strm; zstd_get_params(aa_g_rawdata_compression_level, slen);
void *stgbuf, *dstbuf; const size_t wksp_len = zstd_cctx_workspace_bound(&params.cParams);
size_t stglen = deflateBound(slen); void *wksp = NULL;
zstd_cctx *ctx = NULL;
memset(&strm, 0, sizeof(strm)); size_t out_len = zstd_compress_bound(slen);
void *out = NULL;
if (stglen < slen) int ret = 0;
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); out = kvzalloc(out_len, GFP_KERNEL);
if (error != Z_OK) { if (!out) {
error = -ENOMEM; ret = -ENOMEM;
goto fail_deflate_init; goto cleanup;
} }
stgbuf = kvzalloc(stglen, GFP_KERNEL); wksp = kvzalloc(wksp_len, GFP_KERNEL);
if (!stgbuf) { if (!wksp) {
error = -ENOMEM; ret = -ENOMEM;
goto fail_stg_alloc; goto cleanup;
} }
strm.next_in = src; ctx = zstd_init_cctx(wksp, wksp_len);
strm.avail_in = slen; if (!ctx) {
strm.next_out = stgbuf; ret = -EINVAL;
strm.avail_out = stglen; goto cleanup;
}
error = zlib_deflate(&strm, Z_FINISH); out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, &params);
if (error != Z_STREAM_END) { if (zstd_is_error(out_len)) {
error = -EINVAL; ret = -EINVAL;
goto fail_deflate; goto cleanup;
} }
error = 0;
if (is_vmalloc_addr(stgbuf)) { if (is_vmalloc_addr(out)) {
dstbuf = kvzalloc(strm.total_out, GFP_KERNEL); *dst = kvzalloc(out_len, GFP_KERNEL);
if (dstbuf) { if (*dst) {
memcpy(dstbuf, stgbuf, strm.total_out); memcpy(*dst, out, out_len);
kvfree(stgbuf); kvfree(out);
out = NULL;
} }
} else } else {
/* /*
* If the staging buffer was kmalloc'd, then using krealloc is * If the staging buffer was kmalloc'd, then using krealloc is
* probably going to be faster. The destination buffer will * probably going to be faster. The destination buffer will
* always be smaller, so it's just shrunk, avoiding a memcpy * 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) { if (!*dst) {
error = -ENOMEM; ret = -ENOMEM;
goto fail_deflate; goto cleanup;
} }
*dst = dstbuf; *dlen = out_len;
*dlen = strm.total_out;
fail_stg_alloc: cleanup:
zlib_deflateEnd(&strm); if (ret) {
fail_deflate_init: kvfree(out);
kvfree(strm.workspace); *dst = NULL;
return error; }
fail_deflate: kvfree(wksp);
kvfree(stgbuf); return ret;
goto fail_stg_alloc;
#else #else
*dlen = slen; *dlen = slen;
return 0; return 0;
...@@ -1142,7 +1134,6 @@ static int deflate_compress(const char *src, size_t slen, char **dst, ...@@ -1142,7 +1134,6 @@ static int deflate_compress(const char *src, size_t slen, char **dst,
static int compress_loaddata(struct aa_loaddata *data) static int compress_loaddata(struct aa_loaddata *data)
{ {
AA_BUG(data->compressed_size > 0); AA_BUG(data->compressed_size > 0);
/* /*
...@@ -1151,7 +1142,7 @@ static int compress_loaddata(struct aa_loaddata *data) ...@@ -1151,7 +1142,7 @@ static int compress_loaddata(struct aa_loaddata *data)
*/ */
if (aa_g_rawdata_compression_level != 0) { if (aa_g_rawdata_compression_level != 0) {
void *udata = data->data; 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); &data->compressed_size);
if (error) if (error)
return error; return error;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment