mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
@ 2026-11-02 13:05 Ondrej Mosnáček
  2026-11-02 13:13 ` Ondrej Mosnáček
  2026-11-02 15:14 ` Stephen Smalley
  0 siblings, 2 replies; 5+ messages in thread
From: Ondrej Mosnáček @ 2026-11-02 13:05 UTC (permalink / raw)
  To: Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn,
	Jakub Kicinski
  Cc: Paul Moore, Stephen Smalley, James Morris, Serge E. Hallyn,
	Simon Horman, Jens Axboe, netdev, selinux, linux-security-module,
	io-uring, linux-fsdevel, linux-kernel

Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
mechanism, the security_file_receive() LSM hook is calledto allow LSMs
like SELinux to validate the receiver's access to the received file
(similarly to a process inheriting file descriptors through execve(2)).

However, there are cases (such as dbus daemon/dbus-broker) where the
receiver doesn't intend to access the received file and merely wants to
forward it to a third process. In this case the forwarding process would
need to have the appropriate r/w access to the file in order to receive
it, even though it will never actually acess it. That leads to the
security policy having to grant the forwarder access to a potentially
wide range of files (in case of dbus-broker basically all files), which
is not ideal.

This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
recvmsg(2), wraps any received files into an opaque anonymous inode with
the name "[envelope]", skipping the security_file_receive() LSM check.
The returned "[envelope]" file descriptor can't be used in any
meaningful way other than being passed forward via sendmsg(2) with the
MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
sent via SCM_RIGHTS to the receiver. The receiver can then either
receive the file as normal (subject to the LSM check) or again "sealed"
in the "[envelope]" anon inode for further forwarding.

Note that sending the "[envelope]" file is only allowed with the
MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
unexpected behavior. Attempting to send it without this flag will fail
with an error. This also avoids recursive nesting of "[envelope]" files,
which would only make this feature harder to audit for possible issues.

This patch also adds a simple read-only sockopt for UNIX sockets that
allows userspace to determine if this feature is present (passing
unknown CMSG flags unfortunately doesn't lead to an error currently).
The minimal code to do so can look like this:

    #ifndef SO_RIGHTS_SEALED_SUPPORTED
    #define SO_RIGHTS_SEALED_SUPPORTED 88
    #endif

    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdbool.h>

    bool MSG_CMSG_SEALED_supported(void)
    {
        int sock, ret;
        socklen_t optlen = 0;

        sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sock < 0) {
            /* handle error */
            /* ... */
        }

        ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
                         NULL, &optlen);
        close(sock);

        return ret == 0;
    }

Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
---
 arch/alpha/include/uapi/asm/socket.h    |  2 +
 arch/mips/include/uapi/asm/socket.h     |  2 +
 arch/parisc/include/uapi/asm/socket.h   |  2 +
 arch/sparc/include/uapi/asm/socket.h    |  2 +
 include/linux/socket.h                  |  4 +-
 include/net/scm.h                       |  2 +-
 include/uapi/asm-generic/socket.h       |  2 +
 io_uring/net.c                          |  2 +-
 net/compat.c                            |  3 +-
 net/core/scm.c                          | 81 +++++++++++++++++++++----
 net/core/sock.c                         |  9 +++
 net/socket.c                            |  2 +-
 tools/include/uapi/asm-generic/socket.h |  2 +
 13 files changed, 97 insertions(+), 18 deletions(-)

diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 5d3524c26b2b..cd9fce1d9432 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -161,6 +161,8 @@
 
 #define SO_PEERPIDFD_THREAD    87
 
+#define SO_RIGHTS_SEALED_SUPPORTED	88
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
index 245a43f52fb1..393898479ce6 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -172,6 +172,8 @@
 
 #define SO_PEERPIDFD_THREAD    87
 
+#define SO_RIGHTS_SEALED_SUPPORTED	88
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
index f23710e1c671..055b532ccc14 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -153,6 +153,8 @@
 
 #define SO_PEERPIDFD_THREAD	0x4055
 
+#define SO_RIGHTS_SEALED_SUPPORTED	0x4056
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
index b35b25bdefc2..e152e2bdf42e 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -154,6 +154,8 @@
 
 #define SO_PEERPIDFD_THREAD      0x0060
 
