On 5/21/2025 7:01 AM, Simon THOBY wrote: > 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 The // comment style is not used in the kernel. > + 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; > +}