Skip to content

Commit df32333

Browse files
Sebastian Andrzej Siewiorjrjohansen
authored andcommitted
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 <[email protected]> Signed-off-by: John Johansen <[email protected]>
1 parent bf1d2ee commit df32333

File tree

5 files changed

+164
-111
lines changed

5 files changed

+164
-111
lines changed

security/apparmor/domain.c

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -689,20 +689,9 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
689689
} else if (COMPLAIN_MODE(profile)) {
690690
/* no exec permission - learning mode */
691691
struct aa_profile *new_profile = NULL;
692-
char *n = kstrdup(name, GFP_ATOMIC);
693-
694-
if (n) {
695-
/* name is ptr into buffer */
696-
long pos = name - buffer;
697-
/* break per cpu buffer hold */
698-
put_buffers(buffer);
699-
new_profile = aa_new_null_profile(profile, false, n,
700-
GFP_KERNEL);
701-
get_buffers(buffer);
702-
name = buffer + pos;
703-
strcpy((char *)name, n);
704-
kfree(n);
705-
}
692+
693+
new_profile = aa_new_null_profile(profile, false, name,
694+
GFP_KERNEL);
706695
if (!new_profile) {
707696
error = -ENOMEM;
708697
info = "could not create null profile";
@@ -907,7 +896,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
907896
ctx->nnp = aa_get_label(label);
908897

909898
/* buffer freed below, name is pointer into buffer */
910-
get_buffers(buffer);
899+
buffer = aa_get_buffer();
900+
if (!buffer) {
901+
error = -ENOMEM;
902+
goto done;
903+
}
904+
911905
/* Test for onexec first as onexec override other x transitions. */
912906
if (ctx->onexec)
913907
new = handle_onexec(label, ctx->onexec, ctx->token,
@@ -979,7 +973,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
979973

980974
done:
981975
aa_put_label(label);
982-
put_buffers(buffer);
976+
aa_put_buffer(buffer);
983977

984978
return error;
985979

security/apparmor/file.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,14 @@ int aa_path_perm(const char *op, struct aa_label *label,
336336

337337
flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR :
338338
0);
339-
get_buffers(buffer);
339+
buffer = aa_get_buffer();
340+
if (!buffer)
341+
return -ENOMEM;
340342
error = fn_for_each_confined(label, profile,
341343
profile_path_perm(op, profile, path, buffer, request,
342344
cond, flags, &perms));
343345

344-
put_buffers(buffer);
346+
aa_put_buffer(buffer);
345347

346348
return error;
347349
}
@@ -479,12 +481,18 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
479481
int error;
480482

481483
/* buffer freed below, lname is pointer in buffer */
482-
get_buffers(buffer, buffer2);
484+
buffer = aa_get_buffer();
485+
buffer2 = aa_get_buffer();
486+
error = -ENOMEM;
487+
if (!buffer || !buffer2)
488+
goto out;
489+
483490
error = fn_for_each_confined(label, profile,
484491
profile_path_link(profile, &link, buffer, &target,
485492
buffer2, &cond));
486-
put_buffers(buffer, buffer2);
487-
493+
out:
494+
aa_put_buffer(buffer);
495+
aa_put_buffer(buffer2);
488496
return error;
489497
}
490498

@@ -528,7 +536,9 @@ static int __file_path_perm(const char *op, struct aa_label *label,
528536
return 0;
529537

530538
flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
531-
get_buffers(buffer);
539+
buffer = aa_get_buffer();
540+
if (!buffer)
541+
return -ENOMEM;
532542

533543
/* check every profile in task label not in current cache */
534544
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,
557567
if (!error)
558568
update_file_ctx(file_ctx(file), label, request);
559569

560-
put_buffers(buffer);
570+
aa_put_buffer(buffer);
561571

562572
return error;
563573
}

security/apparmor/include/path.h

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
#ifndef __AA_PATH_H
1616
#define __AA_PATH_H
1717

18-
1918
enum path_flags {
2019
PATH_IS_DIR = 0x1, /* path is a directory */
2120
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
@@ -30,51 +29,7 @@ int aa_path_name(const struct path *path, int flags, char *buffer,
3029
const char **name, const char **info,
3130
const char *disconnected);
3231

33-
#define MAX_PATH_BUFFERS 2
34-
35-
/* Per cpu buffers used during mediation */
36-
/* preallocated buffers to use during path lookups */
37-
struct aa_buffers {
38-
char *buf[MAX_PATH_BUFFERS];
39-
};
40-
41-
#include <linux/percpu.h>
42-
#include <linux/preempt.h>
43-
44-
DECLARE_PER_CPU(struct aa_buffers, aa_buffers);
45-
46-
#define ASSIGN(FN, A, X, N) ((X) = FN(A, N))
47-
#define EVAL1(FN, A, X) ASSIGN(FN, A, X, 0) /*X = FN(0)*/
48-
#define EVAL2(FN, A, X, Y...) \
49-
do { ASSIGN(FN, A, X, 1); EVAL1(FN, A, Y); } while (0)
50-
#define EVAL(FN, A, X...) CONCATENATE(EVAL, COUNT_ARGS(X))(FN, A, X)
51-
52-
#define for_each_cpu_buffer(I) for ((I) = 0; (I) < MAX_PATH_BUFFERS; (I)++)
53-
54-
#ifdef CONFIG_DEBUG_PREEMPT
55-
#define AA_BUG_PREEMPT_ENABLED(X) AA_BUG(preempt_count() <= 0, X)
56-
#else
57-
#define AA_BUG_PREEMPT_ENABLED(X) /* nop */
58-
#endif
59-
60-
#define __get_buffer(C, N) ({ \
61-
AA_BUG_PREEMPT_ENABLED("__get_buffer without preempt disabled"); \
62-
(C)->buf[(N)]; })
63-
64-
#define __get_buffers(C, X...) EVAL(__get_buffer, C, X)
65-
66-
#define __put_buffers(X, Y...) ((void)&(X))
67-
68-
#define get_buffers(X...) \
69-
do { \
70-
struct aa_buffers *__cpu_var = get_cpu_ptr(&aa_buffers); \
71-
__get_buffers(__cpu_var, X); \
72-
} while (0)
73-
74-
#define put_buffers(X, Y...) \
75-
do { \
76-
__put_buffers(X, Y); \
77-
put_cpu_ptr(&aa_buffers); \
78-
} while (0)
32+
char *aa_get_buffer(void);
33+
void aa_put_buffer(char *buf);
7934

