From 63c16c3a7608558a8e5ced96b8b6b06c490fd513 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 23 Jan 2019 19:17:09 +0000 Subject: 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/include/apparmor.h | 1 + security/apparmor/include/policy_unpack.h | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'security/apparmor/include') 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; }; -- cgit v1.2.3 From df323337e507a0009d3db1ea25948d4c7f320d62 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 3 May 2019 16:12:21 +0200 Subject: 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 | 26 ++++----- security/apparmor/file.c | 24 ++++++--- security/apparmor/include/path.h | 49 +---------------- security/apparmor/lsm.c | 111 +++++++++++++++++++++++++++++---------- security/apparmor/mount.c | 65 ++++++++++++++++++----- 5 files changed, 164 insertions(+), 111 deletions(-) (limited to 'security/apparmor/include') 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; - - for_each_possible_cpu(i) { - for_each_cpu_buffer(j) { - char *buffer; - - 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; + union aa_buffer *aa_buf; + int i, num; + + /* + * 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; + + 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; -- cgit v1.2.3 From 136db994852a9b405ac1074de0e7a1c4c840b8ee Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 31 May 2019 06:54:54 -0700 Subject: 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(-) (limited to 'security/apparmor/include') 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--; } -- cgit v1.2.3 From 341c1fda5e17156619fb71acfc7082b2669b4b72 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 14 Sep 2019 03:34:06 -0700 Subject: 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(-) (limited to 'security/apparmor/include') 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; -- cgit v1.2.3