Skip to content

Commit

Permalink
apparmor: Use a memory pool instead per-CPU caches
Browse files Browse the repository at this point in the history
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 <bigeasy@linutronix.de>
Signed-off-by: John Johansen <john.johansen@canonical.com>
  • Loading branch information
Sebastian Andrzej Siewior authored and John Johansen committed Jun 20, 2019
1 parent bf1d2ee commit df32333
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 111 deletions.
26 changes: 10 additions & 16 deletions security/apparmor/domain.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down
24 changes: 17 additions & 7 deletions security/apparmor/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
49 changes: 2 additions & 47 deletions security/apparmor/include/path.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 / */
Expand All @@ -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 <linux/percpu.h>
#include <linux/preempt.h>

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 */
111 changes: 84 additions & 27 deletions security/apparmor/lsm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
*/
Expand All @@ -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;
}

Expand Down Expand Up @@ -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();
Expand All @@ -1806,7 +1864,6 @@ static int __init apparmor_init(void)

buffers_out:
destroy_buffers();

alloc_out:
aa_destroy_aafs();
aa_teardown_dfa_engine();
Expand Down
Loading

0 comments on commit df32333

Please sign in to comment.