8035
#endif /* __AA_PATH_H */

security/apparmor/lsm.c

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@
4848
/* Flag indicating whether initialization completed */
4949
int apparmor_initialized;
5050

51-
DEFINE_PER_CPU(struct aa_buffers, aa_buffers);
51+
union aa_buffer {
52+
struct list_head list;
53+
char buffer[1];
54+
};
5255

56+
static LIST_HEAD(aa_global_buffers);
57+
static DEFINE_SPINLOCK(aa_buffers_lock);
5358

5459
/*
5560
* LSM hook functions
@@ -1422,6 +1427,7 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp)
14221427
return -EPERM;
14231428

14241429
error = param_set_uint(val, kp);
1430+
aa_g_path_max = max_t(uint32_t, aa_g_path_max, sizeof(union aa_buffer));
14251431
pr_info("AppArmor: buffer size set to %d bytes\n", aa_g_path_max);
14261432

14271433
return error;
@@ -1565,6 +1571,48 @@ static int param_set_mode(const char *val, const struct kernel_param *kp)
15651571
return 0;
15661572
}
15671573

1574+
char *aa_get_buffer(void)
1575+
{
1576+
union aa_buffer *aa_buf;
1577+
bool try_again = true;
1578+
1579+
retry:
1580+
spin_lock(&aa_buffers_lock);
1581+
if (!list_empty(&aa_global_buffers)) {
1582+
aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer,
1583+
list);
1584+
list_del(&aa_buf->list);
1585+
spin_unlock(&aa_buffers_lock);
1586+
return &aa_buf->buffer[0];
1587+
}
1588+
spin_unlock(&aa_buffers_lock);
1589+
1590+
aa_buf = kmalloc(aa_g_path_max, GFP_KERNEL | __GFP_RETRY_MAYFAIL |
1591+
__GFP_NOWARN);
1592+
if (!aa_buf) {
1593+
if (try_again) {
1594+
try_again = false;
1595+
goto retry;
1596+
}
1597+
pr_warn_once("AppArmor: Failed to allocate a memory buffer.\n");
1598+
return NULL;
1599+
}
1600+
return &aa_buf->buffer[0];
1601+
}
1602+
1603+
void aa_put_buffer(char *buf)
1604+
{
1605+
union aa_buffer *aa_buf;
1606+
1607+
if (!buf)
1608+
return;
1609+
aa_buf = container_of(buf, union aa_buffer, buffer[0]);
1610+
1611+
spin_lock(&aa_buffers_lock);
1612+
list_add(&aa_buf->list, &aa_global_buffers);
1613+
spin_unlock(&aa_buffers_lock);
1614+
}
1615+
15681616
/*
15691617
* AppArmor init functions
15701618
*/
@@ -1585,38 +1633,48 @@ static int __init set_init_ctx(void)
15851633

