Re: [RFC PATCH 1/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS

From: Kuniyuki Iwashima

Date: Wed Apr 29 2026 - 22:04:59 EST


On Tue, Apr 28, 2026 at 10:51 AM Jori Koolstra <jkoolstra@xxxxxxxxx> wrote:
>
> Right now if some LSM such as Smack denies an AF_UNIX socket peer to
> receive an SCM_RIGHTS fd the SCM_RIGHTS fd array will be cut short at
> that point, and MSG_CTRUNC is set on return of recvmsg(). This is
> highly problematic behaviour, because it leaves the receiver
> wondering what happened. As per man page MSG_CTRUNC is supposed to
> indicate that the control buffer was sized too short, but suddenly
> a permission error might result in the exact same flag being set.
> Moreover, the receiver has no chance to determine how many fds got
> originally sent and how many were suppressed.[1]
>
> Add two MSG_* flags:

Since we only have 5 bits remaining for future extension,
we need to consider the use case a bit more carefully.


> - MSG_RIGHTS_DENIAL is set whenever any file is rejected by the LSM
> during recvmsg() of SCM_RIGHTS fds.

Is this really needed ?

Even if the fd array is truncated, the application will traverse
the array anyway since it has some fds already installed (to
clean up in case of MSG_CTRUNC ?).

Then, it will find the -EPERM entry.

I assume no one uses MSG_RIGHTS_DENIAL without
MSG_RIGHTS_FILTER.


> - If MSG_RIGHTS_FILTER is passed as a flag to recvmsg(), the SCM_RIGHTS

Does this flag need per-recvmsg() granularity ?

If the application does not welcome the truncated fd array,
it would have passed MSG_RIGHTS_FILTER to every
recvmsg(), no ?

( and I feel _FILTER sounds like "please do filtering (truncase)".
Maybe _NOTRUNC ? )


> fd array is always passed in its full original size. However, any
> files rejected by the LSM are replaced in this array with -EPERM
> instead of an assigned fd, while keeping the original order. If the
> flag is not set, the original truncate behavior is used.
>
> [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
>
> Signed-off-by: Jori Koolstra <jkoolstra@xxxxxxxxx>
> ---
> fs/file.c | 21 ++++++++++++++++++---
> include/linux/file.h | 4 +++-
> include/linux/socket.h | 3 +++
> include/net/scm.h | 8 ++++----
> io_uring/openclose.c | 2 +-
> kernel/pid.c | 2 +-
> kernel/seccomp.c | 2 +-
> net/compat.c | 7 ++++---
> net/core/scm.c | 11 ++++++-----
> 9 files changed, 41 insertions(+), 19 deletions(-)
>
> diff --git a/fs/file.c b/fs/file.c
> index 2c81c0b162d0..cc33a1e77049 100644
> --- a/fs/file.c
> +++ b/fs/file.c
> @@ -1370,10 +1370,11 @@ int replace_fd(unsigned fd, struct file *file, unsigned flags)
> }
>
> /**
> - * receive_fd() - Install received file into file descriptor table
> + * receive_fd_msg() - Install received file into file descriptor table
> * @file: struct file that was received from another process
> * @ufd: __user pointer to write new fd number to
> * @o_flags: the O_* flags to apply to the new fd entry
> + * @msg_flags: the MSG_* flags to set for recvmsg(2)
> *
> * Installs a received file into the file descriptor table, with appropriate
> * checks and count updates. Optionally writes the fd number to userspace, if
> @@ -1384,13 +1385,21 @@ int replace_fd(unsigned fd, struct file *file, unsigned flags)
> *
> * Returns newly install fd or -ve on error.
> */
> -int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags)
> +int receive_fd_msg(struct file *file, int __user *ufd, unsigned int o_flags,
> + unsigned int *msg_flags)
> {
> int error;
>
> error = security_file_receive(file);
> - if (error)
> + if (error) {
> + if (msg_flags)
> + *msg_flags |= MSG_RIGHTS_DENIAL;
> +
> + if (ufd)
> + put_user(-EPERM, ufd);
> +
> return error;
> + }
>
> FD_PREPARE(fdf, o_flags, file);
> if (fdf.err)
> @@ -1406,6 +1415,12 @@ int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags)
> __receive_sock(fd_prepare_file(fdf));
> return fd_publish(fdf);
> }
> +EXPORT_SYMBOL_GPL(receive_fd_msg);
> +
> +int receive_fd(struct file *file, unsigned int o_flags)
> +{
> + return receive_fd_msg(file, NULL, o_flags, NULL);
> +}
> EXPORT_SYMBOL_GPL(receive_fd);
>
> int receive_fd_replace(int new_fd, struct file *file, unsigned int o_flags)
> diff --git a/include/linux/file.h b/include/linux/file.h
> index 27484b444d31..38f022d997a6 100644
> --- a/include/linux/file.h
> +++ b/include/linux/file.h
> @@ -118,7 +118,9 @@ DEFINE_FREE(fput, struct file *, if (!IS_ERR_OR_NULL(_T)) fput(_T))
>
> extern void fd_install(unsigned int fd, struct file *file);
>
> -int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags);
> +int receive_fd_msg(struct file *file, int __user *ufd, unsigned int o_flags,
> + unsigned int *msg_flags);
> +int receive_fd(struct file *file, unsigned int o_flags);
>
> int receive_fd_replace(int new_fd, struct file *file, unsigned int o_flags);
>
> diff --git a/include/linux/socket.h b/include/linux/socket.h
> index ec4a0a025793..3809a8add2fc 100644
> --- a/include/linux/socket.h
> +++ b/include/linux/socket.h
> @@ -342,6 +342,9 @@ struct ucred {
> * plain text and require encryption
> */
>
> +#define MSG_RIGHTS_DENIAL 0x200000
> +#define MSG_RIGHTS_FILTER 0x400000
> +
> #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() */
> diff --git a/include/net/scm.h b/include/net/scm.h
> index c52519669349..983efa952c8e 100644
> --- a/include/net/scm.h
> +++ b/include/net/scm.h
> @@ -50,8 +50,8 @@ struct scm_cookie {
> #endif
> };
>
> -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm);
> -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm);
> +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, int recv_flags);
> +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, int recv_flags);
> int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm);
> void __scm_destroy(struct scm_cookie *scm);
> struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl);
> @@ -108,11 +108,11 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
> struct scm_cookie *scm, int flags);
>
> static inline int scm_recv_one_fd(struct file *f, int __user *ufd,
> - unsigned int flags)
> + unsigned int o_flags, unsigned int *msg_flags)
> {
> if (!ufd)
> return -EFAULT;
> - return receive_fd(f, ufd, flags);
> + return receive_fd_msg(f, ufd, o_flags, msg_flags);
> }
>
> #endif /* __LINUX_NET_SCM_H */
> diff --git a/io_uring/openclose.c b/io_uring/openclose.c
> index c71242915dad..1b6cb05b0e3d 100644
> --- a/io_uring/openclose.c
> +++ b/io_uring/openclose.c
> @@ -308,7 +308,7 @@ int io_install_fixed_fd(struct io_kiocb *req, unsigned int issue_flags)
> int ret;
>
> ifi = io_kiocb_to_cmd(req, struct io_fixed_install);
> - ret = receive_fd(req->file, NULL, ifi->o_flags);
> + ret = receive_fd(req->file, ifi->o_flags);
> if (ret < 0)
> req_set_fail(req);
> io_req_set_res(req, ret, 0);
> diff --git a/kernel/pid.c b/kernel/pid.c
> index fd5c2d4aa349..62af6874192d 100644
> --- a/kernel/pid.c
> +++ b/kernel/pid.c
> @@ -929,7 +929,7 @@ static int pidfd_getfd(struct pid *pid, int fd)
> if (IS_ERR(file))
> return PTR_ERR(file);
>
> - ret = receive_fd(file, NULL, O_CLOEXEC);
> + ret = receive_fd(file, O_CLOEXEC);
> fput(file);
>
> return ret;
> diff --git a/kernel/seccomp.c b/kernel/seccomp.c
> index 066909393c38..ad5ab16fe2b1 100644
> --- a/kernel/seccomp.c
> +++ b/kernel/seccomp.c
> @@ -1130,7 +1130,7 @@ static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd, struct seccomp_kn
> */
> list_del_init(&addfd->list);
> if (!addfd->setfd)
> - fd = receive_fd(addfd->file, NULL, addfd->flags);
> + fd = receive_fd(addfd->file, addfd->flags);
> else
> fd = receive_fd_replace(addfd->fd, addfd->file, addfd->flags);
> addfd->ret = fd;
> diff --git a/net/compat.c b/net/compat.c
> index 2c9bd0edac99..056bce0927c4 100644
> --- a/net/compat.c
> +++ b/net/compat.c
> @@ -287,18 +287,19 @@ static int scm_max_fds_compat(struct msghdr *msg)
> return (msg->msg_controllen - sizeof(struct compat_cmsghdr)) / sizeof(int);
> }
>
> -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)
> +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, int recv_flags)
> {
> struct compat_cmsghdr __user *cm =
> (struct compat_cmsghdr __user *)msg->msg_control_user;
> unsigned int o_flags = (msg->msg_flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0;
> + bool filter_rights = recv_flags & MSG_RIGHTS_FILTER;
> int fdmax = min_t(int, scm_max_fds_compat(msg), scm->fp->count);
> int __user *cmsg_data = CMSG_COMPAT_DATA(cm);
> int err = 0, i;
>
> for (i = 0; i < fdmax; i++) {
> - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
> - if (err < 0)
> + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, &msg->msg_flags);
> + if (err < 0 && !filter_rights)
> break;
> }
>
> diff --git a/net/core/scm.c b/net/core/scm.c
> index eec13f50ecaf..035329645d8f 100644
> --- a/net/core/scm.c
> +++ b/net/core/scm.c
> @@ -351,10 +351,11 @@ static int scm_max_fds(struct msghdr *msg)
> return (msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int);
> }
>
> -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
> +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, int recv_flags)
> {
> struct cmsghdr __user *cm =
> (__force struct cmsghdr __user *)msg->msg_control_user;
> + bool filter_rights = recv_flags & MSG_RIGHTS_FILTER;
> unsigned int o_flags = (msg->msg_flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0;
> int fdmax = min_t(int, scm_max_fds(msg), scm->fp->count);
> int __user *cmsg_data = CMSG_USER_DATA(cm);
> @@ -365,13 +366,13 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
> return;
>
> if (msg->msg_flags & MSG_CMSG_COMPAT) {
> - scm_detach_fds_compat(msg, scm);
> + scm_detach_fds_compat(msg, scm, recv_flags);
> return;
> }
>
> for (i = 0; i < fdmax; i++) {
> - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
> - if (err < 0)
> + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, &msg->msg_flags);
> + if (err < 0 && !filter_rights)
> break;
> }
>
> @@ -524,7 +525,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
> scm_passec(sk, msg, scm);
>
> if (scm->fp)
> - scm_detach_fds(msg, scm);
> + scm_detach_fds(msg, scm, flags);
>
> return true;
> }
> --
> 2.54.0
>