+#define SO_RIGHTS_SEALED_SUPPORTED	0x0061
+
 #if !defined(__KERNEL__)
 
 
diff --git a/include/linux/socket.h b/include/linux/socket.h
index b17fdb7d38a7..90e4bc5d3f91 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -345,7 +345,9 @@ struct ucred {
 #define MSG_SOCK_DEVMEM 0x2000000	/* Receive devmem skbs as cmsg */
 #define MSG_ZEROCOPY	0x4000000	/* Use user data in kernel path */
 #define MSG_SPLICE_PAGES 0x8000000	/* Splice the pages from the iterator in sendmsg() */
-#define MSG_FASTOPEN	0x20000000	/* Send data in TCP SYN */
+#define MSG_CMSG_SEALED  0x10000000	/* Seal file descriptors received
+					   through SCM_RIGHTS */
+#define MSG_FASTOPEN     0x20000000	/* Send data in TCP SYN */
 #define MSG_CMSG_CLOEXEC 0x40000000	/* Set close_on_exec for file
 					   descriptor received through
 					   SCM_RIGHTS */
diff --git a/include/net/scm.h b/include/net/scm.h
index b68a143f42de..53b999868104 100644
--- a/include/net/scm.h
+++ b/include/net/scm.h
@@ -108,7 +108,7 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
 		   struct scm_cookie *scm, int flags);
 
 int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
-		    bool notrunc);
+		    bool notrunc, bool sealed);
 
 #endif /* __LINUX_NET_SCM_H */
 
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 56fed7ab27ab..14bf06367ef1 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -156,6 +156,8 @@
 
 #define SO_PEERPIDFD_THREAD	87
 
+#define SO_RIGHTS_SEALED_SUPPORTED	88
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
diff --git a/io_uring/net.c b/io_uring/net.c
index eeab88e77fd8..b579d542b7e2 100644
--- a/io_uring/net.c
+++ b/io_uring/net.c
@@ -992,7 +992,7 @@ static int io_recvmsg_multishot(struct socket *sock, struct io_sr_msg *io,
 
 	if (kmsg->namelen)
 		kmsg->msg.msg_name = &hdr.addr;
-	kmsg->msg.msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);
+	kmsg->msg.msg_flags = flags & (MSG_CMSG_CLOEXEC | MSG_CMSG_SEALED | MSG_CMSG_COMPAT);
 	kmsg->msg.msg_namelen = 0;
 
 	if (sock->file->f_flags & O_NONBLOCK)
diff --git a/net/compat.c b/net/compat.c
index 6bdf4a2c9077..29816b47f675 100644
--- a/net/compat.c
+++ b/net/compat.c
@@ -296,7 +296,8 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notr
 	int err = 0, i;
 
 	for (i = 0; i < fdmax; i++) {
-		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
+		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc,
+				      msg->msg_flags & MSG_CMSG_SEALED);
 		if (err < 0)
 			break;
 	}
diff --git a/net/core/scm.c b/net/core/scm.c
index ae38f3c1165c..08822b2a1f22 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -29,6 +29,7 @@
 #include <linux/slab.h>
 #include <linux/errqueue.h>
 #include <linux/io_uring.h>
+#include <linux/anon_inodes.h>
 
 #include <linux/uaccess.h>
 
@@ -66,7 +67,19 @@ static __inline__ int scm_check_creds(struct ucred *creds)
 	return -EPERM;
 }
 
