Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED

From: Stephen Smalley

Date: Mon Nov 02 2026 - 11:23:08 EST


On Mon, Nov 2, 2026 at 8:05 AM Ondrej Mosnáček <omosnacek@xxxxxxxxx> 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@xxxxxxxxx>
> ---

> 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.