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

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) + \