apparmor: Initial implementation of raw policy blob compression
authorChris Coulson <chris.coulson@canonical.com>
Wed, 23 Jan 2019 19:17:09 +0000 (19:17 +0000)
committerJohn Johansen <john.johansen@canonical.com>
Thu, 11 Apr 2019 21:56:29 +0000 (14:56 -0700)
This adds an initial implementation of raw policy blob compression,
using deflate. Compression level can be controlled via a new sysctl,
"apparmor.rawdata_compression_level", which can be set to a value
between 0 (no compression) and 9 (highest compression).

Signed-off-by: Chris Coulson <chris.coulson@canonical.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
security/apparmor/apparmorfs.c
security/apparmor/include/apparmor.h
security/apparmor/include/policy_unpack.h
security/apparmor/lsm.c
security/apparmor/policy_unpack.c

index fefee040bf79132e03864320d6c5e19b83907094..9c0e593e30aa0ea9d976f5cebd62c56042444ef1 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/rcupdate.h>
 #include <linux/fs.h>
 #include <linux/poll.h>
+#include <linux/zlib.h>
 #include <uapi/linux/major.h>
 #include <uapi/linux/magic.h>
 
  * support fns
  */
 
+struct rawdata_f_data {
+       struct aa_loaddata *loaddata;
+};
+
+#define RAWDATA_F_DATA_BUF(p) (char *)(p + 1)
+
+static void rawdata_f_data_free(struct rawdata_f_data *private)
+{
+       if (!private)
+               return;
+
+       aa_put_loaddata(private->loaddata);
+       kvfree(private);
+}
+
+static struct rawdata_f_data *rawdata_f_data_alloc(size_t size)
+{
+       struct rawdata_f_data *ret;
+
+       if (size > SIZE_MAX - sizeof(*ret))
+               return ERR_PTR(-EINVAL);
+
+       ret = kvzalloc(sizeof(*ret) + size, GFP_KERNEL);
+       if (!ret)
+               return ERR_PTR(-ENOMEM);
+
+       return ret;
+}
+
 /**
  * aa_mangle_name - mangle a profile name to std profile layout form
  * @name: profile name to mangle  (NOT NULL)
@@ -1275,36 +1305,117 @@ static int seq_rawdata_hash_show(struct seq_file *seq, void *v)
        return 0;
 }
 
+static int seq_rawdata_compressed_size_show(struct seq_file *seq, void *v)
+{
+       struct aa_loaddata *data = seq->private;
+
+       seq_printf(seq, "%zu\n", data->compressed_size);
+
+       return 0;
+}
+
 SEQ_RAWDATA_FOPS(abi);
 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)
+{
+       int error;
+       struct z_stream_s strm;
+
+       if (aa_g_rawdata_compression_level == 0) {
+               if (dlen < slen)
+                       return -EINVAL;
+               memcpy(dst, src, slen);
+               return 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;
+       }
+
+       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;
+}
 
 static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
                            loff_t *ppos)
 {
-       struct aa_loaddata *rawdata = file->private_data;
+       struct rawdata_f_data *private = file->private_data;
 
-       return simple_read_from_buffer(buf, size, ppos, rawdata->data,
-                                      rawdata->size);
+       return simple_read_from_buffer(buf, size, ppos,
+                                      RAWDATA_F_DATA_BUF(private),
+                                      private->loaddata->size);
 }
 
 static int rawdata_release(struct inode *inode, struct file *file)
 {
-       aa_put_loaddata(file->private_data);
+       rawdata_f_data_free(file->private_data);
 
        return 0;
 }
 
 static int rawdata_open(struct inode *inode, struct file *file)
 {
+       int error;
+       struct aa_loaddata *loaddata;
+       struct rawdata_f_data *private;
+
        if (!policy_view_capable(NULL))
                return -EACCES;
-       file->private_data = __aa_get_loaddata(inode->i_private);
-       if (!file->private_data)
+
+       loaddata = __aa_get_loaddata(inode->i_private);
+       if (!loaddata)
                /* lost race: this entry is being reaped */
                return -ENOENT;
 
