mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH] selinux: prevent truncation of status map
@ 2026-01-30 17:11 Christian Göttsche
  2026-01-30 20:46 ` [PATCH RFC] " Paul Moore
  2026-01-30 21:07 ` [RFC PATCH] " Stephen Smalley
  0 siblings, 2 replies; 8+ messages in thread
From: Christian Göttsche @ 2026-01-30 17:11 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel

From: Christian Göttsche <cgzones@googlemail.com>

Currently the SELinux status map can be truncated, given the necessary
permissions, leading to foreign user space processes getting a bus error
(SIGBUS) while concurrently making use of the status map.
For example systemd can be killed that way, see [1].

Override the setattr inode handler and check for O_TRUNC in the open
handler to prevent truncations.

Link [1]: https://github.com/systemd/systemd/issues/37349
Closes: https://github.com/SELinuxProject/selinux/issues/475
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 security/selinux/selinuxfs.c | 43 ++++++++++++++++++++++++++++++++++--
 1 file changed, 41 insertions(+), 2 deletions(-)

diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 896acad1f5f7..df079a35a02d 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -214,10 +214,30 @@ static const struct file_operations sel_handle_unknown_ops = {
 	.llseek		= generic_file_llseek,
 };
 
+static int sel_setattr_handle_status(struct mnt_idmap *idmap,
+				     struct dentry *dentry,
+				     struct iattr *iattr)
+{
+	/* Prevent truncation to avoid raising SIGBUS */
+	if (iattr->ia_valid & ATTR_SIZE)
+		return -EINVAL;
+
+	return simple_setattr(idmap, dentry, iattr);
+}
+
+static const struct inode_operations sel_handle_status_iops = {
+	.setattr	= sel_setattr_handle_status,
+};
+
 static int sel_open_handle_status(struct inode *inode, struct file *filp)
 {
-	struct page    *status = selinux_kernel_status_page();
+	struct page *status;
 
+	/* Prevent truncation to avoid raising SIGBUS */
+	if (filp->f_flags & O_TRUNC)
+		return -EINVAL;
+
+	status = selinux_kernel_status_page();
 	if (!status)
 		return -ENOMEM;
 
@@ -1980,7 +2000,6 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
 		[SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
 		[SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
 		[SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
-		[SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
 		[SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
 		[SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
 					S_IWUGO},
@@ -1995,6 +2014,26 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
 	if (ret)
 		goto err;
 
+	/* Create "status" separately to assign a custom inode_operations */
+	{
+		ret = -ENOMEM;
+
+		dentry = d_alloc_name(sb->s_root, "status");
+		if (!dentry)
+			goto err;
+		inode = new_inode(sb);
+		if (!inode) {
+			dput(dentry);
+			goto err;
+		}
+		inode->i_mode = S_IFREG | 0444;
+		simple_inode_init_ts(inode);
+		inode->i_fop = &sel_handle_status_ops;
+		inode->i_op = &sel_handle_status_iops;
+		inode->i_ino = SEL_STATUS;
+		d_add(dentry, inode);
+	}
+
 	fsi = sb->s_fs_info;
 	fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino);
 	if (IS_ERR(fsi->bool_dir)) {
-- 
2.51.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH RFC] selinux: prevent truncation of status map
  2026-01-30 17:11 [RFC PATCH] selinux: prevent truncation of status map Christian Göttsche
@ 2026-01-30 20:46 ` Paul Moore
  2026-01-31 17:18   ` Christian Göttsche
  2026-01-30 21:07 ` [RFC PATCH] " Stephen Smalley
  1 sibling, 1 reply; 8+ messages in thread
From: Paul Moore @ 2026-01-30 20:46 UTC (permalink / raw)
  To: Christian Göttsche, selinux
  Cc: Christian Göttsche, Stephen Smalley, Ondrej Mosnacek, linux-kernel

On Jan 30, 2026 =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgoettsche@seltendoof.de> wrote:
> 
> Currently the SELinux status map can be truncated, given the necessary
> permissions, leading to foreign user space processes getting a bus error
> (SIGBUS) while concurrently making use of the status map.
> For example systemd can be killed that way, see [1].
> 
> Override the setattr inode handler and check for O_TRUNC in the open
> handler to prevent truncations.
> 
> Link [1]: https://github.com/systemd/systemd/issues/37349
> 
> Closes: https://github.com/SELinuxProject/selinux/issues/475
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> ---
>  security/selinux/selinuxfs.c | 43 ++++++++++++++++++++++++++++++++++--
>  1 file changed, 41 insertions(+), 2 deletions(-)
> 
> diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> index 896acad1f5f7..df079a35a02d 100644
> --- a/security/selinux/selinuxfs.c
> +++ b/security/selinux/selinuxfs.c
> @@ -214,10 +214,30 @@ static const struct file_operations sel_handle_unknown_ops = {
>  	.llseek		= generic_file_llseek,
>  };
>  
> +static int sel_setattr_handle_status(struct mnt_idmap *idmap,
> +				     struct dentry *dentry,
> +				     struct iattr *iattr)
> +{
> +	/* Prevent truncation to avoid raising SIGBUS */
> +	if (iattr->ia_valid & ATTR_SIZE)
> +		return -EINVAL;

Do we want this as -EINVAL or -EPERM?  However, see my comments below
about how to handle the ATTR_SIZE case.

> +	return simple_setattr(idmap, dentry, iattr);
> +}
> +
> +static const struct inode_operations sel_handle_status_iops = {
> +	.setattr	= sel_setattr_handle_status,
> +};
> +
>  static int sel_open_handle_status(struct inode *inode, struct file *filp)
>  {
> -	struct page    *status = selinux_kernel_status_page();
> +	struct page *status;
>  
> +	/* Prevent truncation to avoid raising SIGBUS */
> +	if (filp->f_flags & O_TRUNC)
> +		return -EINVAL;

Same as above, should this be -EPERM?

> +	status = selinux_kernel_status_page();
>  	if (!status)
>  		return -ENOMEM;
>  
> @@ -1980,7 +2000,6 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
>  		[SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
>  		[SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
>  		[SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
> -		[SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
>  		[SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
>  		[SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
>  					S_IWUGO},
> @@ -1995,6 +2014,26 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
>  	if (ret)
>  		goto err;
>  
> +	/* Create "status" separately to assign a custom inode_operations */
> +	{
> +		ret = -ENOMEM;
> +
> +		dentry = d_alloc_name(sb->s_root, "status");
> +		if (!dentry)
> +			goto err;
> +		inode = new_inode(sb);
> +		if (!inode) {
> +			dput(dentry);
> +			goto err;
> +		}
> +		inode->i_mode = S_IFREG | 0444;
> +		simple_inode_init_ts(inode);
> +		inode->i_fop = &sel_handle_status_ops;
> +		inode->i_op = &sel_handle_status_iops;
> +		inode->i_ino = SEL_STATUS;
> +		d_add(dentry, inode);
> +	}

I worry a little about duplicating and open coding the per-file loop from
simple_fill_super(), I can see things slowly getting out of sync and bad
things happening.  Unfortunately, I don't see anything in libfs that would
allow us to supply our own inode_operations either.

What do you think about using selinux_inode_setattr() as shown below,
would this work?

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index feda34b18d83..8e4374f22a18 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -106,6 +106,7 @@
 #include "netlabel.h"
 #include "audit.h"
 #include "avc_ss.h"
+#include "selinuxfs.h"
 
 #define SELINUX_INODE_INIT_XATTRS 1
 
@@ -3291,6 +3292,12 @@ static int selinux_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
                        return 0;
        }
 
+       if (inode->i_sb->s_magic == SELINUX_MAGIC) {
+               int rc = selinuxfs_inode_setattr(idmap, inode, iattr);
+               if (rc)
+                       return rc;
+       }
+
        if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID |
                        ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_TIMES_SET))
                return dentry_has_perm(cred, dentry, FILE__SETATTR);
diff --git a/security/selinux/include/selinuxfs.h b/security/selinux/include/selinuxfs.h
new file mode 100644
index 000000000000..f5e6da02833b
--- /dev/null
+++ b/security/selinux/include/selinuxfs.h
@@ -0,0 +1,2 @@
+int selinuxfs_inode_setattr(struct mnt_idmap *idmap, struct inode *inode,
+                            struct iattr *iattr);
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 896acad1f5f7..5af5e1dee743 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -43,6 +43,7 @@
 #include "objsec.h"
 #include "conditional.h"
 #include "ima.h"
+#include "selinuxfs.h"
 
 enum sel_inos {
        SEL_ROOT_INO = 2,
@@ -1954,6 +1955,20 @@ static struct dentry *sel_make_swapover_dir(struct super_block *sb,
        return dentry;  // borrowed
 }
 
+int selinuxfs_inode_setattr(struct mnt_idmap *idmap, struct inode *inode,
+                           struct iattr *iattr)
+{
+       switch (inode->i_ino) {
+       case SEL_STATUS:
+               if (iattr->ia_valid & ATTR_SIZE)
+                       return -EPERM;
+       default:
+               break;
+       }
+
+       return 0;
+}

--
paul-moore.com

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH] selinux: prevent truncation of status map
  2026-01-30 17:11 [RFC PATCH] selinux: prevent truncation of status map Christian Göttsche
  2026-01-30 20:46 ` [PATCH RFC] " Paul Moore
@ 2026-01-30 21:07 ` Stephen Smalley
  2026-01-30 21:47   ` Paul Moore
  2026-01-31 17:20   ` Christian Göttsche
  1 sibling, 2 replies; 8+ messages in thread
From: Stephen Smalley @ 2026-01-30 21:07 UTC (permalink / raw)
  To: cgzones; +Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel

On Fri, Jan 30, 2026 at 12:11 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Currently the SELinux status map can be truncated, given the necessary
> permissions, leading to foreign user space processes getting a bus error
> (SIGBUS) while concurrently making use of the status map.
> For example systemd can be killed that way, see [1].
>
> Override the setattr inode handler and check for O_TRUNC in the open
> handler to prevent truncations.
>
> Link [1]: https://github.com/systemd/systemd/issues/37349
> Closes: https://github.com/SELinuxProject/selinux/issues/475
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

This issue can't be limited to just the status node, so if we care
about preventing this, we ought to do it for them all.
When would we want to allow truncation of _any_ selinuxfs file?

> ---
>  security/selinux/selinuxfs.c | 43 ++++++++++++++++++++++++++++++++++--
>  1 file changed, 41 insertions(+), 2 deletions(-)
>
> diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> index 896acad1f5f7..df079a35a02d 100644
> --- a/security/selinux/selinuxfs.c
> +++ b/security/selinux/selinuxfs.c
> @@ -214,10 +214,30 @@ static const struct file_operations sel_handle_unknown_ops = {
>         .llseek         = generic_file_llseek,
>  };
>
> +static int sel_setattr_handle_status(struct mnt_idmap *idmap,
> +                                    struct dentry *dentry,
> +                                    struct iattr *iattr)
> +{
> +       /* Prevent truncation to avoid raising SIGBUS */
> +       if (iattr->ia_valid & ATTR_SIZE)
> +               return -EINVAL;
> +
> +       return simple_setattr(idmap, dentry, iattr);
> +}
> +
> +static const struct inode_operations sel_handle_status_iops = {
> +       .setattr        = sel_setattr_handle_status,
> +};
> +
>  static int sel_open_handle_status(struct inode *inode, struct file *filp)
>  {
> -       struct page    *status = selinux_kernel_status_page();
> +       struct page *status;
>
> +       /* Prevent truncation to avoid raising SIGBUS */
> +       if (filp->f_flags & O_TRUNC)
> +               return -EINVAL;
> +
> +       status = selinux_kernel_status_page();
>         if (!status)
>                 return -ENOMEM;
>
> @@ -1980,7 +2000,6 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
>                 [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
>                 [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
>                 [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
> -               [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
>                 [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
>                 [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
>                                         S_IWUGO},
> @@ -1995,6 +2014,26 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
>         if (ret)
>                 goto err;
>
> +       /* Create "status" separately to assign a custom inode_operations */
> +       {
> +               ret = -ENOMEM;
> +
> +               dentry = d_alloc_name(sb->s_root, "status");
> +               if (!dentry)
> +                       goto err;
> +               inode = new_inode(sb);
> +               if (!inode) {
> +                       dput(dentry);
> +                       goto err;
> +               }
> +               inode->i_mode = S_IFREG | 0444;
> +               simple_inode_init_ts(inode);
> +               inode->i_fop = &sel_handle_status_ops;
> +               inode->i_op = &sel_handle_status_iops;
> +               inode->i_ino = SEL_STATUS;
> +               d_add(dentry, inode);
> +       }
> +
>         fsi = sb->s_fs_info;
>         fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino);
>         if (IS_ERR(fsi->bool_dir)) {
> --
> 2.51.0
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH] selinux: prevent truncation of status map
  2026-01-30 21:07 ` [RFC PATCH] " Stephen Smalley
@ 2026-01-30 21:47   ` Paul Moore
  2026-01-31 17:20   ` Christian Göttsche
  1 sibling, 0 replies; 8+ messages in thread
From: Paul Moore @ 2026-01-30 21:47 UTC (permalink / raw)
  To: Stephen Smalley; +Cc: cgzones, selinux, Ondrej Mosnacek, linux-kernel

On Fri, Jan 30, 2026 at 4:08 PM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
> On Fri, Jan 30, 2026 at 12:11 PM Christian Göttsche
> <cgoettsche@seltendoof.de> wrote:
> >
> > From: Christian Göttsche <cgzones@googlemail.com>
> >
> > Currently the SELinux status map can be truncated, given the necessary
> > permissions, leading to foreign user space processes getting a bus error
> > (SIGBUS) while concurrently making use of the status map.
> > For example systemd can be killed that way, see [1].
> >
> > Override the setattr inode handler and check for O_TRUNC in the open
> > handler to prevent truncations.
> >
> > Link [1]: https://github.com/systemd/systemd/issues/37349
> > Closes: https://github.com/SELinuxProject/selinux/issues/475
> > Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
>
> This issue can't be limited to just the status node, so if we care
> about preventing this, we ought to do it for them all.
> When would we want to allow truncation of _any_ selinuxfs file?

Fair point.

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH RFC] selinux: prevent truncation of status map
  2026-01-30 20:46 ` [PATCH RFC] " Paul Moore
@ 2026-01-31 17:18   ` Christian Göttsche
  2026-02-20 23:13     ` Paul Moore
  0 siblings, 1 reply; 8+ messages in thread
From: Christian Göttsche @ 2026-01-31 17:18 UTC (permalink / raw)
  To: selinux; +Cc: Paul Moore, Stephen Smalley, Ondrej Mosnacek, linux-kernel

On Fri, 30 Jan 2026 at 21:46, Paul Moore <paul@paul-moore.com> wrote:
>
> On Jan 30, 2026 =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgoettsche@seltendoof.de> wrote:
> >
> > Currently the SELinux status map can be truncated, given the necessary
> > permissions, leading to foreign user space processes getting a bus error
> > (SIGBUS) while concurrently making use of the status map.
> > For example systemd can be killed that way, see [1].
> >
> > Override the setattr inode handler and check for O_TRUNC in the open
> > handler to prevent truncations.
> >
> > Link [1]: https://github.com/systemd/systemd/issues/37349
> >
> > Closes: https://github.com/SELinuxProject/selinux/issues/475
> > Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> > ---
> >  security/selinux/selinuxfs.c | 43 ++++++++++++++++++++++++++++++++++--
> >  1 file changed, 41 insertions(+), 2 deletions(-)
> >
> > diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> > index 896acad1f5f7..df079a35a02d 100644
> > --- a/security/selinux/selinuxfs.c
> > +++ b/security/selinux/selinuxfs.c
> > @@ -214,10 +214,30 @@ static const struct file_operations sel_handle_unknown_ops = {
> >       .llseek         = generic_file_llseek,
> >  };
> >
> > +static int sel_setattr_handle_status(struct mnt_idmap *idmap,
> > +                                  struct dentry *dentry,
> > +                                  struct iattr *iattr)
> > +{
> > +     /* Prevent truncation to avoid raising SIGBUS */
> > +     if (iattr->ia_valid & ATTR_SIZE)
> > +             return -EINVAL;
>
> Do we want this as -EINVAL or -EPERM?  However, see my comments below
> about how to handle the ATTR_SIZE case.

I did not choose EPERM because it is not a matter of missing
permissions of the caller.
The status page should just not support truncation and EINVAL seemed
more natural than ENOTSUP.

>
> > +     return simple_setattr(idmap, dentry, iattr);
> > +}
> > +
> > +static const struct inode_operations sel_handle_status_iops = {
> > +     .setattr        = sel_setattr_handle_status,
> > +};
> > +
> >  static int sel_open_handle_status(struct inode *inode, struct file *filp)
> >  {
> > -     struct page    *status = selinux_kernel_status_page();
> > +     struct page *status;
> >
> > +     /* Prevent truncation to avoid raising SIGBUS */
> > +     if (filp->f_flags & O_TRUNC)
> > +             return -EINVAL;
>
> Same as above, should this be -EPERM?
>
> > +     status = selinux_kernel_status_page();
> >       if (!status)
> >               return -ENOMEM;
> >
> > @@ -1980,7 +2000,6 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
> >               [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
> >               [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
> >               [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
> > -             [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
> >               [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
> >               [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
> >                                       S_IWUGO},
> > @@ -1995,6 +2014,26 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
> >       if (ret)
> >               goto err;
> >
> > +     /* Create "status" separately to assign a custom inode_operations */
> > +     {
> > +             ret = -ENOMEM;
> > +
> > +             dentry = d_alloc_name(sb->s_root, "status");
> > +             if (!dentry)
> > +                     goto err;
> > +             inode = new_inode(sb);
> > +             if (!inode) {
> > +                     dput(dentry);
> > +                     goto err;
> > +             }
> > +             inode->i_mode = S_IFREG | 0444;
> > +             simple_inode_init_ts(inode);
> > +             inode->i_fop = &sel_handle_status_ops;
> > +             inode->i_op = &sel_handle_status_iops;
> > +             inode->i_ino = SEL_STATUS;
> > +             d_add(dentry, inode);
> > +     }
>
> I worry a little about duplicating and open coding the per-file loop from
> simple_fill_super(), I can see things slowly getting out of sync and bad
> things happening.  Unfortunately, I don't see anything in libfs that would
> allow us to supply our own inode_operations either.

We could ask the FS people if they would be fine with the following patch:


diff --git a/fs/libfs.c b/fs/libfs.c
index 9264523be85c..76f7fec136cb 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -1089,6 +1089,7 @@ int simple_fill_super(struct super_block *s,
unsigned long magic,
               }
               inode->i_mode = S_IFREG | files->mode;
               simple_inode_init_ts(inode);
+               inode->i_op = files->iops;
               inode->i_fop = files->ops;
               inode->i_ino = i;
               d_make_persistent(dentry, inode);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 04ceeca12a0d..9f1a9f0a9b48 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3225,7 +3225,7 @@ extern const struct file_operations simple_dir_operations;
extern const struct inode_operations simple_dir_inode_operations;
extern void make_empty_dir_inode(struct inode *inode);
extern bool is_empty_dir_inode(struct inode *inode);
-struct tree_descr { const char *name; const struct file_operations
*ops; int mode; };
+struct tree_descr { const char *name; const struct file_operations
*ops; int mode; const struct inode_operations *iops; };
struct dentry *d_alloc_name(struct dentry *, const char *);
extern int simple_fill_super(struct super_block *, unsigned long,
                            const struct tree_descr *);


and then adding the hook would just be

-               [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
+               [SEL_STATUS] = {"status", &sel_handle_status_ops,
S_IRUGO, &sel_handle_status_iops},

>
> What do you think about using selinux_inode_setattr() as shown below,
> would this work?
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index feda34b18d83..8e4374f22a18 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -106,6 +106,7 @@
>  #include "netlabel.h"
>  #include "audit.h"
>  #include "avc_ss.h"
> +#include "selinuxfs.h"
>
>  #define SELINUX_INODE_INIT_XATTRS 1
>
> @@ -3291,6 +3292,12 @@ static int selinux_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
>                         return 0;
>         }
>
> +       if (inode->i_sb->s_magic == SELINUX_MAGIC) {
> +               int rc = selinuxfs_inode_setattr(idmap, inode, iattr);
> +               if (rc)
> +                       return rc;
> +       }
> +

I don't like that approach, since up to now selinux_inode_setattr() is
only for SELinux permission checking and this would add some behavior
modification into it.

>         if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID |
>                         ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_TIMES_SET))
>                 return dentry_has_perm(cred, dentry, FILE__SETATTR);
> diff --git a/security/selinux/include/selinuxfs.h b/security/selinux/include/selinuxfs.h
> new file mode 100644
> index 000000000000..f5e6da02833b
> --- /dev/null
> +++ b/security/selinux/include/selinuxfs.h
> @@ -0,0 +1,2 @@
> +int selinuxfs_inode_setattr(struct mnt_idmap *idmap, struct inode *inode,
> +                            struct iattr *iattr);
> diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> index 896acad1f5f7..5af5e1dee743 100644
> --- a/security/selinux/selinuxfs.c
> +++ b/security/selinux/selinuxfs.c
> @@ -43,6 +43,7 @@
>  #include "objsec.h"
>  #include "conditional.h"
>  #include "ima.h"
> +#include "selinuxfs.h"
>
>  enum sel_inos {
>         SEL_ROOT_INO = 2,
> @@ -1954,6 +1955,20 @@ static struct dentry *sel_make_swapover_dir(struct super_block *sb,
>         return dentry;  // borrowed
>  }
>
> +int selinuxfs_inode_setattr(struct mnt_idmap *idmap, struct inode *inode,
> +                           struct iattr *iattr)
> +{
> +       switch (inode->i_ino) {
> +       case SEL_STATUS:
> +               if (iattr->ia_valid & ATTR_SIZE)
> +                       return -EPERM;
> +       default:
> +               break;
> +       }
> +
> +       return 0;
> +}
>
> --
> paul-moore.com

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH] selinux: prevent truncation of status map
  2026-01-30 21:07 ` [RFC PATCH] " Stephen Smalley
  2026-01-30 21:47   ` Paul Moore
@ 2026-01-31 17:20   ` Christian Göttsche
  2026-02-12 13:59     ` Stephen Smalley
  1 sibling, 1 reply; 8+ messages in thread
From: Christian Göttsche @ 2026-01-31 17:20 UTC (permalink / raw)
  To: selinux; +Cc: Paul Moore, Stephen Smalley, Ondrej Mosnacek, linux-kernel

On Fri, 30 Jan 2026 at 22:08, Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Fri, Jan 30, 2026 at 12:11 PM Christian Göttsche
> <cgoettsche@seltendoof.de> wrote:
> >
> > From: Christian Göttsche <cgzones@googlemail.com>
> >
> > Currently the SELinux status map can be truncated, given the necessary
> > permissions, leading to foreign user space processes getting a bus error
> > (SIGBUS) while concurrently making use of the status map.
> > For example systemd can be killed that way, see [1].
> >
> > Override the setattr inode handler and check for O_TRUNC in the open
> > handler to prevent truncations.
> >
> > Link [1]: https://github.com/systemd/systemd/issues/37349
> > Closes: https://github.com/SELinuxProject/selinux/issues/475
> > Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
>
> This issue can't be limited to just the status node, so if we care
> about preventing this, we ought to do it for them all.
> When would we want to allow truncation of _any_ selinuxfs file?

Probably not, but - at least to my quick overview - the status map is
the only file-object backed up by real memory and the only file
commonly mapped by userspace.

>
> > ---
> >  security/selinux/selinuxfs.c | 43 ++++++++++++++++++++++++++++++++++--
> >  1 file changed, 41 insertions(+), 2 deletions(-)
> >
> > diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> > index 896acad1f5f7..df079a35a02d 100644
> > --- a/security/selinux/selinuxfs.c
> > +++ b/security/selinux/selinuxfs.c
> > @@ -214,10 +214,30 @@ static const struct file_operations sel_handle_unknown_ops = {
> >         .llseek         = generic_file_llseek,
> >  };
> >
> > +static int sel_setattr_handle_status(struct mnt_idmap *idmap,
> > +                                    struct dentry *dentry,
> > +                                    struct iattr *iattr)
> > +{
> > +       /* Prevent truncation to avoid raising SIGBUS */
> > +       if (iattr->ia_valid & ATTR_SIZE)
> > +               return -EINVAL;
> > +
> > +       return simple_setattr(idmap, dentry, iattr);
> > +}
> > +
> > +static const struct inode_operations sel_handle_status_iops = {
> > +       .setattr        = sel_setattr_handle_status,
> > +};
> > +
> >  static int sel_open_handle_status(struct inode *inode, struct file *filp)
> >  {
> > -       struct page    *status = selinux_kernel_status_page();
> > +       struct page *status;
> >
> > +       /* Prevent truncation to avoid raising SIGBUS */
> > +       if (filp->f_flags & O_TRUNC)
> > +               return -EINVAL;
> > +
> > +       status = selinux_kernel_status_page();
> >         if (!status)
> >                 return -ENOMEM;
> >
> > @@ -1980,7 +2000,6 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
> >                 [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
> >                 [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
> >                 [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
> > -               [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
> >                 [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
> >                 [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
> >                                         S_IWUGO},
> > @@ -1995,6 +2014,26 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
> >         if (ret)
> >                 goto err;
> >
> > +       /* Create "status" separately to assign a custom inode_operations */
> > +       {
> > +               ret = -ENOMEM;
> > +
> > +               dentry = d_alloc_name(sb->s_root, "status");
> > +               if (!dentry)
> > +                       goto err;
> > +               inode = new_inode(sb);
> > +               if (!inode) {
> > +                       dput(dentry);
> > +                       goto err;
> > +               }
> > +               inode->i_mode = S_IFREG | 0444;
> > +               simple_inode_init_ts(inode);
> > +               inode->i_fop = &sel_handle_status_ops;
> > +               inode->i_op = &sel_handle_status_iops;
> > +               inode->i_ino = SEL_STATUS;
> > +               d_add(dentry, inode);
> > +       }
> > +
> >         fsi = sb->s_fs_info;
> >         fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino);
> >         if (IS_ERR(fsi->bool_dir)) {
> > --
> > 2.51.0
> >

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH] selinux: prevent truncation of status map
  2026-01-31 17:20   ` Christian Göttsche
@ 2026-02-12 13:59     ` Stephen Smalley
  0 siblings, 0 replies; 8+ messages in thread
From: Stephen Smalley @ 2026-02-12 13:59 UTC (permalink / raw)
  To: Christian Göttsche
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel

On Sat, Jan 31, 2026 at 12:20 PM Christian Göttsche
<cgzones@googlemail.com> wrote:
>
> On Fri, 30 Jan 2026 at 22:08, Stephen Smalley
> <stephen.smalley.work@gmail.com> wrote:
> >
> > On Fri, Jan 30, 2026 at 12:11 PM Christian Göttsche
> > <cgoettsche@seltendoof.de> wrote:
> > >
> > > From: Christian Göttsche <cgzones@googlemail.com>
> > >
> > > Currently the SELinux status map can be truncated, given the necessary
> > > permissions, leading to foreign user space processes getting a bus error
> > > (SIGBUS) while concurrently making use of the status map.
> > > For example systemd can be killed that way, see [1].
> > >
> > > Override the setattr inode handler and check for O_TRUNC in the open
> > > handler to prevent truncations.
> > >
> > > Link [1]: https://github.com/systemd/systemd/issues/37349
> > > Closes: https://github.com/SELinuxProject/selinux/issues/475
> > > Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> >
> > This issue can't be limited to just the status node, so if we care
> > about preventing this, we ought to do it for them all.
> > When would we want to allow truncation of _any_ selinuxfs file?
>
> Probably not, but - at least to my quick overview - the status map is
> the only file-object backed up by real memory and the only file
> commonly mapped by userspace.

/sys/fs/selinux/policy would be another one.

I would expect this to be an issue for other pseudo filesystems using
libfs, so would ask on linux-fsdevel and cc the VFS maintainers on
whether there is a general solution for this problem.

>
> >
> > > ---
> > >  security/selinux/selinuxfs.c | 43 ++++++++++++++++++++++++++++++++++--
> > >  1 file changed, 41 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> > > index 896acad1f5f7..df079a35a02d 100644
> > > --- a/security/selinux/selinuxfs.c
> > > +++ b/security/selinux/selinuxfs.c
> > > @@ -214,10 +214,30 @@ static const struct file_operations sel_handle_unknown_ops = {
> > >         .llseek         = generic_file_llseek,
> > >  };
> > >
> > > +static int sel_setattr_handle_status(struct mnt_idmap *idmap,
> > > +                                    struct dentry *dentry,
> > > +                                    struct iattr *iattr)
> > > +{
> > > +       /* Prevent truncation to avoid raising SIGBUS */
> > > +       if (iattr->ia_valid & ATTR_SIZE)
> > > +               return -EINVAL;
> > > +
> > > +       return simple_setattr(idmap, dentry, iattr);
> > > +}
> > > +
> > > +static const struct inode_operations sel_handle_status_iops = {
> > > +       .setattr        = sel_setattr_handle_status,
> > > +};
> > > +
> > >  static int sel_open_handle_status(struct inode *inode, struct file *filp)
> > >  {
> > > -       struct page    *status = selinux_kernel_status_page();
> > > +       struct page *status;
> > >
> > > +       /* Prevent truncation to avoid raising SIGBUS */
> > > +       if (filp->f_flags & O_TRUNC)
> > > +               return -EINVAL;
> > > +
> > > +       status = selinux_kernel_status_page();
> > >         if (!status)
> > >                 return -ENOMEM;
> > >
> > > @@ -1980,7 +2000,6 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
> > >                 [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
> > >                 [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
> > >                 [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
> > > -               [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
> > >                 [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
> > >                 [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
> > >                                         S_IWUGO},
> > > @@ -1995,6 +2014,26 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
> > >         if (ret)
> > >                 goto err;
> > >
> > > +       /* Create "status" separately to assign a custom inode_operations */
> > > +       {
> > > +               ret = -ENOMEM;
> > > +
> > > +               dentry = d_alloc_name(sb->s_root, "status");
> > > +               if (!dentry)
> > > +                       goto err;
> > > +               inode = new_inode(sb);
> > > +               if (!inode) {
> > > +                       dput(dentry);
> > > +                       goto err;
> > > +               }
> > > +               inode->i_mode = S_IFREG | 0444;
> > > +               simple_inode_init_ts(inode);
> > > +               inode->i_fop = &sel_handle_status_ops;
> > > +               inode->i_op = &sel_handle_status_iops;
> > > +               inode->i_ino = SEL_STATUS;
> > > +               d_add(dentry, inode);
> > > +       }
> > > +
> > >         fsi = sb->s_fs_info;
> > >         fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino);
> > >         if (IS_ERR(fsi->bool_dir)) {
> > > --
> > > 2.51.0
> > >

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH RFC] selinux: prevent truncation of status map
  2026-01-31 17:18   ` Christian Göttsche
@ 2026-02-20 23:13     ` Paul Moore
  0 siblings, 0 replies; 8+ messages in thread
From: Paul Moore @ 2026-02-20 23:13 UTC (permalink / raw)
  To: Christian Göttsche
  Cc: selinux, Stephen Smalley, Ondrej Mosnacek, linux-kernel

On Sat, Jan 31, 2026 at 12:18 PM Christian Göttsche
<cgzones@googlemail.com> wrote:
> On Fri, 30 Jan 2026 at 21:46, Paul Moore <paul@paul-moore.com> wrote:
> > On Jan 30, 2026 =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgoettsche@seltendoof.de> wrote:
> > >
> > > Currently the SELinux status map can be truncated, given the necessary
> > > permissions, leading to foreign user space processes getting a bus error
> > > (SIGBUS) while concurrently making use of the status map.
> > > For example systemd can be killed that way, see [1].
> > >
> > > Override the setattr inode handler and check for O_TRUNC in the open
> > > handler to prevent truncations.
> > >
> > > Link [1]: https://github.com/systemd/systemd/issues/37349
> > >
> > > Closes: https://github.com/SELinuxProject/selinux/issues/475
> > > Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> > > ---
> > >  security/selinux/selinuxfs.c | 43 ++++++++++++++++++++++++++++++++++--
> > >  1 file changed, 41 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> > > index 896acad1f5f7..df079a35a02d 100644
> > > --- a/security/selinux/selinuxfs.c
> > > +++ b/security/selinux/selinuxfs.c
> > > @@ -214,10 +214,30 @@ static const struct file_operations sel_handle_unknown_ops = {
> > >       .llseek         = generic_file_llseek,
> > >  };
> > >
> > > +static int sel_setattr_handle_status(struct mnt_idmap *idmap,
> > > +                                  struct dentry *dentry,
> > > +                                  struct iattr *iattr)
> > > +{
> > > +     /* Prevent truncation to avoid raising SIGBUS */
> > > +     if (iattr->ia_valid & ATTR_SIZE)
> > > +             return -EINVAL;
> >
> > Do we want this as -EINVAL or -EPERM?  However, see my comments below
> > about how to handle the ATTR_SIZE case.
>
> I did not choose EPERM because it is not a matter of missing
> permissions of the caller.
> The status page should just not support truncation and EINVAL seemed
> more natural than ENOTSUP.

Okay, I can understand that argument.

> > > @@ -1995,6 +2014,26 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
> > >       if (ret)
> > >               goto err;
> > >
> > > +     /* Create "status" separately to assign a custom inode_operations */
> > > +     {
> > > +             ret = -ENOMEM;
> > > +
> > > +             dentry = d_alloc_name(sb->s_root, "status");
> > > +             if (!dentry)
> > > +                     goto err;
> > > +             inode = new_inode(sb);
> > > +             if (!inode) {
> > > +                     dput(dentry);
> > > +                     goto err;
> > > +             }
> > > +             inode->i_mode = S_IFREG | 0444;
> > > +             simple_inode_init_ts(inode);
> > > +             inode->i_fop = &sel_handle_status_ops;
> > > +             inode->i_op = &sel_handle_status_iops;
> > > +             inode->i_ino = SEL_STATUS;
> > > +             d_add(dentry, inode);
> > > +     }
> >
> > I worry a little about duplicating and open coding the per-file loop from
> > simple_fill_super(), I can see things slowly getting out of sync and bad
> > things happening.  Unfortunately, I don't see anything in libfs that would
> > allow us to supply our own inode_operations either.
>
> We could ask the FS people if they would be fine with the following patch:

We probably need to give them another week or so to look at your
proposal and reply.   I'm very curious to hear what they have to say
on the issue.

> > What do you think about using selinux_inode_setattr() as shown below,
> > would this work?
> >
> > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> > index feda34b18d83..8e4374f22a18 100644
> > --- a/security/selinux/hooks.c
> > +++ b/security/selinux/hooks.c
> > @@ -106,6 +106,7 @@
> >  #include "netlabel.h"
> >  #include "audit.h"
> >  #include "avc_ss.h"
> > +#include "selinuxfs.h"
> >
> >  #define SELINUX_INODE_INIT_XATTRS 1
> >
> > @@ -3291,6 +3292,12 @@ static int selinux_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
> >                         return 0;
> >         }
> >
> > +       if (inode->i_sb->s_magic == SELINUX_MAGIC) {
> > +               int rc = selinuxfs_inode_setattr(idmap, inode, iattr);
> > +               if (rc)
> > +                       return rc;
> > +       }
> > +
>
> I don't like that approach, since up to now selinux_inode_setattr() is
> only for SELinux permission checking and this would add some behavior
> modification into it.

I disagree with that characterization, but I'm not in love with that
solution either; special-casing specific filesystems always feels ugly
to me.  Let's see what vfs folks have to say about your proposal.

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-02-20 23:13 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-30 17:11 [RFC PATCH] selinux: prevent truncation of status map Christian Göttsche
2026-01-30 20:46 ` [PATCH RFC] " Paul Moore
2026-01-31 17:18   ` Christian Göttsche
2026-02-20 23:13     ` Paul Moore
2026-01-30 21:07 ` [RFC PATCH] " Stephen Smalley
2026-01-30 21:47   ` Paul Moore
2026-01-31 17:20   ` Christian Göttsche
2026-02-12 13:59     ` Stephen Smalley

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®