Enable users to manage SELinux policies through the new hook
lsm_config_system_policy. This feature is restricted to CAP_MAC_ADMIN.
Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
---
security/selinux/hooks.c | 27 +++++++++++++++++++++++++++
security/selinux/include/security.h | 7 +++++++
security/selinux/selinuxfs.c | 16 ++++++++++++----
3 files changed, 46 insertions(+), 4 deletions(-)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index e7a7dcab81db..3d14d4e47937 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -7196,6 +7196,31 @@ static int selinux_uring_allowed(void)
}
#endif /* CONFIG_IO_URING */
+/**
+ * selinux_lsm_config_system_policy - Manage a LSM policy
+ * @op: operation to perform. Currently, only LSM_POLICY_LOAD is supported
+ * @buf: User-supplied buffer
+ * @size: size of @buf
+ * @flags: reserved for future use; must be zero
+ *
+ * Returns: number of written rules on success, negative value on error
+ */
+static int selinux_lsm_config_system_policy(u32 op, void __user *buf,
+ size_t size, u32 flags)
+{
+ loff_t pos = 0;
+
+ if (op != LSM_POLICY_LOAD || flags)
+ return -EOPNOTSUPP;
+
+ if (!selinux_null.dentry || !selinux_null.dentry->d_sb ||
+ !selinux_null.dentry->d_sb->s_fs_info)
+ return -ENODEV;
+
+ return __sel_write_load(selinux_null.dentry->d_sb->s_fs_info, buf, size,
+ &pos);
+}
+
static const struct lsm_id selinux_lsmid = {
.name = "selinux",
.id = LSM_ID_SELINUX,
@@ -7499,6 +7524,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
#ifdef CONFIG_PERF_EVENTS
LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc),
#endif
+ LSM_HOOK_INIT(lsm_config_system_policy, selinux_lsm_config_system_policy),
+
};
static __init int selinux_init(void)
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index e7827ed7be5f..7b779ea43cc3 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -389,7 +389,14 @@ struct selinux_kernel_status {
extern void selinux_status_update_setenforce(bool enforcing);
extern void selinux_status_update_policyload(u32 seqno);
extern void selinux_complete_init(void);
+
+struct selinux_fs_info;
+
extern struct path selinux_null;
+extern ssize_t __sel_write_load(struct selinux_fs_info *fsi,
+ const char __user *buf, size_t count,
+ loff_t *ppos);
+
extern void selnl_notify_setenforce(int val);
extern void selnl_notify_policyload(u32 seqno);
extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 47480eb2189b..1f7e611d8300 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -567,11 +567,11 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
return ret;
}
-static ssize_t sel_write_load(struct file *file, const char __user *buf,
- size_t count, loff_t *ppos)
+ssize_t __sel_write_load(struct selinux_fs_info *fsi,
+ const char __user *buf, size_t count,
+ loff_t *ppos)
{
- struct selinux_fs_info *fsi;
struct selinux_load_state load_state;
ssize_t length;
void *data = NULL;
@@ -605,7 +605,6 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
pr_warn_ratelimited("SELinux: failed to load policy\n");
goto out;
}
- fsi = file_inode(file)->i_sb->s_fs_info;
length = sel_make_policy_nodes(fsi, load_state.policy);
if (length) {
pr_warn_ratelimited("SELinux: failed to initialize selinuxfs\n");
@@ -626,6 +625,15 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
return length;
}
+static ssize_t sel_write_load(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
+
+ return __sel_write_load(fsi, buf, count, ppos);
+}
+
+
static const struct file_operations sel_load_ops = {
.write = sel_write_load,
.llseek = generic_file_llseek,
--
2.48.1
On Fri, Oct 10, 2025 at 9:27 AM Maxime Bélair
<maxime.belair@canonical.com> wrote:
>
> Enable users to manage SELinux policies through the new hook
> lsm_config_system_policy. This feature is restricted to CAP_MAC_ADMIN.
(added selinux mailing list and Fedora/Red Hat SELinux kernel maintainer to cc)
A couple of observations:
1. We do not currently require CAP_MAC_ADMIN for loading SELinux
policy, since it was only added later for Smack and SELinux implements
its own permission checks. When loading policy via selinuxfs, one
requires uid-0 or CAP_DAC_OVERRIDE to write to /sys/fs/selinux/load
plus the corresponding SELinux permissions, but this is just an
artifact of the filesystem-based interface. I'm not opposed to using
CAP_MAC_ADMIN for loading policy via the new system call but wanted to
note it as a difference.
2. The SELinux namespaces support [1], [2] is based on instantiating a
separate selinuxfs instance for each namespace; you load a policy for
a namespace by mounting a new selinuxfs instance after unsharing your
SELinux namespace and then write to its /sys/fs/selinux/load
interface, only affecting policy for the new namespace. Your interface
doesn't appear to support such an approach and IIUC will currently
always load the init SELinux namespace's policy rather than the
current process' SELinux namespace.
[1] https://github.com/stephensmalley/selinuxns
[2] https://lore.kernel.org/selinux/20250814132637.1659-1-stephen.smalley.work@gmail.com/
>
> Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
> ---
> security/selinux/hooks.c | 27 +++++++++++++++++++++++++++
> security/selinux/include/security.h | 7 +++++++
> security/selinux/selinuxfs.c | 16 ++++++++++++----
> 3 files changed, 46 insertions(+), 4 deletions(-)
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index e7a7dcab81db..3d14d4e47937 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -7196,6 +7196,31 @@ static int selinux_uring_allowed(void)
> }
> #endif /* CONFIG_IO_URING */
>
> +/**
> + * selinux_lsm_config_system_policy - Manage a LSM policy
> + * @op: operation to perform. Currently, only LSM_POLICY_LOAD is supported
> + * @buf: User-supplied buffer
> + * @size: size of @buf
> + * @flags: reserved for future use; must be zero
> + *
> + * Returns: number of written rules on success, negative value on error
> + */
> +static int selinux_lsm_config_system_policy(u32 op, void __user *buf,
> + size_t size, u32 flags)
> +{
> + loff_t pos = 0;
> +
> + if (op != LSM_POLICY_LOAD || flags)
> + return -EOPNOTSUPP;
> +
> + if (!selinux_null.dentry || !selinux_null.dentry->d_sb ||
> + !selinux_null.dentry->d_sb->s_fs_info)
> + return -ENODEV;
> +
> + return __sel_write_load(selinux_null.dentry->d_sb->s_fs_info, buf, size,
> + &pos);
> +}
> +
> static const struct lsm_id selinux_lsmid = {
> .name = "selinux",
> .id = LSM_ID_SELINUX,
> @@ -7499,6 +7524,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
> #ifdef CONFIG_PERF_EVENTS
> LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc),
> #endif
> + LSM_HOOK_INIT(lsm_config_system_policy, selinux_lsm_config_system_policy),
> +
> };
>
> static __init int selinux_init(void)
> diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
> index e7827ed7be5f..7b779ea43cc3 100644
> --- a/security/selinux/include/security.h
> +++ b/security/selinux/include/security.h
> @@ -389,7 +389,14 @@ struct selinux_kernel_status {
> extern void selinux_status_update_setenforce(bool enforcing);
> extern void selinux_status_update_policyload(u32 seqno);
> extern void selinux_complete_init(void);
> +
> +struct selinux_fs_info;
> +
> extern struct path selinux_null;
> +extern ssize_t __sel_write_load(struct selinux_fs_info *fsi,
> + const char __user *buf, size_t count,
> + loff_t *ppos);
> +
> extern void selnl_notify_setenforce(int val);
> extern void selnl_notify_policyload(u32 seqno);
> extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);
> diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> index 47480eb2189b..1f7e611d8300 100644
> --- a/security/selinux/selinuxfs.c
> +++ b/security/selinux/selinuxfs.c
> @@ -567,11 +567,11 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
> return ret;
> }
>
> -static ssize_t sel_write_load(struct file *file, const char __user *buf,
> - size_t count, loff_t *ppos)
> +ssize_t __sel_write_load(struct selinux_fs_info *fsi,
> + const char __user *buf, size_t count,
> + loff_t *ppos)
>
> {
> - struct selinux_fs_info *fsi;
> struct selinux_load_state load_state;
> ssize_t length;
> void *data = NULL;
> @@ -605,7 +605,6 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
> pr_warn_ratelimited("SELinux: failed to load policy\n");
> goto out;
> }
> - fsi = file_inode(file)->i_sb->s_fs_info;
> length = sel_make_policy_nodes(fsi, load_state.policy);
> if (length) {
> pr_warn_ratelimited("SELinux: failed to initialize selinuxfs\n");
> @@ -626,6 +625,15 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
> return length;
> }
>
> +static ssize_t sel_write_load(struct file *file, const char __user *buf,
> + size_t count, loff_t *ppos)
> +{
> + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
> +
> + return __sel_write_load(fsi, buf, count, ppos);
> +}
> +
> +
> static const struct file_operations sel_load_ops = {
> .write = sel_write_load,
> .llseek = generic_file_llseek,
> --
> 2.48.1
>
On Fri, Oct 10, 2025 at 9:59 AM Stephen Smalley <stephen.smalley.work@gmail.com> wrote: > > 2. The SELinux namespaces support [1], [2] is based on instantiating a > separate selinuxfs instance for each namespace; you load a policy for > a namespace by mounting a new selinuxfs instance after unsharing your > SELinux namespace and then write to its /sys/fs/selinux/load > interface, only affecting policy for the new namespace. Your interface > doesn't appear to support such an approach and IIUC will currently > always load the init SELinux namespace's policy rather than the > current process' SELinux namespace. I'm distracted on other things at the moment, but my current thinking is that while policy loading and namespace management APIs are largely separate, there is some minor overlap when it comes to loading policy as others have mentioned. For that reason, I think we need to resolve the namespace API first, keeping in mind the potential for a policy load API, and then implement the policy loading API, if desired. -- paul-moore.com
On Fri, Oct 10, 2025 at 9:58 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Fri, Oct 10, 2025 at 9:27 AM Maxime Bélair
> <maxime.belair@canonical.com> wrote:
> >
> > Enable users to manage SELinux policies through the new hook
> > lsm_config_system_policy. This feature is restricted to CAP_MAC_ADMIN.
>
> (added selinux mailing list and Fedora/Red Hat SELinux kernel maintainer to cc)
>
> A couple of observations:
> 1. We do not currently require CAP_MAC_ADMIN for loading SELinux
> policy, since it was only added later for Smack and SELinux implements
> its own permission checks. When loading policy via selinuxfs, one
> requires uid-0 or CAP_DAC_OVERRIDE to write to /sys/fs/selinux/load
> plus the corresponding SELinux permissions, but this is just an
> artifact of the filesystem-based interface. I'm not opposed to using
> CAP_MAC_ADMIN for loading policy via the new system call but wanted to
> note it as a difference.
>
> 2. The SELinux namespaces support [1], [2] is based on instantiating a
> separate selinuxfs instance for each namespace; you load a policy for
> a namespace by mounting a new selinuxfs instance after unsharing your
> SELinux namespace and then write to its /sys/fs/selinux/load
> interface, only affecting policy for the new namespace. Your interface
> doesn't appear to support such an approach and IIUC will currently
> always load the init SELinux namespace's policy rather than the
> current process' SELinux namespace.
Actually, on second thought, checking CAP_MAC_ADMIN via capable() will
require the process to have that capability in the global/init
namespace, which IIUC would prevent systemd running in a non-init user
namespace from loading the SELinux policy at all. That's problematic
for a different reason since it would prevent us from using this
interface for loading the namespace policy using this system call.
>
> [1] https://github.com/stephensmalley/selinuxns
> [2] https://lore.kernel.org/selinux/20250814132637.1659-1-stephen.smalley.work@gmail.com/
>
> >
> > Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
> > ---
> > security/selinux/hooks.c | 27 +++++++++++++++++++++++++++
> > security/selinux/include/security.h | 7 +++++++
> > security/selinux/selinuxfs.c | 16 ++++++++++++----
> > 3 files changed, 46 insertions(+), 4 deletions(-)
> >
> > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> > index e7a7dcab81db..3d14d4e47937 100644
> > --- a/security/selinux/hooks.c
> > +++ b/security/selinux/hooks.c
> > @@ -7196,6 +7196,31 @@ static int selinux_uring_allowed(void)
> > }
> > #endif /* CONFIG_IO_URING */
> >
> > +/**
> > + * selinux_lsm_config_system_policy - Manage a LSM policy
> > + * @op: operation to perform. Currently, only LSM_POLICY_LOAD is supported
> > + * @buf: User-supplied buffer
> > + * @size: size of @buf
> > + * @flags: reserved for future use; must be zero
> > + *
> > + * Returns: number of written rules on success, negative value on error
> > + */
> > +static int selinux_lsm_config_system_policy(u32 op, void __user *buf,
> > + size_t size, u32 flags)
> > +{
> > + loff_t pos = 0;
> > +
> > + if (op != LSM_POLICY_LOAD || flags)
> > + return -EOPNOTSUPP;
> > +
> > + if (!selinux_null.dentry || !selinux_null.dentry->d_sb ||
> > + !selinux_null.dentry->d_sb->s_fs_info)
> > + return -ENODEV;
> > +
> > + return __sel_write_load(selinux_null.dentry->d_sb->s_fs_info, buf, size,
> > + &pos);
> > +}
> > +
> > static const struct lsm_id selinux_lsmid = {
> > .name = "selinux",
> > .id = LSM_ID_SELINUX,
> > @@ -7499,6 +7524,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
> > #ifdef CONFIG_PERF_EVENTS
> > LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc),
> > #endif
> > + LSM_HOOK_INIT(lsm_config_system_policy, selinux_lsm_config_system_policy),
> > +
> > };
> >
> > static __init int selinux_init(void)
> > diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
> > index e7827ed7be5f..7b779ea43cc3 100644
> > --- a/security/selinux/include/security.h
> > +++ b/security/selinux/include/security.h
> > @@ -389,7 +389,14 @@ struct selinux_kernel_status {
> > extern void selinux_status_update_setenforce(bool enforcing);
> > extern void selinux_status_update_policyload(u32 seqno);
> > extern void selinux_complete_init(void);
> > +
> > +struct selinux_fs_info;
> > +
> > extern struct path selinux_null;
> > +extern ssize_t __sel_write_load(struct selinux_fs_info *fsi,
> > + const char __user *buf, size_t count,
> > + loff_t *ppos);
> > +
> > extern void selnl_notify_setenforce(int val);
> > extern void selnl_notify_policyload(u32 seqno);
> > extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);
> > diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> > index 47480eb2189b..1f7e611d8300 100644
> > --- a/security/selinux/selinuxfs.c
> > +++ b/security/selinux/selinuxfs.c
> > @@ -567,11 +567,11 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
> > return ret;
> > }
> >
> > -static ssize_t sel_write_load(struct file *file, const char __user *buf,
> > - size_t count, loff_t *ppos)
> > +ssize_t __sel_write_load(struct selinux_fs_info *fsi,
> > + const char __user *buf, size_t count,
> > + loff_t *ppos)
> >
> > {
> > - struct selinux_fs_info *fsi;
> > struct selinux_load_state load_state;
> > ssize_t length;
> > void *data = NULL;
> > @@ -605,7 +605,6 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
> > pr_warn_ratelimited("SELinux: failed to load policy\n");
> > goto out;
> > }
> > - fsi = file_inode(file)->i_sb->s_fs_info;
> > length = sel_make_policy_nodes(fsi, load_state.policy);
> > if (length) {
> > pr_warn_ratelimited("SELinux: failed to initialize selinuxfs\n");
> > @@ -626,6 +625,15 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
> > return length;
> > }
> >
> > +static ssize_t sel_write_load(struct file *file, const char __user *buf,
> > + size_t count, loff_t *ppos)
> > +{
> > + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
> > +
> > + return __sel_write_load(fsi, buf, count, ppos);
> > +}
> > +
> > +
> > static const struct file_operations sel_load_ops = {
> > .write = sel_write_load,
> > .llseek = generic_file_llseek,
> > --
> > 2.48.1
> >
© 2016 - 2025 Red Hat, Inc.