+       private = rawdata_f_data_alloc(loaddata->size);
+       if (IS_ERR(private)) {
+               error = PTR_ERR(private);
+               goto fail_private_alloc;
+       }
+
+       private->loaddata = loaddata;
+
+       error = deflate_decompress(loaddata->data, loaddata->compressed_size,
+                                  RAWDATA_F_DATA_BUF(private),
+                                  loaddata->size);
+       if (error)
+               goto fail_decompress;
+
+       file->private_data = private;
        return 0;
+
+fail_decompress:
+       rawdata_f_data_free(private);
+       return error;
+
+fail_private_alloc:
+       aa_put_loaddata(loaddata);
+       return error;
 }
 
 static const struct file_operations rawdata_fops = {
@@ -1383,6 +1494,13 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
                rawdata->dents[AAFS_LOADDATA_HASH] = dent;
        }
 
+       dent = aafs_create_file("compressed_size", S_IFREG | 0444, dir,
+                               rawdata,
+                               &seq_rawdata_compressed_size_fops);
+       if (IS_ERR(dent))
+               goto fail;
+       rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent;
+
        dent = aafs_create_file("raw_data", S_IFREG | 0444,
                                      dir, rawdata, &rawdata_fops);
        if (IS_ERR(dent))
index 73d63b58d875bc774f8bdcb25eeae7989ff2f6f5..fc04e422b8ba2df9cf234badb57c674a5bf59a2e 100644 (file)
@@ -40,6 +40,7 @@ extern enum audit_mode aa_g_audit;
 extern bool aa_g_audit_header;
 extern bool aa_g_debug;
 extern bool aa_g_hash_policy;
+extern int aa_g_rawdata_compression_level;
 extern bool aa_g_lock_policy;
 extern bool aa_g_logsyscall;
 extern bool aa_g_paranoid_load;
index 8db4ab759e80f37837e170744f5788125ef5f657..0739867bb87c5ba85bf57484a527d15e1eafa0a5 100644 (file)
@@ -45,6 +45,7 @@ enum {
        AAFS_LOADDATA_REVISION,
        AAFS_LOADDATA_HASH,
        AAFS_LOADDATA_DATA,
+       AAFS_LOADDATA_COMPRESSED_SIZE,
        AAFS_LOADDATA_DIR,              /* must be last actual entry */
        AAFS_LOADDATA_NDENTS            /* count of entries */
 };
@@ -65,11 +66,16 @@ struct aa_loaddata {
        struct dentry *dents[AAFS_LOADDATA_NDENTS];
        struct aa_ns *ns;
        char *name;
-       size_t size;
+       size_t size;                    /* the original size of the payload */
+       size_t compressed_size;         /* the compressed size of the payload */
        long revision;                  /* the ns policy revision this caused */
        int abi;
        unsigned char *hash;
 
+       /* Pointer to payload. If @compressed_size > 0, then this is the
+        * compressed version of the payload, else it is the uncompressed
+        * version (with the size indicated by @size).
+        */
        char *data;
 };
 
index 87500bde5a92d599ccaa4e892ff12527feac58eb..502846789965a4d44b76cf16d9cbe34f02cf4d05 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/user_namespace.h>
 #include <linux/netfilter_ipv4.h>
 #include <linux/netfilter_ipv6.h>
+#include <linux/zlib.h>
 #include <net/sock.h>
 #include <uapi/linux/mount.h>
 
@@ -1266,6 +1267,16 @@ static const struct kernel_param_ops param_ops_aauint = {
        .get = param_get_aauint
 };
 
+static int param_set_aacompressionlevel(const char *val,
+                                       const struct kernel_param *kp);
+static int param_get_aacompressionlevel(char *buffer,
+                                       const struct kernel_param *kp);
+#define param_check_aacompressionlevel param_check_int
+static const struct kernel_param_ops param_ops_aacompressionlevel = {
+       .set = param_set_aacompressionlevel,
+       .get = param_get_aacompressionlevel
+};
+
 static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp);
 static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp);
 #define param_check_aalockpolicy param_check_bool
