diff mbox series

[4/5] LSM: Add a LSM module which handles dynamically appendable LSM hooks.

Message ID 39f27c5d-2c41-4f7b-a6e9-740a6af4b364@I-love.SAKURA.ne.jp (mailing list archive)
State Superseded
Headers show
Series LSM: Officially support appending LSM hooks after boot. | expand

Checks

Context Check Description
bpf/vmtest-bpf-PR fail merge-conflict
netdev/tree_selection success Not a local patch
bpf/vmtest-bpf-VM_Test-19 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-20 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-VM_Test-11 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-VM_Test-21 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-9 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-VM_Test-2 success Logs for Validate matrix.py
bpf/vmtest-bpf-VM_Test-15 success Logs for set-matrix
bpf/vmtest-bpf-VM_Test-18 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-6 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-14 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-VM_Test-13 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-VM_Test-5 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-4 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-16 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-VM_Test-12 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-VM_Test-3 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-VM_Test-22 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-17 fail Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-7 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-VM_Test-10 success Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc
bpf/vmtest-bpf-VM_Test-8 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-VM_Test-23 fail Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-24 success Logs for x86_64-llvm-16 / build / build for x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-25 success Logs for x86_64-llvm-16 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-26 success Logs for x86_64-llvm-16 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-27 success Logs for x86_64-llvm-16 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-28 success Logs for x86_64-llvm-16 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-29 success Logs for x86_64-llvm-16 / veristat

Commit Message

Tetsuo Handa Nov. 11, 2023, 10:11 a.m. UTC
TOMOYO security module will use this functionality.

By the way, I was surprised to see /proc/kallsyms containing many hundreds
of symbols due to assigning "number of LSM hooks" * "number of built-in
LSMs" for static call slots. Since the motivation of converting from
linked list to static calls was that indirect function calls are slow,
I expect that overhead of testing whether the list is empty is negligible.

Should this LSM module occupy one set of static call slots (so that
list_for_each_entry() is called only when this LSM module is enabled) ?
If the overhead of testing list_for_each_entry() on an empty list is
negligible, this module does not need to occupy one set of static call
slots? I don't have a native hardware that is suitable for performance
measurement...

Also, since LSM hook assignment is handled by a macro, we could somehow
let the hook assignment macro define one static call slot and call the
next LSM hook (i.e. move static_call() from security/security.c to
individual LSM modules). Then, loop unrolling won't be needed, and
total number of symbols reserved for static calls will be reduced to
"number of LSM hooks" + "sum of all LSM callbacks which are built-into
vmlinux". Side effect of such approach is that kernel stack usage
increases due to nested static calls. But since nest level of static
calls is very small, kernel stack usage won't become a real problem...

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 include/linux/lsm_count.h |   2 +-
 include/linux/lsm_hooks.h |  16 ++++++
 include/uapi/linux/lsm.h  |   1 +
 security/Makefile         |   2 +-
 security/mod_lsm.c        | 100 ++++++++++++++++++++++++++++++++++++++
 security/security.c       |   2 +-
 6 files changed, 120 insertions(+), 3 deletions(-)
 create mode 100644 security/mod_lsm.c
diff mbox series

Patch

diff --git a/include/linux/lsm_count.h b/include/linux/lsm_count.h
index dbb3c8573959..de8db3c77169 100644
--- a/include/linux/lsm_count.h
+++ b/include/linux/lsm_count.h
@@ -19,7 +19,7 @@ 
  * Capabilities is enabled when CONFIG_SECURITY is enabled.
  */
 #if IS_ENABLED(CONFIG_SECURITY)
-#define CAPABILITIES_ENABLED 1,
+#define CAPABILITIES_ENABLED 1, 1,
 #else
 #define CAPABILITIES_ENABLED
 #endif
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 135b3f58f8d2..669ee9406a62 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -215,4 +215,20 @@  extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
 extern int lsm_inode_alloc(struct inode *inode);
 extern struct lsm_static_calls_table static_calls_table __ro_after_init;
 
+/* Definition of all modular callbacks. */
+struct security_hook_mappings {
+#define LSM_HOOK(RET, DEFAULT, NAME, ...)	\
+	struct static_call_key *key_##NAME;	\
+	RET (*NAME)(__VA_ARGS__);
+#include <linux/lsm_hook_defs.h>
+} /* __randomize_layout is useless here, for this is a "const __initdata" struct. */;
+
+/* Type of individual modular callback. */
+struct security_hook_list2 {
+	struct list_head list;
+	union security_list_options hook;
+} __randomize_layout;
+
+extern int mod_lsm_add_hooks(const struct security_hook_mappings *maps);
+
 #endif /* ! __LINUX_LSM_HOOKS_H */
