When a kernel module is loaded, the LSM accepts or rejects the demand according to its policy. Signed-off-by: Simon THOBY <git@xxxxxxxxxxxxx> --- security/loadpol/Makefile | 2 +- security/loadpol/loadpol.c | 22 ++++++++++++ security/loadpol/loadpol.h | 27 ++++++++++++++ security/loadpol/loadpol_policy.c | 59 +++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 security/loadpol/loadpol_policy.c diff --git a/security/loadpol/Makefile b/security/loadpol/Makefile index a794c8cfbfee..062215e1f831 100644 --- a/security/loadpol/Makefile +++ b/security/loadpol/Makefile @@ -1 +1 @@ -obj-$(CONFIG_SECURITY_LOADPOL) := loadpol.o +obj-$(CONFIG_SECURITY_LOADPOL) := loadpol.o loadpol_policy.o diff --git a/security/loadpol/loadpol.c b/security/loadpol/loadpol.c index 3fc29263e2f8..4d1a495a1462 100644 --- a/security/loadpol/loadpol.c +++ b/security/loadpol/loadpol.c @@ -6,6 +6,15 @@ #include "loadpol.h" +// default policy: allow all modules +static struct loadpol_policy_entry default_policy_entries[] __ro_after_init = { + { + .origin = (ORIGIN_KERNEL | ORIGIN_USERSPACE), + .action = ACTION_ALLOW, + .module_name = NULL, + }, +}; + static int __init loadpol_init(void); static const struct lsm_id loadpol_lsmid = { @@ -14,6 +23,7 @@ static const struct lsm_id loadpol_lsmid = { }; static struct security_hook_list loadpol_hooks[] __ro_after_init = { + LSM_HOOK_INIT(kernel_module_load, loadpol_kernel_module_load), }; DEFINE_LSM(LOADPOL_NAME) = { @@ -23,6 +33,18 @@ DEFINE_LSM(LOADPOL_NAME) = { static int __init loadpol_init(void) { + for (int i = 0; i < ARRAY_SIZE(default_policy_entries); i++) { + struct loadpol_policy_entry *entry = kmemdup( + &default_policy_entries[i], + sizeof(struct loadpol_policy_entry), + GFP_KERNEL + ); + if (!entry) + return -ENOMEM; + + list_add_tail(&entry->list, loadpol_policy); + } + security_add_hooks(loadpol_hooks, ARRAY_SIZE(loadpol_hooks), &loadpol_lsmid); pr_info("Loadpol started.\n"); return 0; diff --git a/security/loadpol/loadpol.h b/security/loadpol/loadpol.h index 5e11474191f0..a81d52f6d4da 100644 --- a/security/loadpol/loadpol.h +++ b/security/loadpol/loadpol.h @@ -3,6 +3,33 @@ #ifndef _SECURITY_LOADPOL_LOADPOL_H #define _SECURITY_LOADPOL_LOADPOL_H +#include "linux/list.h" + #define LOADPOL_NAME "loadpol" +enum policy_entry_origin { + ORIGIN_KERNEL = 1 << 0, + ORIGIN_USERSPACE = 1 << 1, +}; + +enum __packed policy_entry_action { + ACTION_UNDEFINED, + ACTION_ALLOW, + ACTION_DENY +}; + +struct loadpol_policy_entry { + struct list_head list; + // bitfield of policy_entry_origin + u8 origin; + enum policy_entry_action action; + // when NULL, the policy apply to every module + char *module_name; +}; + +extern struct list_head __rcu *loadpol_policy; + +// evaluate if a kernel module called 'kmod' is allowed to be loaded in the kernel +int loadpol_kernel_module_load(const char *kmod); + #endif /* _SECURITY_LOADPOL_LOADPOL_H */ diff --git a/security/loadpol/loadpol_policy.c b/security/loadpol/loadpol_policy.c new file mode 100644 index 000000000000..6ba5ab600e3e --- /dev/null +++ b/security/loadpol/loadpol_policy.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "linux/rculist.h" +#include <linux/sched.h> +#include <linux/sysctl.h> +#include <linux/parser.h> + +#include "loadpol.h" + +/* use A/B policy entries: switch from one to the next every time the policy get overwritten */ +static LIST_HEAD(loadpol_policy_a); +static LIST_HEAD(loadpol_policy_b); +struct list_head __rcu *loadpol_policy = (struct list_head __rcu *)(&loadpol_policy_a); + +int loadpol_kernel_module_load(const char *kmod) +{ + struct task_struct *parent_task; + struct loadpol_policy_entry *entry; + struct list_head *policy_list_tmp; + enum policy_entry_origin orig = ORIGIN_USERSPACE; + bool allowed = false; + + rcu_read_lock(); + parent_task = rcu_dereference(current->parent); + /* the parent of the current task is a workqueue -> the request comes from the kernel */ + if (parent_task && (parent_task->flags & PF_WQ_WORKER)) + orig = ORIGIN_KERNEL; + rcu_read_unlock(); + + pr_debug("Loadpol: trying to load '%s' (asked by %s)", + kmod, + orig == ORIGIN_KERNEL ? "kernel" : "userspace"); + + rcu_read_lock(); + policy_list_tmp = rcu_dereference(loadpol_policy); + list_for_each_entry_rcu(entry, policy_list_tmp, list) { + /* the requestor does not match */ + if ((orig & entry->origin) == 0) + continue; + + allowed = entry->action == ACTION_ALLOW; + + if (!entry->module_name) + goto unlock_and_exit; + + if (entry->module_name && match_wildcard(entry->module_name, kmod)) + goto unlock_and_exit; + } + + /* No match -> reject the demand */ + allowed = false; + +unlock_and_exit: + rcu_read_unlock(); + + pr_debug("Loadpol: load of module '%s' %s", kmod, allowed ? "allowed" : "blocked"); + + return allowed ? 0 : -EPERM; +} -- 2.49.0