From 63c16c3a7608558a8e5ced96b8b6b06c490fd513 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 23 Jan 2019 19:17:09 +0000 Subject: [PATCH 01/13] apparmor: Initial implementation of raw policy blob compression 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 Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 130 +++++++++++++++++++++- security/apparmor/include/apparmor.h | 1 + security/apparmor/include/policy_unpack.h | 8 +- security/apparmor/lsm.c | 47 ++++++++ security/apparmor/policy_unpack.c | 107 +++++++++++++++++- 5 files changed, 285 insertions(+), 8 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index fefee040bf79..9c0e593e30aa 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,35 @@ * 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)) diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 73d63b58d875..fc04e422b8ba 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -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; diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index 8db4ab759e80..0739867bb87c 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -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; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 87500bde5a92..502846789965 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -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) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index f6c2bcb2ab14..4c077aadc383 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -20,6 +20,7 @@ #include #include #include +#include #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: From 6a59d9243d349ae598e8ea74c36aa8e31705071a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 8 Feb 2019 17:14:35 -0800 Subject: [PATCH 02/13] apparmor: fix blob compression build failure on ppc security/apparmor/policy_unpack.c: In function 'deflate_compress': security/apparmor/policy_unpack.c:1064:4: error: implicit declaration of function 'vfree'; did you mean 'kfree'? [-Werror=implicit-function-declaration] vfree(stgbuf); ^~~~~ kfree Fixes: 876dd866c084 ("apparmor: Initial implementation of raw policy blob compression") Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 4c077aadc383..c421801409e3 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1062,7 +1062,7 @@ static int deflate_compress(const char *src, size_t slen, char **dst, dstbuf = kvzalloc(strm.total_out, GFP_KERNEL); if (dstbuf) { memcpy(dstbuf, stgbuf, strm.total_out); - vfree(stgbuf); + kvfree(stgbuf); } } else /* From fe166a9f2868839a1e2f7bd950164d05e86eb154 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 11 Feb 2019 21:56:46 -0800 Subject: [PATCH 03/13] apparmor: fix missing ZLIB defines On configs where ZLIB is not already selected we are getting undefined reference to `zlib_deflateInit2' undefined reference to `zlib_deflate' undefined reference to `zlib_deflateEnd' For now just select the necessary ZLIB configs. Fixes: 876dd866c084 ("apparmor: Initial implementation of raw policy blob compression") Signed-off-by: John Johansen --- security/apparmor/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 3de21f46c82a..99c35e22c119 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -5,6 +5,8 @@ config SECURITY_APPARMOR select SECURITY_PATH select SECURITYFS select SECURITY_NETWORK + select ZLIB_INFLATE + select ZLIB_DEFLATE default n help This enables the AppArmor security module. From 145a0ef21c8e944957f58e2c8ffcd8a10f46266a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 9 Mar 2019 16:58:10 -0800 Subject: [PATCH 04/13] apparmor: fix blob compression when ns is forced on a policy load When blob compression is turned on, if the policy namespace is forced onto a policy load, the policy load will fail as the namespace name being referenced is inside the compressed policy blob, resulting in invalid or names that are too long. So duplicate the name before the blob is compressed. Fixes: 876dd866c084 ("apparmor: Initial implementation of raw policy blob compression") Signed-off-by: John Johansen --- security/apparmor/policy.c | 3 ++- security/apparmor/policy_unpack.c | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index df9c5890a878..71a3e6291478 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -861,7 +861,7 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, u32 mask, struct aa_loaddata *udata) { - const char *ns_name, *info = NULL; + const char *ns_name = NULL, *info = NULL; struct aa_ns *ns = NULL; struct aa_load_ent *ent, *tmp; struct aa_loaddata *rawdata_ent; @@ -1048,6 +1048,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, out: aa_put_ns(ns); aa_put_loaddata(udata); + kfree(ns_name); if (error) return error; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index c421801409e3..20f07f629598 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -944,11 +944,14 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) e, error); return error; } - if (*ns && strcmp(*ns, name)) + if (*ns && strcmp(*ns, name)) { audit_iface(NULL, NULL, NULL, "invalid ns change", e, error); - else if (!*ns) - *ns = name; + } else if (!*ns) { + *ns = kstrdup(name, GFP_KERNEL); + if (!*ns) + return -ENOMEM; + } } return 0; From 058c4f342582362c75dd5e162dc4ff73a392ffad Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 16 Apr 2019 15:42:18 +0100 Subject: [PATCH 05/13] apparmor: fix spelling mistake "immutible" -> "immutable" There is a spelling mistake in an information message string, fix it. Signed-off-by: Colin Ian King Signed-off-by: John Johansen --- security/apparmor/policy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 71a3e6291478..04f2480e8374 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -587,7 +587,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, { if (profile) { if (profile->label.flags & FLAG_IMMUTIBLE) { - *info = "cannot replace immutible profile"; + *info = "cannot replace immutable profile"; return -EPERM; } else if (noreplace) { *info = "profile already exists"; From bf1d2ee7bc6215dd92427625a4c707227457a5db Mon Sep 17 00:00:00 2001 From: Bharath Vedartham Date: Tue, 23 Apr 2019 22:23:00 +0530 Subject: [PATCH 06/13] apparmor: Force type-casting of current->real_cred This patch fixes the sparse warning: warning: cast removes address space '' of expression. Signed-off-by: Bharath Vedartham Signed-off-by: John Johansen --- security/apparmor/lsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 502846789965..8f7ffc51ad86 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1576,7 +1576,7 @@ static int param_set_mode(const char *val, const struct kernel_param *kp) */ static int __init set_init_ctx(void) { - struct cred *cred = (struct cred *)current->real_cred; + struct cred *cred = (__force struct cred *)current->real_cred; set_cred_label(cred, aa_get_label(ns_unconfined(root_ns))); From df323337e507a0009d3db1ea25948d4c7f320d62 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 3 May 2019 16:12:21 +0200 Subject: [PATCH 07/13] apparmor: Use a memory pool instead per-CPU caches The get_buffers() macro may provide one or two buffers to the caller. Those buffers are pre-allocated on init for each CPU. By default it allocates 2* 2 * MAX_PATH * POSSIBLE_CPU which equals 64KiB on a system with 4 CPUs or 1MiB with 64 CPUs and so on. Replace the per-CPU buffers with a common memory pool which is shared across all CPUs. The pool grows on demand and never shrinks. The pool starts with two (UP) or four (SMP) elements. By using this pool it is possible to request a buffer and keeping preemption enabled which avoids the hack in profile_transition(). It has been pointed out by Tetsuo Handa that GFP_KERNEL allocations for small amount of memory do not fail. In order not to have an endless retry, __GFP_RETRY_MAYFAIL is passed (so the memory allocation is not repeated until success) and retried once hoping that in the meantime a buffer has been returned to the pool. Since now NULL is possible all allocation paths check the buffer pointer and return -ENOMEM on failure. Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: John Johansen --- security/apparmor/domain.c | 24 +++---- security/apparmor/file.c | 24 +++++-- security/apparmor/include/path.h | 49 +------------- security/apparmor/lsm.c | 107 +++++++++++++++++++++++-------- security/apparmor/mount.c | 65 +++++++++++++++---- 5 files changed, 161 insertions(+), 108 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index ca2dccf5b445..cae0e619ff4f 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -689,20 +689,9 @@ static struct aa_label *profile_transition(struct aa_profile *profile, } else if (COMPLAIN_MODE(profile)) { /* no exec permission - learning mode */ struct aa_profile *new_profile = NULL; - char *n = kstrdup(name, GFP_ATOMIC); - if (n) { - /* name is ptr into buffer */ - long pos = name - buffer; - /* break per cpu buffer hold */ - put_buffers(buffer); - new_profile = aa_new_null_profile(profile, false, n, - GFP_KERNEL); - get_buffers(buffer); - name = buffer + pos; - strcpy((char *)name, n); - kfree(n); - } + new_profile = aa_new_null_profile(profile, false, name, + GFP_KERNEL); if (!new_profile) { error = -ENOMEM; info = "could not create null profile"; @@ -907,7 +896,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ctx->nnp = aa_get_label(label); /* buffer freed below, name is pointer into buffer */ - get_buffers(buffer); + buffer = aa_get_buffer(); + if (!buffer) { + error = -ENOMEM; + goto done; + } + /* Test for onexec first as onexec override other x transitions. */ if (ctx->onexec) new = handle_onexec(label, ctx->onexec, ctx->token, @@ -979,7 +973,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) done: aa_put_label(label); - put_buffers(buffer); + aa_put_buffer(buffer); return error; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d0afed9ebd0e..7b424e73a8c7 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -336,12 +336,14 @@ int aa_path_perm(const char *op, struct aa_label *label, flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); - get_buffers(buffer); + buffer = aa_get_buffer(); + if (!buffer) + return -ENOMEM; error = fn_for_each_confined(label, profile, profile_path_perm(op, profile, path, buffer, request, cond, flags, &perms)); - put_buffers(buffer); + aa_put_buffer(buffer); return error; } @@ -479,12 +481,18 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry, int error; /* buffer freed below, lname is pointer in buffer */ - get_buffers(buffer, buffer2); + buffer = aa_get_buffer(); + buffer2 = aa_get_buffer(); + error = -ENOMEM; + if (!buffer || !buffer2) + goto out; + error = fn_for_each_confined(label, profile, profile_path_link(profile, &link, buffer, &target, buffer2, &cond)); - put_buffers(buffer, buffer2); - +out: + aa_put_buffer(buffer); + aa_put_buffer(buffer2); return error; } @@ -528,7 +536,9 @@ static int __file_path_perm(const char *op, struct aa_label *label, return 0; flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); - get_buffers(buffer); + buffer = aa_get_buffer(); + if (!buffer) + return -ENOMEM; /* check every profile in task label not in current cache */ error = fn_for_each_not_in_set(flabel, label, profile, @@ -557,7 +567,7 @@ static int __file_path_perm(const char *op, struct aa_label *label, if (!error) update_file_ctx(file_ctx(file), label, request); - put_buffers(buffer); + aa_put_buffer(buffer); return error; } diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index b6380c5f0097..b0b2ab85e42d 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -15,7 +15,6 @@ #ifndef __AA_PATH_H #define __AA_PATH_H - enum path_flags { PATH_IS_DIR = 0x1, /* path is a directory */ PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */ @@ -30,51 +29,7 @@ int aa_path_name(const struct path *path, int flags, char *buffer, const char **name, const char **info, const char *disconnected); -#define MAX_PATH_BUFFERS 2 - -/* Per cpu buffers used during mediation */ -/* preallocated buffers to use during path lookups */ -struct aa_buffers { - char *buf[MAX_PATH_BUFFERS]; -}; - -#include -#include - -DECLARE_PER_CPU(struct aa_buffers, aa_buffers); - -#define ASSIGN(FN, A, X, N) ((X) = FN(A, N)) -#define EVAL1(FN, A, X) ASSIGN(FN, A, X, 0) /*X = FN(0)*/ -#define EVAL2(FN, A, X, Y...) \ - do { ASSIGN(FN, A, X, 1); EVAL1(FN, A, Y); } while (0) -#define EVAL(FN, A, X...) CONCATENATE(EVAL, COUNT_ARGS(X))(FN, A, X) - -#define for_each_cpu_buffer(I) for ((I) = 0; (I) < MAX_PATH_BUFFERS; (I)++) - -#ifdef CONFIG_DEBUG_PREEMPT -#define AA_BUG_PREEMPT_ENABLED(X) AA_BUG(preempt_count() <= 0, X) -#else -#define AA_BUG_PREEMPT_ENABLED(X) /* nop */ -#endif - -#define __get_buffer(C, N) ({ \ - AA_BUG_PREEMPT_ENABLED("__get_buffer without preempt disabled"); \ - (C)->buf[(N)]; }) - -#define __get_buffers(C, X...) EVAL(__get_buffer, C, X) - -#define __put_buffers(X, Y...) ((void)&(X)) - -#define get_buffers(X...) \ -do { \ - struct aa_buffers *__cpu_var = get_cpu_ptr(&aa_buffers); \ - __get_buffers(__cpu_var, X); \ -} while (0) - -#define put_buffers(X, Y...) \ -do { \ - __put_buffers(X, Y); \ - put_cpu_ptr(&aa_buffers); \ -} while (0) +char *aa_get_buffer(void); +void aa_put_buffer(char *buf); #endif /* __AA_PATH_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8f7ffc51ad86..3e0cfdebee45 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -48,8 +48,13 @@ /* Flag indicating whether initialization completed */ int apparmor_initialized; -DEFINE_PER_CPU(struct aa_buffers, aa_buffers); +union aa_buffer { + struct list_head list; + char buffer[1]; +}; +static LIST_HEAD(aa_global_buffers); +static DEFINE_SPINLOCK(aa_buffers_lock); /* * LSM hook functions @@ -1422,6 +1427,7 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp) return -EPERM; error = param_set_uint(val, kp); + aa_g_path_max = max_t(uint32_t, aa_g_path_max, sizeof(union aa_buffer)); pr_info("AppArmor: buffer size set to %d bytes\n", aa_g_path_max); return error; @@ -1565,6 +1571,48 @@ static int param_set_mode(const char *val, const struct kernel_param *kp) return 0; } +char *aa_get_buffer(void) +{ + union aa_buffer *aa_buf; + bool try_again = true; + +retry: + spin_lock(&aa_buffers_lock); + if (!list_empty(&aa_global_buffers)) { + aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer, + list); + list_del(&aa_buf->list); + spin_unlock(&aa_buffers_lock); + return &aa_buf->buffer[0]; + } + spin_unlock(&aa_buffers_lock); + + aa_buf = kmalloc(aa_g_path_max, GFP_KERNEL | __GFP_RETRY_MAYFAIL | + __GFP_NOWARN); + if (!aa_buf) { + if (try_again) { + try_again = false; + goto retry; + } + pr_warn_once("AppArmor: Failed to allocate a memory buffer.\n"); + return NULL; + } + return &aa_buf->buffer[0]; +} + +void aa_put_buffer(char *buf) +{ + union aa_buffer *aa_buf; + + if (!buf) + return; + aa_buf = container_of(buf, union aa_buffer, buffer[0]); + + spin_lock(&aa_buffers_lock); + list_add(&aa_buf->list, &aa_global_buffers); + spin_unlock(&aa_buffers_lock); +} + /* * AppArmor init functions */ @@ -1585,38 +1633,48 @@ static int __init set_init_ctx(void) static void destroy_buffers(void) { - u32 i, j; + union aa_buffer *aa_buf; - for_each_possible_cpu(i) { - for_each_cpu_buffer(j) { - kfree(per_cpu(aa_buffers, i).buf[j]); - per_cpu(aa_buffers, i).buf[j] = NULL; - } + spin_lock(&aa_buffers_lock); + while (!list_empty(&aa_global_buffers)) { + aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer, + list); + list_del(&aa_buf->list); + spin_unlock(&aa_buffers_lock); + kfree(aa_buf); + spin_lock(&aa_buffers_lock); } + spin_unlock(&aa_buffers_lock); } static int __init alloc_buffers(void) { - u32 i, j; + union aa_buffer *aa_buf; + int i, num; - for_each_possible_cpu(i) { - for_each_cpu_buffer(j) { - char *buffer; + /* + * A function may require two buffers at once. Usually the buffers are + * used for a short period of time and are shared. On UP kernel buffers + * two should be enough, with more CPUs it is possible that more + * buffers will be used simultaneously. The preallocated pool may grow. + * This preallocation has also the side-effect that AppArmor will be + * disabled early at boot if aa_g_path_max is extremly high. + */ + if (num_online_cpus() > 1) + num = 4; + else + num = 2; - if (cpu_to_node(i) > num_online_nodes()) - /* fallback to kmalloc for offline nodes */ - buffer = kmalloc(aa_g_path_max, GFP_KERNEL); - else - buffer = kmalloc_node(aa_g_path_max, GFP_KERNEL, - cpu_to_node(i)); - if (!buffer) { - destroy_buffers(); - return -ENOMEM; - } - per_cpu(aa_buffers, i).buf[j] = buffer; + for (i = 0; i < num; i++) { + + aa_buf = kmalloc(aa_g_path_max, GFP_KERNEL | + __GFP_RETRY_MAYFAIL | __GFP_NOWARN); + if (!aa_buf) { + destroy_buffers(); + return -ENOMEM; } + aa_put_buffer(&aa_buf->buffer[0]); } - return 0; } @@ -1781,7 +1839,7 @@ static int __init apparmor_init(void) error = alloc_buffers(); if (error) { AA_ERROR("Unable to allocate work buffers\n"); - goto buffers_out; + goto alloc_out; } error = set_init_ctx(); @@ -1806,7 +1864,6 @@ static int __init apparmor_init(void) buffers_out: destroy_buffers(); - alloc_out: aa_destroy_aafs(); aa_teardown_dfa_engine(); diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 8c3787399356..267a26fba14e 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -412,11 +412,13 @@ int aa_remount(struct aa_label *label, const struct path *path, binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; - get_buffers(buffer); + buffer = aa_get_buffer(); + if (!buffer) + return -ENOMEM; error = fn_for_each_confined(label, profile, match_mnt(profile, path, buffer, NULL, NULL, NULL, flags, data, binary)); - put_buffers(buffer); + aa_put_buffer(buffer); return error; } @@ -441,11 +443,18 @@ int aa_bind_mount(struct aa_label *label, const struct path *path, if (error) return error; - get_buffers(buffer, old_buffer); + buffer = aa_get_buffer(); + old_buffer = aa_get_buffer(); + error = -ENOMEM; + if (!buffer || old_buffer) + goto out; + error = fn_for_each_confined(label, profile, match_mnt(profile, path, buffer, &old_path, old_buffer, NULL, flags, NULL, false)); - put_buffers(buffer, old_buffer); +out: + aa_put_buffer(buffer); + aa_put_buffer(old_buffer); path_put(&old_path); return error; @@ -465,11 +474,13 @@ int aa_mount_change_type(struct aa_label *label, const struct path *path, flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE); - get_buffers(buffer); + buffer = aa_get_buffer(); + if (!buffer) + return -ENOMEM; error = fn_for_each_confined(label, profile, match_mnt(profile, path, buffer, NULL, NULL, NULL, flags, NULL, false)); - put_buffers(buffer); + aa_put_buffer(buffer); return error; } @@ -492,11 +503,17 @@ int aa_move_mount(struct aa_label *label, const struct path *path, if (error) return error; - get_buffers(buffer, old_buffer); + buffer = aa_get_buffer(); + old_buffer = aa_get_buffer(); + error = -ENOMEM; + if (!buffer || !old_buffer) + goto out; error = fn_for_each_confined(label, profile, match_mnt(profile, path, buffer, &old_path, old_buffer, NULL, MS_MOVE, NULL, false)); - put_buffers(buffer, old_buffer); +out: + aa_put_buffer(buffer); + aa_put_buffer(old_buffer); path_put(&old_path); return error; @@ -537,17 +554,29 @@ int aa_new_mount(struct aa_label *label, const char *dev_name, } } - get_buffers(buffer, dev_buffer); + buffer = aa_get_buffer(); + if (!buffer) { + error = -ENOMEM; + goto out; + } if (dev_path) { error = fn_for_each_confined(label, profile, match_mnt(profile, path, buffer, dev_path, dev_buffer, type, flags, data, binary)); } else { + dev_buffer = aa_get_buffer(); + if (!dev_buffer) { + error = -ENOMEM; + goto out; + } error = fn_for_each_confined(label, profile, match_mnt_path_str(profile, path, buffer, dev_name, type, flags, data, binary, NULL)); } - put_buffers(buffer, dev_buffer); + +out: + aa_put_buffer(buffer); + aa_put_buffer(dev_buffer); if (dev_path) path_put(dev_path); @@ -595,10 +624,13 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) AA_BUG(!label); AA_BUG(!mnt); - get_buffers(buffer); + buffer = aa_get_buffer(); + if (!buffer) + return -ENOMEM; + error = fn_for_each_confined(label, profile, profile_umount(profile, &path, buffer)); - put_buffers(buffer); + aa_put_buffer(buffer); return error; } @@ -671,7 +703,11 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path, AA_BUG(!old_path); AA_BUG(!new_path); - get_buffers(old_buffer, new_buffer); + old_buffer = aa_get_buffer(); + new_buffer = aa_get_buffer(); + error = -ENOMEM; + if (!old_buffer || !new_buffer) + goto out; target = fn_label_build(label, profile, GFP_ATOMIC, build_pivotroot(profile, new_path, new_buffer, old_path, old_buffer)); @@ -690,7 +726,8 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path, /* already audited error */ error = PTR_ERR(target); out: - put_buffers(old_buffer, new_buffer); + aa_put_buffer(old_buffer); + aa_put_buffer(new_buffer); return error; From 8ac2ca328ec9356f56d0dad3aa350d9600db951a Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 5 Apr 2019 15:34:58 +0200 Subject: [PATCH 08/13] apparmor: Switch to GFP_KERNEL where possible After removing preempt_disable() from get_buffers() it is possible to replace a few GFP_ATOMIC allocations with GFP_KERNEL. Replace GFP_ATOMIC allocations with GFP_KERNEL where the context looks to bee preepmtible. Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: John Johansen --- security/apparmor/domain.c | 20 ++++++++++---------- security/apparmor/file.c | 2 +- security/apparmor/mount.c | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index cae0e619ff4f..0da0b2b96972 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -524,7 +524,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, label = &new_profile->label; continue; } - label = aa_label_parse(&profile->label, *name, GFP_ATOMIC, + label = aa_label_parse(&profile->label, *name, GFP_KERNEL, true, false); if (IS_ERR(label)) label = NULL; @@ -604,7 +604,7 @@ static struct aa_label *x_to_label(struct aa_profile *profile, /* base the stack on post domain transition */ struct aa_label *base = new; - new = aa_label_parse(base, stack, GFP_ATOMIC, true, false); + new = aa_label_parse(base, stack, GFP_KERNEL, true, false); if (IS_ERR(new)) new = NULL; aa_put_label(base); @@ -712,7 +712,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile, if (DEBUG_ON) { dbg_printk("apparmor: scrubbing environment variables" " for %s profile=", name); - aa_label_printk(new, GFP_ATOMIC); + aa_label_printk(new, GFP_KERNEL); dbg_printk("\n"); } *secure_exec = true; @@ -788,7 +788,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, if (DEBUG_ON) { dbg_printk("apparmor: scrubbing environment " "variables for %s label=", xname); - aa_label_printk(onexec, GFP_ATOMIC); + aa_label_printk(onexec, GFP_KERNEL); dbg_printk("\n"); } *secure_exec = true; @@ -822,7 +822,7 @@ static struct aa_label *handle_onexec(struct aa_label *label, bprm, buffer, cond, unsafe)); if (error) return ERR_PTR(error); - new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, aa_get_newest_label(onexec), profile_transition(profile, bprm, buffer, cond, unsafe)); @@ -834,9 +834,9 @@ static struct aa_label *handle_onexec(struct aa_label *label, buffer, cond, unsafe)); if (error) return ERR_PTR(error); - new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, aa_label_merge(&profile->label, onexec, - GFP_ATOMIC), + GFP_KERNEL), profile_transition(profile, bprm, buffer, cond, unsafe)); } @@ -907,7 +907,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) new = handle_onexec(label, ctx->onexec, ctx->token, bprm, buffer, &cond, &unsafe); else - new = fn_label_build(label, profile, GFP_ATOMIC, + new = fn_label_build(label, profile, GFP_KERNEL, profile_transition(profile, bprm, buffer, &cond, &unsafe)); @@ -951,7 +951,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) if (DEBUG_ON) { dbg_printk("scrubbing environment variables for %s " "label=", bprm->filename); - aa_label_printk(new, GFP_ATOMIC); + aa_label_printk(new, GFP_KERNEL); dbg_printk("\n"); } bprm->secureexec = 1; @@ -962,7 +962,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) if (DEBUG_ON) { dbg_printk("apparmor: clearing unsafe personality " "bits. %s label=", bprm->filename); - aa_label_printk(new, GFP_ATOMIC); + aa_label_printk(new, GFP_KERNEL); dbg_printk("\n"); } bprm->per_clear |= PER_CLEAR_ON_SETID; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 7b424e73a8c7..ab56e1994b01 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -80,7 +80,7 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) if (aad(sa)->peer) { audit_log_format(ab, " target="); aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, - FLAG_VIEW_SUBNS, GFP_ATOMIC); + FLAG_VIEW_SUBNS, GFP_KERNEL); } else if (aad(sa)->fs.target) { audit_log_format(ab, " target="); audit_log_untrustedstring(ab, aad(sa)->fs.target); diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 267a26fba14e..79379d9275d8 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -708,7 +708,7 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path, error = -ENOMEM; if (!old_buffer || !new_buffer) goto out; - target = fn_label_build(label, profile, GFP_ATOMIC, + target = fn_label_build(label, profile, GFP_KERNEL, build_pivotroot(profile, new_path, new_buffer, old_path, old_buffer)); if (!target) { From 136db994852a9b405ac1074de0e7a1c4c840b8ee Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 31 May 2019 06:54:54 -0700 Subject: [PATCH 09/13] apparmor: increase left match history buffer size There have been cases reported where a history buffer size of 8 was not enough to resolve conflict overlaps. Increase the buffer to and get rid of the size element which is currently just storing the constant WB_HISTORY_SIZE. Signed-off-by: John Johansen --- security/apparmor/include/match.h | 3 +-- security/apparmor/match.c | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 958d2b52a7b7..90fc050a6c2d 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -138,7 +138,7 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start, void aa_dfa_free_kref(struct kref *kref); -#define WB_HISTORY_SIZE 8 +#define WB_HISTORY_SIZE 24 struct match_workbuf { unsigned int count; unsigned int pos; @@ -151,7 +151,6 @@ struct match_workbuf N = { \ .count = 0, \ .pos = 0, \ .len = 0, \ - .size = WB_HISTORY_SIZE, \ } unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start, diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 55f2ee505a01..21fad8f48bc3 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -620,8 +620,8 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start, #define inc_wb_pos(wb) \ do { \ - wb->pos = (wb->pos + 1) & (wb->size - 1); \ - wb->len = (wb->len + 1) & (wb->size - 1); \ + wb->pos = (wb->pos + 1) & (WB_HISTORY_SIZE - 1); \ + wb->len = (wb->len + 1) & (WB_HISTORY_SIZE - 1); \ } while (0) /* For DFAs that don't support extended tagging of states */ @@ -640,7 +640,7 @@ static bool is_loop(struct match_workbuf *wb, unsigned int state, return true; } if (pos == 0) - pos = wb->size; + pos = WB_HISTORY_SIZE; pos--; } From 00e0590dbaec6f1bcaa36a85467d7e3497ced522 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 27 Jun 2019 14:09:04 +0100 Subject: [PATCH 10/13] apparmor: fix unsigned len comparison with less than zero The sanity check in macro update_for_len checks to see if len is less than zero, however, len is a size_t so it can never be less than zero, so this sanity check is a no-op. Fix this by making len a ssize_t so the comparison will work and add ulen that is a size_t copy of len so that the min() macro won't throw warnings about comparing different types. Addresses-Coverity: ("Macro compares unsigned to 0") Fixes: f1bd904175e8 ("apparmor: add the base fns() for domain labels") Signed-off-by: Colin Ian King Signed-off-by: John Johansen --- security/apparmor/label.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index ba11bdf9043a..2469549842d2 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1462,11 +1462,13 @@ static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label, /* helper macro for snprint routines */ #define update_for_len(total, len, size, str) \ do { \ + size_t ulen = len; \ + \ AA_BUG(len < 0); \ - total += len; \ - len = min(len, size); \ - size -= len; \ - str += len; \ + total += ulen; \ + ulen = min(ulen, size); \ + size -= ulen; \ + str += ulen; \ } while (0) /** @@ -1601,7 +1603,7 @@ int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, struct aa_ns *prev_ns = NULL; struct label_it i; int count = 0, total = 0; - size_t len; + ssize_t len; AA_BUG(!str && size != 0); AA_BUG(!label); From 8f21a62475258ba07b032f5006fb26fd6501f314 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 12 Sep 2019 02:01:35 -0700 Subject: [PATCH 11/13] apparmor: fix wrong buffer allocation in aa_new_mount Fix the following trace caused by the dev_path buffer not being allocated. [ 641.044262] AppArmor WARN match_mnt: ((devpath && !devbuffer)): [ 641.044284] WARNING: CPU: 1 PID: 30709 at ../security/apparmor/mount.c:385 match_mnt+0x133/0x180 [ 641.044286] Modules linked in: snd_hda_codec_generic ledtrig_audio snd_hda_intel snd_hda_codec snd_hda_core qxl ttm snd_hwdep snd_pcm drm_kms_helper snd_seq_midi snd_seq_midi_event drm snd_rawmidi crct10dif_pclmul crc32_pclmul ghash_clmulni_intel iptable_mangle aesni_intel aes_x86_64 xt_tcpudp crypto_simd snd_seq cryptd bridge stp llc iptable_filter glue_helper snd_seq_device snd_timer joydev input_leds snd serio_raw fb_sys_fops 9pnet_virtio 9pnet syscopyarea sysfillrect soundcore sysimgblt qemu_fw_cfg mac_hid sch_fq_codel parport_pc ppdev lp parport ip_tables x_tables autofs4 8139too psmouse 8139cp i2c_piix4 pata_acpi mii floppy [ 641.044318] CPU: 1 PID: 30709 Comm: mount Tainted: G D W 5.1.0-rc4+ #223 [ 641.044320] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014 [ 641.044323] RIP: 0010:match_mnt+0x133/0x180 [ 641.044325] Code: 41 5d 41 5e 41 5f c3 48 8b 4c 24 18 eb b1 48 c7 c6 08 84 26 83 48 c7 c7 f0 56 54 83 4c 89 54 24 08 48 89 14 24 e8 7d d3 bb ff <0f> 0b 4c 8b 54 24 08 48 8b 14 24 e9 25 ff ff ff 48 c7 c6 08 84 26 [ 641.044327] RSP: 0018:ffffa9b34ac97d08 EFLAGS: 00010282 [ 641.044329] RAX: 0000000000000000 RBX: ffff9a86725a8558 RCX: 0000000000000000 [ 641.044331] RDX: 0000000000000002 RSI: 0000000000000001 RDI: 0000000000000246 [ 641.044333] RBP: ffffa9b34ac97db0 R08: 0000000000000000 R09: 0000000000000000 [ 641.044334] R10: 0000000000000000 R11: 00000000000077f5 R12: 0000000000000000 [ 641.044336] R13: ffffa9b34ac97e98 R14: ffff9a865e000008 R15: ffff9a86c4cf42b8 [ 641.044338] FS: 00007fab73969740(0000) GS:ffff9a86fbb00000(0000) knlGS:0000000000000000 [ 641.044340] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 641.044342] CR2: 000055f90bc62035 CR3: 00000000aab5f006 CR4: 00000000003606e0 [ 641.044346] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 641.044348] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [ 641.044349] Call Trace: [ 641.044355] aa_new_mount+0x119/0x2c0 [ 641.044363] apparmor_sb_mount+0xd4/0x430 [ 641.044367] security_sb_mount+0x46/0x70 [ 641.044372] do_mount+0xbb/0xeb0 [ 641.044377] ? memdup_user+0x4b/0x70 [ 641.044380] ksys_mount+0x7e/0xd0 [ 641.044384] __x64_sys_mount+0x21/0x30 [ 641.044388] do_syscall_64+0x5a/0x1a0 [ 641.044392] entry_SYSCALL_64_after_hwframe+0x49/0xbe [ 641.044394] RIP: 0033:0x7fab73a8790a [ 641.044397] Code: 48 8b 0d 89 85 0c 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 49 89 ca b8 a5 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 56 85 0c 00 f7 d8 64 89 01 48 [ 641.044399] RSP: 002b:00007ffe0ffe4238 EFLAGS: 00000206 ORIG_RAX: 00000000000000a5 [ 641.044401] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007fab73a8790a [ 641.044429] RDX: 000055f90bc6203b RSI: 00007ffe0ffe57b1 RDI: 00007ffe0ffe57a5 [ 641.044431] RBP: 00007ffe0ffe4250 R08: 0000000000000000 R09: 00007fab73b51d80 [ 641.044433] R10: 00000000c0ed0004 R11: 0000000000000206 R12: 000055f90bc610b0 [ 641.044434] R13: 00007ffe0ffe4330 R14: 0000000000000000 R15: 0000000000000000 [ 641.044457] irq event stamp: 0 [ 641.044460] hardirqs last enabled at (0): [<0000000000000000>] (null) [ 641.044463] hardirqs last disabled at (0): [] copy_process.part.30+0x734/0x23f0 [ 641.044467] softirqs last enabled at (0): [] copy_process.part.30+0x734/0x23f0 [ 641.044469] softirqs last disabled at (0): [<0000000000000000>] (null) [ 641.044470] ---[ end trace c0d54bdacf6af6b2 ]--- Fixes: df323337e507 ("apparmor: Use a memory pool instead per-CPU caches") Signed-off-by: John Johansen --- security/apparmor/mount.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 79379d9275d8..2dbccb021663 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -560,15 +560,15 @@ int aa_new_mount(struct aa_label *label, const char *dev_name, goto out; } if (dev_path) { - error = fn_for_each_confined(label, profile, - match_mnt(profile, path, buffer, dev_path, dev_buffer, - type, flags, data, binary)); - } else { dev_buffer = aa_get_buffer(); if (!dev_buffer) { error = -ENOMEM; goto out; } + error = fn_for_each_confined(label, profile, + match_mnt(profile, path, buffer, dev_path, dev_buffer, + type, flags, data, binary)); + } else { error = fn_for_each_confined(label, profile, match_mnt_path_str(profile, path, buffer, dev_name, type, flags, data, binary, NULL)); From bce4e7e9c45ef97ac1e30b9cb4adc25b5b5a7cfa Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 13 Sep 2019 22:24:23 -0700 Subject: [PATCH 12/13] apparmor: reduce rcu_read_lock scope for aa_file_perm mediation Now that the buffers allocation has changed and no longer needs the full mediation under an rcu_read_lock, reduce the rcu_read_lock scope to only where it is necessary. Fixes: df323337e507 ("apparmor: Use a memory pool instead per-CPU caches") Signed-off-by: John Johansen --- security/apparmor/file.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index ab56e1994b01..37d62ecec29d 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -621,7 +621,8 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file, fctx = file_ctx(file); rcu_read_lock(); - flabel = rcu_dereference(fctx->label); + flabel = aa_get_newest_label(rcu_dereference(fctx->label)); + rcu_read_unlock(); AA_BUG(!flabel); /* revalidate access, if task is unconfined, or the cached cred @@ -646,8 +647,7 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file, error = __file_sock_perm(op, label, flabel, file, request, denied); done: - rcu_read_unlock(); - + aa_put_label(flabel); return error; } From 341c1fda5e17156619fb71acfc7082b2669b4b72 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 14 Sep 2019 03:34:06 -0700 Subject: [PATCH 13/13] apparmor: make it so work buffers can be allocated from atomic context In some situations AppArmor needs to be able to use its work buffers from atomic context. Add the ability to specify when in atomic context and hold a set of work buffers in reserve for atomic context to reduce the chance that a large work buffer allocation will need to be done. Fixes: df323337e507 ("apparmor: Use a memory pool instead per-CPU caches") Signed-off-by: John Johansen --- security/apparmor/domain.c | 2 +- security/apparmor/file.c | 21 ++++++++------ security/apparmor/include/file.h | 2 +- security/apparmor/include/path.h | 3 +- security/apparmor/lsm.c | 50 ++++++++++++++++++++++---------- security/apparmor/mount.c | 22 +++++++------- 6 files changed, 62 insertions(+), 38 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 0da0b2b96972..51b3143ec256 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -896,7 +896,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ctx->nnp = aa_get_label(label); /* buffer freed below, name is pointer into buffer */ - buffer = aa_get_buffer(); + buffer = aa_get_buffer(false); if (!buffer) { error = -ENOMEM; goto done; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 37d62ecec29d..b520fdfc3504 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -336,7 +336,7 @@ int aa_path_perm(const char *op, struct aa_label *label, flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); - buffer = aa_get_buffer(); + buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; error = fn_for_each_confined(label, profile, @@ -481,8 +481,8 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry, int error; /* buffer freed below, lname is pointer in buffer */ - buffer = aa_get_buffer(); - buffer2 = aa_get_buffer(); + buffer = aa_get_buffer(false); + buffer2 = aa_get_buffer(false); error = -ENOMEM; if (!buffer || !buffer2) goto out; @@ -519,7 +519,7 @@ static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label, static int __file_path_perm(const char *op, struct aa_label *label, struct aa_label *flabel, struct file *file, - u32 request, u32 denied) + u32 request, u32 denied, bool in_atomic) { struct aa_profile *profile; struct aa_perms perms = {}; @@ -536,7 +536,7 @@ static int __file_path_perm(const char *op, struct aa_label *label, return 0; flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); - buffer = aa_get_buffer(); + buffer = aa_get_buffer(in_atomic); if (!buffer) return -ENOMEM; @@ -604,11 +604,12 @@ static int __file_sock_perm(const char *op, struct aa_label *label, * @label: label being enforced (NOT NULL) * @file: file to revalidate access permissions on (NOT NULL) * @request: requested permissions + * @in_atomic: whether allocations need to be done in atomic context * * Returns: %0 if access allowed else error */ int aa_file_perm(const char *op, struct aa_label *label, struct file *file, - u32 request) + u32 request, bool in_atomic) { struct aa_file_ctx *fctx; struct aa_label *flabel; @@ -641,7 +642,7 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file, if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) error = __file_path_perm(op, label, flabel, file, request, - denied); + denied, in_atomic); else if (S_ISSOCK(file_inode(file)->i_mode)) error = __file_sock_perm(op, label, flabel, file, request, @@ -669,7 +670,8 @@ static void revalidate_tty(struct aa_label *label) struct tty_file_private, list); file = file_priv->file; - if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE)) + if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE, + IN_ATOMIC)) drop_tty = 1; } spin_unlock(&tty->files_lock); @@ -683,7 +685,8 @@ static int match_file(const void *p, struct file *file, unsigned int fd) { struct aa_label *label = (struct aa_label *)p; - if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file))) + if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file), + IN_ATOMIC)) return fd + 1; return 0; } diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 8be09208cf7c..67fadf06fa73 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -201,7 +201,7 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry); int aa_file_perm(const char *op, struct aa_label *label, struct file *file, - u32 request); + u32 request, bool in_atomic); void aa_inherit_files(const struct cred *cred, struct files_struct *files); diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index b0b2ab85e42d..d2ab8a932bad 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -29,7 +29,8 @@ int aa_path_name(const struct path *path, int flags, char *buffer, const char **name, const char **info, const char *disconnected); -char *aa_get_buffer(void); +#define IN_ATOMIC true +char *aa_get_buffer(bool in_atomic); void aa_put_buffer(char *buf); #endif /* __AA_PATH_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 3e0cfdebee45..c940e8988444 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -53,6 +53,10 @@ union aa_buffer { char buffer[1]; }; +#define RESERVE_COUNT 2 +static int reserve_count = RESERVE_COUNT; +static int buffer_count; + static LIST_HEAD(aa_global_buffers); static DEFINE_SPINLOCK(aa_buffers_lock); @@ -452,7 +456,8 @@ static void apparmor_file_free_security(struct file *file) aa_put_label(rcu_access_pointer(ctx->label)); } -static int common_file_perm(const char *op, struct file *file, u32 mask) +static int common_file_perm(const char *op, struct file *file, u32 mask, + bool in_atomic) { struct aa_label *label; int error = 0; @@ -462,7 +467,7 @@ static int common_file_perm(const char *op, struct file *file, u32 mask) return -EACCES; label = __begin_current_label_crit_section(); - error = aa_file_perm(op, label, file, mask); + error = aa_file_perm(op, label, file, mask, in_atomic); __end_current_label_crit_section(label); return error; @@ -470,12 +475,13 @@ static int common_file_perm(const char *op, struct file *file, u32 mask) static int apparmor_file_receive(struct file *file) { - return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file)); + return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file), + false); } static int apparmor_file_permission(struct file *file, int mask) { - return common_file_perm(OP_FPERM, file, mask); + return common_file_perm(OP_FPERM, file, mask, false); } static int apparmor_file_lock(struct file *file, unsigned int cmd) @@ -485,11 +491,11 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd) if (cmd == F_WRLCK) mask |= MAY_WRITE; - return common_file_perm(OP_FLOCK, file, mask); + return common_file_perm(OP_FLOCK, file, mask, false); } static int common_mmap(const char *op, struct file *file, unsigned long prot, - unsigned long flags) + unsigned long flags, bool in_atomic) { int mask = 0; @@ -507,20 +513,21 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot, if (prot & PROT_EXEC) mask |= AA_EXEC_MMAP; - return common_file_perm(op, file, mask); + return common_file_perm(op, file, mask, in_atomic); } static int apparmor_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { - return common_mmap(OP_FMMAP, file, prot, flags); + return common_mmap(OP_FMMAP, file, prot, flags, GFP_ATOMIC); } static int apparmor_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot) { return common_mmap(OP_FMPROT, vma->vm_file, prot, - !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0, + false); } static int apparmor_sb_mount(const char *dev_name, const struct path *path, @@ -1571,24 +1578,36 @@ static int param_set_mode(const char *val, const struct kernel_param *kp) return 0; } -char *aa_get_buffer(void) +char *aa_get_buffer(bool in_atomic) { union aa_buffer *aa_buf; bool try_again = true; + gfp_t flags = (GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN); retry: spin_lock(&aa_buffers_lock); - if (!list_empty(&aa_global_buffers)) { + if (buffer_count > reserve_count || + (in_atomic && !list_empty(&aa_global_buffers))) { aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer, list); list_del(&aa_buf->list); + buffer_count--; spin_unlock(&aa_buffers_lock); return &aa_buf->buffer[0]; } + if (in_atomic) { + /* + * out of reserve buffers and in atomic context so increase + * how many buffers to keep in reserve + */ + reserve_count++; + flags = GFP_ATOMIC; + } spin_unlock(&aa_buffers_lock); - aa_buf = kmalloc(aa_g_path_max, GFP_KERNEL | __GFP_RETRY_MAYFAIL | - __GFP_NOWARN); + if (!in_atomic) + might_sleep(); + aa_buf = kmalloc(aa_g_path_max, flags); if (!aa_buf) { if (try_again) { try_again = false; @@ -1610,6 +1629,7 @@ void aa_put_buffer(char *buf) spin_lock(&aa_buffers_lock); list_add(&aa_buf->list, &aa_global_buffers); + buffer_count++; spin_unlock(&aa_buffers_lock); } @@ -1661,9 +1681,9 @@ static int __init alloc_buffers(void) * disabled early at boot if aa_g_path_max is extremly high. */ if (num_online_cpus() > 1) - num = 4; + num = 4 + RESERVE_COUNT; else - num = 2; + num = 2 + RESERVE_COUNT; for (i = 0; i < num; i++) { diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 2dbccb021663..b06f5cba8fc4 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -412,7 +412,7 @@ int aa_remount(struct aa_label *label, const struct path *path, binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; - buffer = aa_get_buffer(); + buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; error = fn_for_each_confined(label, profile, @@ -443,8 +443,8 @@ int aa_bind_mount(struct aa_label *label, const struct path *path, if (error) return error; - buffer = aa_get_buffer(); - old_buffer = aa_get_buffer(); + buffer = aa_get_buffer(false); + old_buffer = aa_get_buffer(false); error = -ENOMEM; if (!buffer || old_buffer) goto out; @@ -474,7 +474,7 @@ int aa_mount_change_type(struct aa_label *label, const struct path *path, flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE); - buffer = aa_get_buffer(); + buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; error = fn_for_each_confined(label, profile, @@ -503,8 +503,8 @@ int aa_move_mount(struct aa_label *label, const struct path *path, if (error) return error; - buffer = aa_get_buffer(); - old_buffer = aa_get_buffer(); + buffer = aa_get_buffer(false); + old_buffer = aa_get_buffer(false); error = -ENOMEM; if (!buffer || !old_buffer) goto out; @@ -554,13 +554,13 @@ int aa_new_mount(struct aa_label *label, const char *dev_name, } } - buffer = aa_get_buffer(); + buffer = aa_get_buffer(false); if (!buffer) { error = -ENOMEM; goto out; } if (dev_path) { - dev_buffer = aa_get_buffer(); + dev_buffer = aa_get_buffer(false); if (!dev_buffer) { error = -ENOMEM; goto out; @@ -624,7 +624,7 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) AA_BUG(!label); AA_BUG(!mnt); - buffer = aa_get_buffer(); + buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; @@ -703,8 +703,8 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path, AA_BUG(!old_path); AA_BUG(!new_path); - old_buffer = aa_get_buffer(); - new_buffer = aa_get_buffer(); + old_buffer = aa_get_buffer(false); + new_buffer = aa_get_buffer(false); error = -ENOMEM; if (!old_buffer || !new_buffer) goto out;