diff --git a/include/uapi/linux/lsm.h b/include/uapi/linux/lsm.h
index f0386880a78e..d458b9a123d1 100644
--- a/include/uapi/linux/lsm.h
+++ b/include/uapi/linux/lsm.h
@@ -61,6 +61,7 @@  struct lsm_ctx {
 #define LSM_ID_LOCKDOWN		108
 #define LSM_ID_BPF		109
 #define LSM_ID_LANDLOCK		110
+#define LSM_ID_MOD_LSM		111
 
 /*
  * LSM_ATTR_XXX definitions identify different LSM attributes
diff --git a/security/Makefile b/security/Makefile
index 59f238490665..250b7ba23502 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -11,7 +11,7 @@  obj-$(CONFIG_SECURITY) 			+= lsm_syscalls.o
 obj-$(CONFIG_MMU)			+= min_addr.o
 
 # Object file lists
-obj-$(CONFIG_SECURITY)			+= security.o
+obj-$(CONFIG_SECURITY)			+= security.o mod_lsm.o
 obj-$(CONFIG_SECURITYFS)		+= inode.o
 obj-$(CONFIG_SECURITY_SELINUX)		+= selinux/
 obj-$(CONFIG_SECURITY_SMACK)		+= smack/
diff --git a/security/mod_lsm.c b/security/mod_lsm.c
new file mode 100644
index 000000000000..f148323b724b
--- /dev/null
+++ b/security/mod_lsm.c
@@ -0,0 +1,100 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <linux/lsm_hooks.h>
+
+/* List of registered modular callbacks. */
+static struct {
+#define LSM_HOOK(RET, DEFAULT, NAME, ...) struct list_head NAME;
+#include <linux/lsm_hook_defs.h>
+} mod_lsm_dynamic_hooks;
+
+/* Get LSM_CALL_ARGS_xxx definitions. */
+#include <linux/lsm_hook_args.h>
+/* A built-in callback for calling modular "int" callbacks. */
+#define LSM_INT_HOOK(RET, DEFAULT, NAME, ...)				\
+	static RET mod_lsm_##NAME(__VA_ARGS__) {			\
+		int RC = DEFAULT;					\
+		struct security_hook_list2 *P;				\
+									\
+		pr_info_once("Called %s\n", __func__);			\
+		list_for_each_entry(P, &mod_lsm_dynamic_hooks.NAME, list) { \
+			RC = P->hook.NAME(LSM_CALL_ARGS_##NAME);	\
+			if (RC != 0)					\
+				break;					\
+		}							\
+		return RC;						\
+	}
+/* A built-in callback for calling modular "void" callbacks. */
+#define LSM_VOID_HOOK(RET, DEFAULT, NAME, ...)				\
+	static RET mod_lsm_##NAME(__VA_ARGS__) {			\
+		struct security_hook_list2 *P;				\
+									\
+		pr_info_once("Called %s\n", __func__);			\
+		list_for_each_entry(P, &mod_lsm_dynamic_hooks.NAME, list) { \
+			P->hook.NAME(LSM_CALL_ARGS_##NAME);		\
+		}							\
+	}
+/* Generate all built-in callbacks here. */
+#include <linux/lsm_hook_defs.h>
+
+/* Initialize all built-in callbacks here. */
+#define LSM_HOOK(RET, DEFAULT, NAME, ...) LSM_HOOK_INIT(NAME, mod_lsm_##NAME),
+static struct security_hook_list mod_lsm_builtin_hooks[] __ro_after_init = {
+#include <linux/lsm_hook_defs.h>
+};
+
+static int mod_lsm_enabled __ro_after_init = 1;
+static struct lsm_blob_sizes mod_lsm_blob_sizes __ro_after_init = { };
+static const struct lsm_id mod_lsm_lsmid = {
+	.name = "mod_lsm",
+	.id = LSM_ID_MOD_LSM,
+};
+
+static int __init mod_lsm_init(void)
+{
+	/* Initialize modular callbacks list. */
+#define LSM_HOOK(RET, DEFAULT, NAME, ...) INIT_LIST_HEAD(&mod_lsm_dynamic_hooks.NAME);
+#include <linux/lsm_hook_defs.h>
+	/* Register built-in callbacks. */
+	security_add_hooks(mod_lsm_builtin_hooks, ARRAY_SIZE(mod_lsm_builtin_hooks), &mod_lsm_lsmid);
+	return 0;
+}
+
+DEFINE_LSM(mod_lsm) = {
+	.name = "mod_lsm",
+	.enabled = &mod_lsm_enabled,
+	.flags = 0,
+	.blobs = &mod_lsm_blob_sizes,
+	.init = mod_lsm_init,
+};
+
+/* The only exported function for registering modular callbacks. */
+int mod_lsm_add_hooks(const struct security_hook_mappings *maps)
+{
+	struct security_hook_list2 *entry;
+	int count = 0;
+
+	if (!mod_lsm_enabled) {
+		pr_info_once("Loadable LSM support is not enabled.\n");
+		return -EOPNOTSUPP;
+	}
+
+	/* Count how meny callbacks are implemented. */
+#define LSM_HOOK(RET, DEFAULT, NAME, ...) do { if (maps->NAME) count++; } while (0);
+#include <linux/lsm_hook_defs.h>
+	if (!count)
+		return -EINVAL;
+	/* Allocate memory for registering implemented callbacks. */
+	entry = kmalloc_array(count, sizeof(struct security_hook_list2), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+	/* Registering imdividual callbacks. */
+	count = 0;
+#define LSM_HOOK(RET, DEFAULT, NAME, ...) do { if (maps->NAME) {	\
+			entry[count].hook.NAME = maps->NAME;		\
+			list_add_tail(&entry[count].list, &mod_lsm_dynamic_hooks.NAME); \
+			count++;					\
+		} } while (0);
+#include <linux/lsm_hook_defs.h>
+	return 0;
+}
+EXPORT_SYMBOL_GPL(mod_lsm_add_hooks);
diff --git a/security/security.c b/security/security.c
index 986aa5e6e29d..a34530fa042a 100644
--- a/security/security.c
+++ b/security/security.c
@@ -42,7 +42,7 @@ 
  * The capability module is accounted for by CONFIG_SECURITY
  */
 #define LSM_CONFIG_COUNT ( \
-	(IS_ENABLED(CONFIG_SECURITY) ? 1 : 0) + \
+	(IS_ENABLED(CONFIG_SECURITY) ? 2 : 0) + \
 	(IS_ENABLED(CONFIG_SECURITY_SELINUX) ? 1 : 0) + \
 	(IS_ENABLED(CONFIG_SECURITY_SMACK) ? 1 : 0) + \
 	(IS_ENABLED(CONFIG_SECURITY_TOMOYO) ? 1 : 0) + \