-static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
+static int envelope_release(struct inode *inode, struct file *file)
+{
+	struct file *inner = file->private_data;
+
+	fput(inner);
+	return 0;
+}
+
+static const struct file_operations envelope_ops = {
+	.release = envelope_release,
+};
+
+static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp, bool sealed)
 {
 	int *fdp = (int*)CMSG_DATA(cmsg);
 	struct scm_fp_list *fpl = *fplp;
@@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
 
 		if (fd < 0 || !(file = fget_raw(fd)))
 			return -EBADF;
-		/* don't allow io_uring files */
-		if (io_is_uring_fops(file)) {
-			fput(file);
-			return -EINVAL;
+
+		if (sealed) {
+			/* Only allow sealed files */
+			if (file->f_op != &envelope_ops)
+				return -EINVAL;
+
+			file = (struct file *)file->private_data;
+
+			/*
+			 * No need to check for io_uring files here as the
+			 * underlying file had already successfully passed the
+			 * check when it was first sent.
+			 */
+		} else {
+			/* Don't allow sealed files */
+			if (file->f_op == &envelope_ops)
+				return -EINVAL;
+
+			/* don't allow io_uring files */
+			if (io_is_uring_fops(file)) {
+				fput(file);
+				return -EINVAL;
+			}
 		}
 		if (unix_get_socket(file))
 			fpl->count_unix++;
@@ -203,7 +235,8 @@ int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
 		case SCM_RIGHTS:
 			if (!ops || ops->family != PF_UNIX)
 				goto error;
-			err=scm_fp_copy(cmsg, &p->fp);
+			err = scm_fp_copy(cmsg, &p->fp,
+					  msg->msg_flags & MSG_CMSG_SEALED);
 			if (err<0)
 				goto error;
 			break;
@@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
 }
 
 int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
-		    bool notrunc)
+		    bool notrunc, bool sealed)
 {
+	struct file *f_received;
 	int error;
 
 	if (!ufd)
 		return -EFAULT;
 
-	error = security_file_receive(f);
-	if (error)
-		return notrunc ? put_user(error, ufd) : error;
+	if (sealed) {
+		/*
+		 * Receive fd sealed in an "envelope" anonymous fd.
+		 * This will install a special file that can be unsealed
+		 * upon sending to another process, otherwise the underlying
+		 * file will not be accessible to this process, avoiding the
+		 * need to pass the LSM check below.
+		 */
+		f_received = anon_inode_getfile("[envelope]", &envelope_ops, f,
+						O_RDONLY);
+		if (IS_ERR(f_received))
+			return PTR_ERR(f_received);
+
+		get_file(f);
+	} else {
+		error = security_file_receive(f);
+		if (error)
+			return notrunc ? put_user(error, ufd) : error;
 
-	FD_PREPARE(fdf, flags, get_file(f));
+		f_received = get_file(f);
+	}
+
+	FD_PREPARE(fdf, flags, f_received);
 	if (fdf->fd < 0)
 		return fdf->fd;
 
@@ -384,7 +436,9 @@ int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
 	if (error)
 		return error;
 
-	__receive_sock(fdf->file);
+	if (!sealed)
+		__receive_sock(fdf->file);
+
 	return fd_publish(fdf);
 }
 
@@ -407,7 +461,8 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)
 	}
 
 	for (i = 0; i < fdmax; i++) {
-		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
+		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc,
+				      msg->msg_flags & MSG_CMSG_SEALED);
 		if (err < 0)
 			break;
 	}
diff --git a/net/core/sock.c b/net/core/sock.c
index e7405be8941f..68a447d8bf52 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1959,6 +1959,15 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
 		v.val = sk->sk_scm_rights;
 		break;
 