15861634
static void destroy_buffers(void)
15871635
{
1588-
u32 i, j;
1636+
union aa_buffer *aa_buf;
15891637

1590-
for_each_possible_cpu(i) {
1591-
for_each_cpu_buffer(j) {
1592-
kfree(per_cpu(aa_buffers, i).buf[j]);
1593-
per_cpu(aa_buffers, i).buf[j] = NULL;
1594-
}
1638+
spin_lock(&aa_buffers_lock);
1639+
while (!list_empty(&aa_global_buffers)) {
1640+
aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer,
1641+
list);
1642+
list_del(&aa_buf->list);
1643+
spin_unlock(&aa_buffers_lock);
1644+
kfree(aa_buf);
1645+
spin_lock(&aa_buffers_lock);
15951646
}
1647+
spin_unlock(&aa_buffers_lock);
15961648
}
15971649

15981650
static int __init alloc_buffers(void)
15991651
{
1600-
u32 i, j;
1601-
1602-
for_each_possible_cpu(i) {
1603-
for_each_cpu_buffer(j) {
1604-
char *buffer;
1605-
1606-
if (cpu_to_node(i) > num_online_nodes())
1607-
/* fallback to kmalloc for offline nodes */
1608-
buffer = kmalloc(aa_g_path_max, GFP_KERNEL);
1609-
else
1610-
buffer = kmalloc_node(aa_g_path_max, GFP_KERNEL,
1611-
cpu_to_node(i));
1612-
if (!buffer) {
1613-
destroy_buffers();
1614-
return -ENOMEM;
1615-
}
1616-
per_cpu(aa_buffers, i).buf[j] = buffer;
1652+
union aa_buffer *aa_buf;
1653+
int i, num;
1654+
1655+
/*
1656+
* A function may require two buffers at once. Usually the buffers are
1657+
* used for a short period of time and are shared. On UP kernel buffers
1658+
* two should be enough, with more CPUs it is possible that more
1659+
* buffers will be used simultaneously. The preallocated pool may grow.
1660+
* This preallocation has also the side-effect that AppArmor will be
1661+
* disabled early at boot if aa_g_path_max is extremly high.
1662+
*/
1663+
if (num_online_cpus() > 1)
1664+
num = 4;
1665+
else
1666+
num = 2;
1667+
1668+
for (i = 0; i < num; i++) {
1669+
1670+
aa_buf = kmalloc(aa_g_path_max, GFP_KERNEL |
1671+
__GFP_RETRY_MAYFAIL | __GFP_NOWARN);
1672+
if (!aa_buf) {
1673+
destroy_buffers();
1674+
return -ENOMEM;
16171675
}
1676+
aa_put_buffer(&aa_buf->buffer[0]);
16181677
}
1619-
16201678
return 0;
16211679
}
16221680

@@ -1781,7 +1839,7 @@ static int __init apparmor_init(void)
17811839
error = alloc_buffers();
17821840
if (error) {
17831841
AA_ERROR("Unable to allocate work buffers\n");
1784-
goto buffers_out;
1842+
goto alloc_out;
17851843
}
17861844

17871845
error = set_init_ctx();
@@ -1806,7 +1864,6 @@ static int __init apparmor_init(void)
18061864

18071865
buffers_out:
18081866
destroy_buffers();
1809-
18101867
alloc_out:
18111868
aa_destroy_aafs();
18121869
aa_teardown_dfa_engine();

0 commit comments

Comments
 (0)