@@ -1296,6 +1307,11 @@ bool aa_g_hash_policy = IS_ENABLED(CONFIG_SECURITY_APPARMOR_HASH_DEFAULT);
 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;
+module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level,
+                  aacompressionlevel, 0400);
+
 /* Debug mode */
 bool aa_g_debug = IS_ENABLED(CONFIG_SECURITY_APPARMOR_DEBUG_MESSAGES);
 module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);
@@ -1460,6 +1476,37 @@ static int param_get_aaintbool(char *buffer, const struct kernel_param *kp)
        return param_get_bool(buffer, &kp_local);
 }
 
+static int param_set_aacompressionlevel(const char *val,
+                                       const struct kernel_param *kp)
+{
+       int error;
+
+       if (!apparmor_enabled)
+               return -EINVAL;
+       if (apparmor_initialized)
+               return -EPERM;
+
+       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",
+               aa_g_rawdata_compression_level);
+
+       return error;
+}
+
+static int param_get_aacompressionlevel(char *buffer,
+                                       const struct kernel_param *kp)
+{
+       if (!apparmor_enabled)
+               return -EINVAL;
+       if (apparmor_initialized && !policy_view_capable(NULL))
+               return -EPERM;
+       return param_get_int(buffer, kp);
+}
+
 static int param_get_audit(char *buffer, const struct kernel_param *kp)
 {
        if (!apparmor_enabled)
index f6c2bcb2ab1456be2037ca0c4d2f25175fc2c0b3..4c077aadc38381083bb95124011584009ad97f04 100644 (file)
@@ -20,6 +20,7 @@
 #include <asm/unaligned.h>
 #include <linux/ctype.h>
 #include <linux/errno.h>
+#include <linux/zlib.h>
 
 #include "include/apparmor.h"
 #include "include/audit.h"
@@ -143,9 +144,11 @@ bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r)
 {
        if (l->size != r->size)
                return false;
+       if (l->compressed_size != r->compressed_size)
+               return false;
        if (aa_g_hash_policy && memcmp(l->hash, r->hash, aa_hash_size()) != 0)
                return false;
-       return memcmp(l->data, r->data, r->size) == 0;
+       return memcmp(l->data, r->data, r->compressed_size ?: r->size) == 0;
 }
 
 /*
@@ -1012,6 +1015,105 @@ 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)
+{
+       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;
+       }
+
+       stgbuf = kvzalloc(stglen, GFP_KERNEL);
+       if (!stgbuf) {
+               error = -ENOMEM;
+               goto fail_stg_alloc;
+       }
+
+       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;
+       }
+       error = 0;
+
+       if (is_vmalloc_addr(stgbuf)) {
+               dstbuf = kvzalloc(strm.total_out, GFP_KERNEL);
+               if (dstbuf) {
+                       memcpy(dstbuf, stgbuf, strm.total_out);
+                       vfree(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;
+       }
+
+       *dst = dstbuf;
+       *dlen = strm.total_out;
+
+fail_stg_alloc:
+       zlib_deflateEnd(&strm);
+fail_deflate_init:
+       kvfree(strm.workspace);
+       return error;
+
+fail_deflate:
+       kvfree(stgbuf);
+       goto fail_stg_alloc;
+}
+
+static int compress_loaddata(struct aa_loaddata *data)
+{
+
+       AA_BUG(data->compressed_size > 0);
+
+       /*
+        * Shortcut the no compression case, else we increase the amount of
+        * storage required by a small amount
+        */
+       if (aa_g_rawdata_compression_level != 0) {
+               void *udata = data->data;
+               int error = deflate_compress(udata, data->size, &data->data,
+                                            &data->compressed_size);
+               if (error)
+                       return error;
+
+               kvfree(udata);
+       } else
+               data->compressed_size = data->size;
+
+       return 0;
+}
+
 /**
  * aa_unpack - unpack packed binary profile(s) data loaded from user space
  * @udata: user data copied to kmem  (NOT NULL)
@@ -1080,6 +1182,9 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
                        goto fail;
                }
        }
+       error = compress_loaddata(udata);
+       if (error)
+               goto fail;
        return 0;
 
 fail_profile: