apparmor: use zstd compression for profile data
authorJon Tourville <jon.tourville@canonical.com>
Mon, 11 Jul 2022 16:36:08 +0000 (11:36 -0500)
committerJohn Johansen <john.johansen@canonical.com>
Mon, 3 Oct 2022 21:49:02 +0000 (14:49 -0700)
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>
security/apparmor/Kconfig
security/apparmor/apparmorfs.c
security/apparmor/lsm.c
security/apparmor/policy_unpack.c

index cb3496e..acac3bb 100644 (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.
index 7160e7a..d98bbf2 100644 (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;
-
-               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;
+       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;
                }
 
-               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;
 
index e29cade..ec873ff 100644 (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;
index 55d31ba..10e462d 100644 (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);
-
-       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(&params.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;
+       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 @@ 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;