From c740ece958bc636b68c48559c6de2f0271ef51d0 Mon Sep 17 00:00:00 2001 From: Paul Renauld Date: Fri, 14 Aug 2020 16:14:03 +0200 Subject: [PATCH] Security: create loop unrolling macro Define the macro UNROLL_MACRO_LOOP that can be used to call a macro several times, but no more than MAX_UNROLL_MACRO_LOOP. It will produce an error if N is greater than the maximum. This can be later extended to increase the max. For example, `UNROLL_MACRO_LOOP(3, my_macro, x, y)` will expand into ``` my_macro(0, x, y) my_macro(1, x, y) my_macro(2, x, y) ``` Note that no trailing semicolon is placed so `my_macro` should be defined accordingly. Security: replace LSM indirect calls with static calls Define 11 static calls for each LSM hook. They are filled in reverse order when the LSMs are initialized (see cover letter for example). call_int_hook jumps to the first non-empty static call and executes all of them after that point with a fallthrough switch. If no hooks are attached, it jumps directly to the end and the default value is unchanged. --- include/linux/lsm_hooks.h | 1 + include/linux/lsm_static_call.h | 134 ++++++++++++++++++++ security/security.c | 217 ++++++++++++++++++++++++++++---- 3 files changed, 331 insertions(+), 21 deletions(-) create mode 100644 include/linux/lsm_static_call.h diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 95b7c1d3206218..d11e116b588e7c 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1524,6 +1524,7 @@ union security_list_options { #define LSM_HOOK(RET, DEFAULT, NAME, ...) RET (*NAME)(__VA_ARGS__); #include "lsm_hook_defs.h" #undef LSM_HOOK + void *generic_func; }; struct security_hook_heads { diff --git a/include/linux/lsm_static_call.h b/include/linux/lsm_static_call.h new file mode 100644 index 00000000000000..7171ce04afaf7d --- /dev/null +++ b/include/linux/lsm_static_call.h @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (C) 2020 Google LLC. + */ + +#ifndef __LINUX_LSM_STATIC_CALL_H +#define __LINUX_LSM_STATIC_CALL_H + +/* + * Static slots are used in security/security.c to avoid costly + * indirect calls by replacing them with static calls. + * The number of static calls for each LSM hook is fixed. + */ +#define SECURITY_STATIC_SLOT_COUNT 11 + +/* + * Identifier for the LSM static slots. + * HOOK is an LSM hook as defined in linux/lsm_hookdefs.h + * IDX is the index of the slot. 0 <= NUM < SECURITY_STATIC_SLOT_COUNT + */ +#define STATIC_SLOT(HOOK, IDX) security_static_slot_##HOOK##_##IDX + +/* + * Call the macro M for each LSM hook slot. + * M should take as first argument the index and then + * the same __VA_ARGS__ + * Essentially, this will expand to: + * M(0, ...) + * M(1, ...) + * M(2, ...) + * ... + * Note that no trailing semicolon is placed so M should be defined + * accordingly. + * This adapts to a change to SECURITY_STATIC_SLOT_COUNT. + */ +#define SECURITY_FOREACH_STATIC_SLOT(M, ...) \ + UNROLL_MACRO_LOOP(SECURITY_STATIC_SLOT_COUNT, M, __VA_ARGS__) + +/* + * Intermediate macros to expand SECURITY_STATIC_SLOT_COUNT + */ +#define UNROLL_MACRO_LOOP(N, MACRO, ...) \ + _UNROLL_MACRO_LOOP(N, MACRO, __VA_ARGS__) + +#define _UNROLL_MACRO_LOOP(N, MACRO, ...) \ + __UNROLL_MACRO_LOOP(N, MACRO, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP(N, MACRO, ...) \ + __UNROLL_MACRO_LOOP_##N(MACRO, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_0(MACRO, ...) + +#define __UNROLL_MACRO_LOOP_1(MACRO, ...) \ + __UNROLL_MACRO_LOOP_0(MACRO, __VA_ARGS__) \ + MACRO(0, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_2(MACRO, ...) \ + __UNROLL_MACRO_LOOP_1(MACRO, __VA_ARGS__) \ + MACRO(1, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_3(MACRO, ...) \ + __UNROLL_MACRO_LOOP_2(MACRO, __VA_ARGS__) \ + MACRO(2, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_4(MACRO, ...) \ + __UNROLL_MACRO_LOOP_3(MACRO, __VA_ARGS__) \ + MACRO(3, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_5(MACRO, ...) \ + __UNROLL_MACRO_LOOP_4(MACRO, __VA_ARGS__) \ + MACRO(4, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_6(MACRO, ...) \ + __UNROLL_MACRO_LOOP_5(MACRO, __VA_ARGS__) \ + MACRO(5, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_7(MACRO, ...) \ + __UNROLL_MACRO_LOOP_6(MACRO, __VA_ARGS__) \ + MACRO(6, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_8(MACRO, ...) \ + __UNROLL_MACRO_LOOP_7(MACRO, __VA_ARGS__) \ + MACRO(7, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_9(MACRO, ...) \ + __UNROLL_MACRO_LOOP_8(MACRO, __VA_ARGS__) \ + MACRO(8, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_10(MACRO, ...) \ + __UNROLL_MACRO_LOOP_9(MACRO, __VA_ARGS__) \ + MACRO(9, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_11(MACRO, ...) \ + __UNROLL_MACRO_LOOP_10(MACRO, __VA_ARGS__) \ + MACRO(10, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_12(MACRO, ...) \ + __UNROLL_MACRO_LOOP_11(MACRO, __VA_ARGS__) \ + MACRO(11, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_13(MACRO, ...) \ + __UNROLL_MACRO_LOOP_12(MACRO, __VA_ARGS__) \ + MACRO(12, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_14(MACRO, ...) \ + __UNROLL_MACRO_LOOP_13(MACRO, __VA_ARGS__) \ + MACRO(13, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_15(MACRO, ...) \ + __UNROLL_MACRO_LOOP_14(MACRO, __VA_ARGS__) \ + MACRO(14, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_16(MACRO, ...) \ + __UNROLL_MACRO_LOOP_15(MACRO, __VA_ARGS__) \ + MACRO(15, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_17(MACRO, ...) \ + __UNROLL_MACRO_LOOP_16(MACRO, __VA_ARGS__) \ + MACRO(16, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_18(MACRO, ...) \ + __UNROLL_MACRO_LOOP_17(MACRO, __VA_ARGS__) \ + MACRO(17, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_19(MACRO, ...) \ + __UNROLL_MACRO_LOOP_18(MACRO, __VA_ARGS__) \ + MACRO(18, __VA_ARGS__) + +#define __UNROLL_MACRO_LOOP_20(MACRO, ...) \ + __UNROLL_MACRO_LOOP_19(MACRO, __VA_ARGS__) \ + MACRO(19, __VA_ARGS__) + +#endif /* __LINUX_LSM_STATIC_CALL_H */ diff --git a/security/security.c b/security/security.c index 70a7ad357bc6ab..45a3998c09502f 100644 --- a/security/security.c +++ b/security/security.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #define MAX_LSM_EVM_XATTR 2 @@ -86,6 +88,128 @@ static __initconst const char * const builtin_lsm_order = CONFIG_LSM; static __initdata struct lsm_info **ordered_lsms; static __initdata struct lsm_info *exclusive; +/* + * Necessary information about a static + * slot to call __static_call_update + */ +struct static_slot { + /* static call key as defined by STATIC_CALL_KEY */ + struct static_call_key *key; + /* static call trampoline as defined by STATIC_CALL_TRAMP */ + void *trampoline; +}; + +/* + * Table of the static calls for each LSM hook. + * Once the LSMs are initialized, their callbacks will be copied to these + * tables such that the slots are filled backwards (from last to first). + * This way, we can jump directly to the first used slot, and execute + * all of them after. This essentially makes the entry point point + * dynamic to adapt the number of slot to the number of callbacks. + */ +struct static_slot_list { + #define LSM_HOOK(RET, DEFAULT, NAME, ...) \ + struct static_slot NAME[SECURITY_STATIC_SLOT_COUNT]; + #include + #undef LSM_HOOK +} __randomize_layout; + +/* + * Index of the first used static call for each LSM hook + * in the corresponding static_slot_list table. + * All slots with greater indices are used. + * If no slot is used, the default value is INT_MAX. + */ +struct base_slot_idx { + #define LSM_HOOK(RET, DEFAULT, NAME, ...) \ + int NAME; + #include + #undef LSM_HOOK +} __randomize_layout; + +/* + * Create the static slots for each LSM hook, initially empty. + * This will expand to: + * + * [...] + * + * DEFINE_STATIC_CALL_NULL(security_static_slot_file_permission_0, + * *((int(*)(struct file *file, int mask)))NULL); + * DEFINE_STATIC_CALL_NULL(security_static_slot_file_permission_1, ...); + * + * [...] + */ +#define CREATE_STATIC_SLOT(NUM, NAME, RET, ...) \ + DEFINE_STATIC_CALL_NULL(STATIC_SLOT(NAME, NUM), \ + *((RET(*)(__VA_ARGS__))NULL)); + +#define LSM_HOOK(RET, DEFAULT, NAME, ...) \ + SECURITY_FOREACH_STATIC_SLOT(CREATE_STATIC_SLOT, NAME, RET, __VA_ARGS__) +#include +#undef LSM_HOOK +#undef CREATE_STATIC_SLOT + +/* + * Initialise a table of static slots for each LSM hook. + * When defined with DEFINE_STATIC_CALL_NULL as above, a static call is + * a key and a trampoline. Both are needed to use __static_call_update. + * This will expand to: + * struct static_slot_list static_slots = { + * [...] + * .file_permission = { + * (struct static_slot) { + * .key = &STATIC_CALL_KEY( + * security_static_slot_file_permission_0), + * .trampoline = &STATIC_CALL_TRAMP( + * security_static_slot_file_permission_0) + * }, + * (struct static_slot) { + * .key = &STATIC_CALL_KEY( + * security_static_slot_file_permission_1), + * .trampoline = &STATIC_CALL_TRAMP( + * security_static_slot_file_permission_1) + * }, + * [...] + * }, + * .file_alloc_security = { + * [...] + * }, + * [...] + * } + */ +static struct static_slot_list static_slots __initdata = { +#define DEFINE_SLOT(NUM, NAME) \ + (struct static_slot) { \ + .key = &STATIC_CALL_KEY(STATIC_SLOT(NAME, NUM)), \ + .trampoline = &STATIC_CALL_TRAMP(STATIC_SLOT(NAME, NUM))\ + }, +#define LSM_HOOK(RET, DEFAULT, NAME, ...) \ + .NAME = { \ + SECURITY_FOREACH_STATIC_SLOT(DEFINE_SLOT, NAME) \ + }, +#include +#undef LSM_HOOK +#undef DEFINE_SLOT +}; + +/* + * The base slot index for each is initially INT_MAX, which means + * that no slot is used yet. + * When expanded, this results in: + * struct base_slot_idx base_slot_idx = { + * [...] + * .file_permission = INT_MAX, + * .file_alloc_security = INT_MAX, + * [...] + * } + */ +static struct base_slot_idx base_slot_idx __lsm_ro_after_init = { +#define LSM_HOOK(RET, DEFAULT, NAME, ...) \ + .NAME = INT_MAX, +#include +#undef LSM_HOOK +}; + static __initdata bool debug; #define init_debug(...) \ do { \ @@ -307,6 +431,46 @@ static void __init ordered_lsm_parse(const char *order, const char *origin) kfree(sep); } +static void __init lsm_init_hook_static_slot(struct static_slot *slots, + struct hlist_head *head, + int *first_slot_idx) +{ + struct security_hook_list *pos; + struct static_slot *slot; + int slot_cnt; + + slot_cnt = 0; + hlist_for_each_entry_rcu(pos, head, list) + slot_cnt++; + + if (slot_cnt > SECURITY_STATIC_SLOT_COUNT) + panic("%s - No static hook slot remaining to add LSM hook.\n", + __func__); + + if (slot_cnt == 0) { + *first_slot_idx = INT_MAX; + return; + } + + *first_slot_idx = SECURITY_STATIC_SLOT_COUNT - slot_cnt; + slot = slots + *first_slot_idx; + hlist_for_each_entry_rcu(pos, head, list) { + __static_call_update(slot->key, slot->trampoline, + pos->hook.generic_func); + slot++; + } +} + +static void __init lsm_init_static_slots(void) +{ +#define LSM_HOOK(RET, DEFAULT, NAME, ...) \ + lsm_init_hook_static_slot(static_slots.NAME, \ + &security_hook_heads.NAME, \ + &base_slot_idx.NAME); +#include +#undef LSM_HOOK +} + static void __init lsm_early_cred(struct cred *cred); static void __init lsm_early_task(struct task_struct *task); @@ -354,6 +518,7 @@ static void __init ordered_lsm_init(void) lsm_early_task(current); for (lsm = ordered_lsms; *lsm; lsm++) initialize_lsm(*lsm); + lsm_init_static_slots(); kfree(ordered_lsms); } @@ -374,6 +539,7 @@ int __init early_security_init(void) prepare_lsm(lsm); initialize_lsm(lsm); } + lsm_init_static_slots(); return 0; } @@ -696,27 +862,36 @@ static void __init lsm_early_task(struct task_struct *task) * call_int_hook: * This is a hook that returns a value. */ - -#define call_void_hook(FUNC, ...) \ - do { \ - struct security_hook_list *P; \ - \ - hlist_for_each_entry(P, &security_hook_heads.FUNC, list) \ - P->hook.FUNC(__VA_ARGS__); \ - } while (0) - -#define call_int_hook(FUNC, IRC, ...) ({ \ - int RC = IRC; \ - do { \ - struct security_hook_list *P; \ - \ - hlist_for_each_entry(P, &security_hook_heads.FUNC, list) { \ - RC = P->hook.FUNC(__VA_ARGS__); \ - if (RC != 0) \ - break; \ - } \ - } while (0); \ - RC; \ +#define __CASE_CALL_STATIC_VOID(NUM, HOOK, ...) \ + case NUM: \ + static_call(STATIC_SLOT(HOOK, NUM))(__VA_ARGS__); \ + fallthrough; + +#define call_void_hook(FUNC, ...) do { \ + switch (base_slot_idx.FUNC) { \ + SECURITY_FOREACH_STATIC_SLOT(__CASE_CALL_STATIC_VOID, \ + FUNC, __VA_ARGS__) \ + default : \ + break; \ + } \ +} while (0) + +#define __CASE_CALL_STATIC_INT(NUM, R, HOOK, ...) \ + case NUM: \ + R = static_call(STATIC_SLOT(HOOK, NUM))(__VA_ARGS__); \ + if (R != 0) \ + break; \ + fallthrough; + +#define call_int_hook(FUNC, IRC, ...) ({ \ + int RC = IRC; \ + switch (base_slot_idx.FUNC) { \ + SECURITY_FOREACH_STATIC_SLOT(__CASE_CALL_STATIC_INT, \ + RC, FUNC, __VA_ARGS__) \ + default : \ + break; \ + } \ + RC; \ }) /* Security operations */