+	case SO_RIGHTS_SEALED_SUPPORTED:
+		if (!sk_is_unix(sk))
+			return -EOPNOTSUPP;
+
+		/* Just succeed without returning anything, signaling
+		 * support.
+		 */
+		break;
+
 	case SO_PEERCRED:
 	{
 		struct ucred peercred;
diff --git a/net/socket.c b/net/socket.c
index 2e3b5737c481..dbb3c7c6ff66 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -2921,7 +2921,7 @@ static int ____sys_recvmsg(struct socket *sock, struct msghdr *msg_sys,
 
 	msg_sys->msg_name = &addr;
 	cmsg_ptr = (unsigned long)msg_sys->msg_control;
-	msg_sys->msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);
+	msg_sys->msg_flags = flags & (MSG_CMSG_CLOEXEC | MSG_CMSG_SEALED | MSG_CMSG_COMPAT);
 
 	/* We assume all kernel code knows the size of sockaddr_storage */
 	msg_sys->msg_namelen = 0;
diff --git a/tools/include/uapi/asm-generic/socket.h b/tools/include/uapi/asm-generic/socket.h
index f333a0ac4ee4..fb9e98a7b370 100644
--- a/tools/include/uapi/asm-generic/socket.h
+++ b/tools/include/uapi/asm-generic/socket.h
@@ -147,6 +147,8 @@
 
 #define SO_PASSRIGHTS		83
 
+#define SO_RIGHTS_SEALED_SUPPORTED	88
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
-- 
2.54.0


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

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 13:05 [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED Ondrej Mosnáček
@ 2026-11-02 13:13 ` Ondrej Mosnáček
  2026-11-02 15:14 ` Stephen Smalley
  1 sibling, 0 replies; 5+ messages in thread
From: Ondrej Mosnáček @ 2026-11-02 13:13 UTC (permalink / raw)
  To: Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn,
	Jakub Kicinski
  Cc: Paul Moore, Stephen Smalley, James Morris, Serge E. Hallyn,
	Simon Horman, Jens Axboe, netdev, selinux, linux-security-module,
	io-uring, linux-fsdevel, linux-kernel

On Mon, 2 Nov 2026 at 14:05, Ondrej Mosnáček <omosnacek@gmail.com> wrote:
>
> Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
> mechanism, the security_file_receive() LSM hook is calledto allow LSMs
> like SELinux to validate the receiver's access to the received file
> (similarly to a process inheriting file descriptors through execve(2)).
>
> However, there are cases (such as dbus daemon/dbus-broker) where the
> receiver doesn't intend to access the received file and merely wants to
> forward it to a third process. In this case the forwarding process would
> need to have the appropriate r/w access to the file in order to receive
> it, even though it will never actually acess it. That leads to the
> security policy having to grant the forwarder access to a potentially
> wide range of files (in case of dbus-broker basically all files), which
> is not ideal.
>
> This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
> recvmsg(2), wraps any received files into an opaque anonymous inode with
> the name "[envelope]", skipping the security_file_receive() LSM check.
> The returned "[envelope]" file descriptor can't be used in any
> meaningful way other than being passed forward via sendmsg(2) with the
> MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
> sent via SCM_RIGHTS to the receiver. The receiver can then either
> receive the file as normal (subject to the LSM check) or again "sealed"
> in the "[envelope]" anon inode for further forwarding.
>
> Note that sending the "[envelope]" file is only allowed with the
> MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
> unexpected behavior. Attempting to send it without this flag will fail
> with an error. This also avoids recursive nesting of "[envelope]" files,
> which would only make this feature harder to audit for possible issues.
>
> This patch also adds a simple read-only sockopt for UNIX sockets that
> allows userspace to determine if this feature is present (passing
> unknown CMSG flags unfortunately doesn't lead to an error currently).
> The minimal code to do so can look like this:
>
>     #ifndef SO_RIGHTS_SEALED_SUPPORTED
>     #define SO_RIGHTS_SEALED_SUPPORTED 88
>     #endif
>
>     #include <sys/socket.h>
>     #include <unistd.h>
>     #include <stdbool.h>
>
>     bool MSG_CMSG_SEALED_supported(void)
>     {
>         int sock, ret;
>         socklen_t optlen = 0;
>
>         sock = socket(AF_UNIX, SOCK_STREAM, 0);
>         if (sock < 0) {
>             /* handle error */
>             /* ... */
>         }
>
>         ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
>                          NULL, &optlen);
>         close(sock);
>
>         return ret == 0;
>     }
>
> Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
> Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
> Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
> ---
>  arch/alpha/include/uapi/asm/socket.h    |  2 +
>  arch/mips/include/uapi/asm/socket.h     |  2 +
>  arch/parisc/include/uapi/asm/socket.h   |  2 +
>  arch/sparc/include/uapi/asm/socket.h    |  2 +
>  include/linux/socket.h                  |  4 +-
>  include/net/scm.h                       |  2 +-
>  include/uapi/asm-generic/socket.h       |  2 +
>  io_uring/net.c                          |  2 +-
>  net/compat.c                            |  3 +-
>  net/core/scm.c                          | 81 +++++++++++++++++++++----
>  net/core/sock.c                         |  9 +++
>  net/socket.c                            |  2 +-
>  tools/include/uapi/asm-generic/socket.h |  2 +
>  13 files changed, 97 insertions(+), 18 deletions(-)

See also this RFC patch for selinux-testsuite
(https://github.com/SELinuxProject/selinux-testsuite) that I used to
test this patch:
https://lore.kernel.org/selinux/20261102130834.253605-1-omosnacek@gmail.com/

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

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 13:05 [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED Ondrej Mosnáček
  2026-11-02 13:13 ` Ondrej Mosnáček
@ 2026-11-02 15:14 ` Stephen Smalley
  2026-11-02 19:06   ` Stephen Smalley
  2026-11-02 19:39   ` Paul Moore
  1 sibling, 2 replies; 5+ messages in thread
From: Stephen Smalley @ 2026-11-02 15:14 UTC (permalink / raw)
  To: Ondrej Mosnáček
  Cc: Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn,
	Jakub Kicinski, Paul Moore, James Morris, Serge E. Hallyn,
	Simon Horman, Jens Axboe, netdev, selinux, linux-security-module,
	io-uring, linux-fsdevel, linux-kernel

On Mon, Nov 2, 2026 at 8:05 AM Ondrej Mosnáček <omosnacek@gmail.com> wrote:
>
> Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
> mechanism, the security_file_receive() LSM hook is calledto allow LSMs
> like SELinux to validate the receiver's access to the received file
> (similarly to a process inheriting file descriptors through execve(2)).
>
> However, there are cases (such as dbus daemon/dbus-broker) where the
> receiver doesn't intend to access the received file and merely wants to
> forward it to a third process. In this case the forwarding process would
> need to have the appropriate r/w access to the file in order to receive
> it, even though it will never actually acess it. That leads to the
> security policy having to grant the forwarder access to a potentially
> wide range of files (in case of dbus-broker basically all files), which
> is not ideal.
>
> This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
> recvmsg(2), wraps any received files into an opaque anonymous inode with
> the name "[envelope]", skipping the security_file_receive() LSM check.
> The returned "[envelope]" file descriptor can't be used in any
> meaningful way other than being passed forward via sendmsg(2) with the
> MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
> sent via SCM_RIGHTS to the receiver. The receiver can then either
> receive the file as normal (subject to the LSM check) or again "sealed"
> in the "[envelope]" anon inode for further forwarding.
>
> Note that sending the "[envelope]" file is only allowed with the
> MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
> unexpected behavior. Attempting to send it without this flag will fail
> with an error. This also avoids recursive nesting of "[envelope]" files,
> which would only make this feature harder to audit for possible issues.
>
> This patch also adds a simple read-only sockopt for UNIX sockets that
> allows userspace to determine if this feature is present (passing
> unknown CMSG flags unfortunately doesn't lead to an error currently).
> The minimal code to do so can look like this:
>
>     #ifndef SO_RIGHTS_SEALED_SUPPORTED
>     #define SO_RIGHTS_SEALED_SUPPORTED 88
>     #endif
>
>     #include <sys/socket.h>
>     #include <unistd.h>
>     #include <stdbool.h>
>
>     bool MSG_CMSG_SEALED_supported(void)
>     {
>         int sock, ret;
>         socklen_t optlen = 0;
>
>         sock = socket(AF_UNIX, SOCK_STREAM, 0);
>         if (sock < 0) {
>             /* handle error */
>             /* ... */
>         }
>
>         ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
>                          NULL, &optlen);
>         close(sock);
>
>         return ret == 0;
>     }
>
> Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
> Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
> Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
> ---

> diff --git a/net/core/scm.c b/net/core/scm.c
> index ae38f3c1165c..08822b2a1f22 100644
> --- a/net/core/scm.c
> +++ b/net/core/scm.c
> @@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
>
>                 if (fd < 0 || !(file = fget_raw(fd)))
>                         return -EBADF;
> -               /* don't allow io_uring files */
> -               if (io_is_uring_fops(file)) {
> -                       fput(file);
> -                       return -EINVAL;
> +
> +               if (sealed) {
> +                       /* Only allow sealed files */
> +                       if (file->f_op != &envelope_ops)
> +                               return -EINVAL;

This leaks a file reference.

> +
> +                       file = (struct file *)file->private_data;

This fails to take a reference to the underlying file but will later get fput().

> +
> +                       /*
> +                        * No need to check for io_uring files here as the
> +                        * underlying file had already successfully passed the
> +                        * check when it was first sent.
> +                        */
> +               } else {
> +                       /* Don't allow sealed files */
> +                       if (file->f_op == &envelope_ops)
> +                               return -EINVAL;

This also leaks a file reference.

> @@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
>  }
>
>  int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> -                   bool notrunc)
> +                   bool notrunc, bool sealed)
>  {
> +       struct file *f_received;
>         int error;
>
>         if (!ufd)
>                 return -EFAULT;
>
> -       error = security_file_receive(f);
> -       if (error)
> -               return notrunc ? put_user(error, ufd) : error;

Design point: I would suggest adding and using a new LSM hook that
allows security modules (like SELinux) to still be able to check a
(new) permission that governs whether a process can pass along these
sealed fds so that we do not entirely lose control over their use.

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

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 15:14 ` Stephen Smalley
@ 2026-11-02 19:06   ` Stephen Smalley
  2026-11-02 19:39   ` Paul Moore
  1 sibling, 0 replies; 5+ messages in thread
From: Stephen Smalley @ 2026-11-02 19:06 UTC (permalink / raw)
  To: Ondrej Mosnáček
  Cc: Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn,
	Jakub Kicinski, Paul Moore, James Morris, Serge E. Hallyn,
	Simon Horman, Jens Axboe, netdev, selinux, linux-security-module,
	io-uring, linux-fsdevel, linux-kernel

On Mon, Nov 2, 2026 at 10:14 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Mon, Nov 2, 2026 at 8:05 AM Ondrej Mosnáček <omosnacek@gmail.com> wrote:
> >
> > Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
> > mechanism, the security_file_receive() LSM hook is calledto allow LSMs
> > like SELinux to validate the receiver's access to the received file
> > (similarly to a process inheriting file descriptors through execve(2)).
> >
> > However, there are cases (such as dbus daemon/dbus-broker) where the
> > receiver doesn't intend to access the received file and merely wants to
> > forward it to a third process. In this case the forwarding process would
> > need to have the appropriate r/w access to the file in order to receive
> > it, even though it will never actually acess it. That leads to the
> > security policy having to grant the forwarder access to a potentially
> > wide range of files (in case of dbus-broker basically all files), which
> > is not ideal.
> >
> > This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
> > recvmsg(2), wraps any received files into an opaque anonymous inode with
> > the name "[envelope]", skipping the security_file_receive() LSM check.
> > The returned "[envelope]" file descriptor can't be used in any
> > meaningful way other than being passed forward via sendmsg(2) with the
> > MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
> > sent via SCM_RIGHTS to the receiver. The receiver can then either
> > receive the file as normal (subject to the LSM check) or again "sealed"
> > in the "[envelope]" anon inode for further forwarding.
> >
> > Note that sending the "[envelope]" file is only allowed with the
> > MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
> > unexpected behavior. Attempting to send it without this flag will fail
> > with an error. This also avoids recursive nesting of "[envelope]" files,
> > which would only make this feature harder to audit for possible issues.
> >
> > This patch also adds a simple read-only sockopt for UNIX sockets that
> > allows userspace to determine if this feature is present (passing
> > unknown CMSG flags unfortunately doesn't lead to an error currently).
> > The minimal code to do so can look like this:
> >
> >     #ifndef SO_RIGHTS_SEALED_SUPPORTED
> >     #define SO_RIGHTS_SEALED_SUPPORTED 88
> >     #endif
> >
> >     #include <sys/socket.h>
> >     #include <unistd.h>
> >     #include <stdbool.h>
> >
> >     bool MSG_CMSG_SEALED_supported(void)
> >     {
> >         int sock, ret;
> >         socklen_t optlen = 0;
> >
> >         sock = socket(AF_UNIX, SOCK_STREAM, 0);
> >         if (sock < 0) {
> >             /* handle error */
> >             /* ... */
> >         }
> >
> >         ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
> >                          NULL, &optlen);
> >         close(sock);
> >
> >         return ret == 0;
> >     }
> >
> > Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
> > Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
> > Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
> > ---
>
> > diff --git a/net/core/scm.c b/net/core/scm.c
> > index ae38f3c1165c..08822b2a1f22 100644
> > --- a/net/core/scm.c
> > +++ b/net/core/scm.c
> > @@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
> >
> >                 if (fd < 0 || !(file = fget_raw(fd)))
> >                         return -EBADF;
> > -               /* don't allow io_uring files */
> > -               if (io_is_uring_fops(file)) {
> > -                       fput(file);
> > -                       return -EINVAL;
> > +
> > +               if (sealed) {
> > +                       /* Only allow sealed files */
> > +                       if (file->f_op != &envelope_ops)
> > +                               return -EINVAL;
>
> This leaks a file reference.
>
> > +
> > +                       file = (struct file *)file->private_data;
>
> This fails to take a reference to the underlying file but will later get fput().
>
> > +
> > +                       /*
> > +                        * No need to check for io_uring files here as the
> > +                        * underlying file had already successfully passed the
> > +                        * check when it was first sent.
> > +                        */
> > +               } else {
> > +                       /* Don't allow sealed files */
> > +                       if (file->f_op == &envelope_ops)
> > +                               return -EINVAL;
>
> This also leaks a file reference.
>
> > @@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
> >  }
> >
> >  int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> > -                   bool notrunc)
> > +                   bool notrunc, bool sealed)
> >  {
> > +       struct file *f_received;
> >         int error;
> >
> >         if (!ufd)
> >                 return -EFAULT;
> >
> > -       error = security_file_receive(f);
> > -       if (error)
> > -               return notrunc ? put_user(error, ufd) : error;
>
> Design point: I would suggest adding and using a new LSM hook that
> allows security modules (like SELinux) to still be able to check a
> (new) permission that governs whether a process can pass along these
> sealed fds so that we do not entirely lose control over their use.

One other design point: you are creating the envelope with
anon_inode_getfile(); consider using anon_inode_getfile_secure()
instead so that security_inode_init_security_anon() can give the
envelope a labelable context, which SELinux could then use to label
and control envelope use directly.

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

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 15:14 ` Stephen Smalley
  2026-11-02 19:06   ` Stephen Smalley
@ 2026-11-02 19:39   ` Paul Moore
  1 sibling, 0 replies; 5+ messages in thread
From: Paul Moore @ 2026-11-02 19:39 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: Ondrej Mosnáček, Eric Dumazet, Kuniyuki Iwashima,
	Paolo Abeni, Willem de Bruijn, Jakub Kicinski, James Morris,
	Serge E. Hallyn, Simon Horman, Jens Axboe, netdev, selinux,
	linux-security-module, io-uring, linux-fsdevel, linux-kernel

On Mon, Nov 2, 2026 at 10:14 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Mon, Nov 2, 2026 at 8:05 AM Ondrej Mosnáček <omosnacek@gmail.com> wrote:
> >
> > Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
> > mechanism, the security_file_receive() LSM hook is calledto allow LSMs
> > like SELinux to validate the receiver's access to the received file
> > (similarly to a process inheriting file descriptors through execve(2)).
> >
> > However, there are cases (such as dbus daemon/dbus-broker) where the
> > receiver doesn't intend to access the received file and merely wants to
> > forward it to a third process. In this case the forwarding process would
> > need to have the appropriate r/w access to the file in order to receive
> > it, even though it will never actually acess it. That leads to the
> > security policy having to grant the forwarder access to a potentially
> > wide range of files (in case of dbus-broker basically all files), which
> > is not ideal.
> >
> > This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
> > recvmsg(2), wraps any received files into an opaque anonymous inode with
> > the name "[envelope]", skipping the security_file_receive() LSM check.
> > The returned "[envelope]" file descriptor can't be used in any
> > meaningful way other than being passed forward via sendmsg(2) with the
> > MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
> > sent via SCM_RIGHTS to the receiver. The receiver can then either
> > receive the file as normal (subject to the LSM check) or again "sealed"
> > in the "[envelope]" anon inode for further forwarding.
> >
> > Note that sending the "[envelope]" file is only allowed with the
> > MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
> > unexpected behavior. Attempting to send it without this flag will fail
> > with an error. This also avoids recursive nesting of "[envelope]" files,
> > which would only make this feature harder to audit for possible issues.
> >
> > This patch also adds a simple read-only sockopt for UNIX sockets that
> > allows userspace to determine if this feature is present (passing
> > unknown CMSG flags unfortunately doesn't lead to an error currently).
> > The minimal code to do so can look like this:
> >
> >     #ifndef SO_RIGHTS_SEALED_SUPPORTED
> >     #define SO_RIGHTS_SEALED_SUPPORTED 88
> >     #endif
> >
> >     #include <sys/socket.h>
> >     #include <unistd.h>
> >     #include <stdbool.h>
> >
> >     bool MSG_CMSG_SEALED_supported(void)
> >     {
> >         int sock, ret;
> >         socklen_t optlen = 0;
> >
> >         sock = socket(AF_UNIX, SOCK_STREAM, 0);
> >         if (sock < 0) {
> >             /* handle error */
> >             /* ... */
> >         }
> >
> >         ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
> >                          NULL, &optlen);
> >         close(sock);
> >
> >         return ret == 0;
> >     }
> >
> > Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
> > Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
> > Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
> > ---
>
> > diff --git a/net/core/scm.c b/net/core/scm.c
> > index ae38f3c1165c..08822b2a1f22 100644
> > --- a/net/core/scm.c
> > +++ b/net/core/scm.c
> > @@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
> >
> >                 if (fd < 0 || !(file = fget_raw(fd)))
> >                         return -EBADF;
> > -               /* don't allow io_uring files */
> > -               if (io_is_uring_fops(file)) {
> > -                       fput(file);
> > -                       return -EINVAL;
> > +
> > +               if (sealed) {
> > +                       /* Only allow sealed files */
> > +                       if (file->f_op != &envelope_ops)
> > +                               return -EINVAL;
>
> This leaks a file reference.
>
> > +
> > +                       file = (struct file *)file->private_data;
>
> This fails to take a reference to the underlying file but will later get fput().
>
> > +
> > +                       /*
> > +                        * No need to check for io_uring files here as the
> > +                        * underlying file had already successfully passed the
> > +                        * check when it was first sent.
> > +                        */
> > +               } else {
> > +                       /* Don't allow sealed files */
> > +                       if (file->f_op == &envelope_ops)
> > +                               return -EINVAL;
>
> This also leaks a file reference.
>
> > @@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
> >  }
> >
> >  int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> > -                   bool notrunc)
> > +                   bool notrunc, bool sealed)
> >  {
> > +       struct file *f_received;
> >         int error;
> >
> >         if (!ufd)
> >                 return -EFAULT;
> >
> > -       error = security_file_receive(f);
> > -       if (error)
> > -               return notrunc ? put_user(error, ufd) : error;
>
> Design point: I would suggest adding and using a new LSM hook that
> allows security modules (like SELinux) to still be able to check a
> (new) permission that governs whether a process can pass along these
> sealed fds so that we do not entirely lose control over their use.

I've only given this a quick look, but I'm also not excited about the
LSM hook being conditional on the "sealed" flag.  I'd prefer for the
security_file_receive() hook to catch all the files, but take an
additional bool argument indicating the sealed state; this would allow
individual LSMs to decide how best to handle this.

  security_file_receive(struct file *file, bool sealed)

-- 
paul-moore.com

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

end of thread, other threads:[~2026-11-02 19:39 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02 13:05 [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED Ondrej Mosnáček
2026-11-02 13:13 ` Ondrej Mosnáček
2026-11-02 15:14 ` Stephen Smalley
2026-11-02 19:06   ` Stephen Smalley
2026-11-02 19:39   ` Paul Moore

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®