* [PATCH net-next v3 1/5] net: scm: move scm_detach_fds() from common path to scm_recv_unix()
2026-06-29 19:43 [PATCH net-next v3 0/5] af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
@ 2026-06-29 19:43 ` Jori Koolstra
2026-06-29 19:43 ` [PATCH net-next v3 2/5] vfs: add function receive_fd_filtered() that makes LSM filtering explicit Jori Koolstra
` (3 subsequent siblings)
4 siblings, 0 replies; 16+ messages in thread
From: Jori Koolstra @ 2026-06-29 19:43 UTC (permalink / raw)
To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra
scm->fp can only be set when using UNIX sockets, therefore we should
move it out of the common path __scm_recv_common() into
scm_recv_unix().
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
net/core/scm.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/net/core/scm.c b/net/core/scm.c
index eec13f50ecaf..a73b1eb30fd2 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -523,9 +523,6 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
scm_passec(sk, msg, scm);
- if (scm->fp)
- scm_detach_fds(msg, scm);
-
return true;
}
@@ -545,6 +542,9 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
if (!__scm_recv_common(sock->sk, msg, scm, flags))
return;
+ if (scm->fp)
+ scm_detach_fds(msg, scm);
+
if (sock->sk->sk_scm_pidfd)
scm_pidfd_recv(msg, scm);
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH net-next v3 2/5] vfs: add function receive_fd_filtered() that makes LSM filtering explicit
2026-06-29 19:43 [PATCH net-next v3 0/5] af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
2026-06-29 19:43 ` [PATCH net-next v3 1/5] net: scm: move scm_detach_fds() from common path to scm_recv_unix() Jori Koolstra
@ 2026-06-29 19:43 ` Jori Koolstra
2026-06-29 19:43 ` [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
` (2 subsequent siblings)
4 siblings, 0 replies; 16+ messages in thread
From: Jori Koolstra @ 2026-06-29 19:43 UTC (permalink / raw)
To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra
To prepare for filtering LSM blocked fds received with SCM_RIGHTS, we
first need to know when a received fd was filtered. Currently,
receive_fd() relays the error returned by security_file_receive(). As
there is no strict convention about what errnos this LSM hook can
return, the caller of receive_fd() has no robust way of knowing whether
an error is returned because the LSM blocked the fd, or because of some
other failure (put_user, FD_PREPARE, etc.)
Fix this by adding receive_fd_filtered() which carries an out-argument
that is set only on LSM error.
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
fs/file.c | 48 +++++++++++++++++++++++++++++---------------
include/linux/file.h | 2 ++
2 files changed, 34 insertions(+), 16 deletions(-)
diff --git a/fs/file.c b/fs/file.c
index 628ca07dc4b1..2bc22cc69e84 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -1367,6 +1367,25 @@ int replace_fd(unsigned fd, struct file *file, unsigned flags)
return err;
}
+static int __receive_fd(struct file *file, int __user *ufd, unsigned int o_flags)
+{
+ int error;
+
+ FD_PREPARE(fdf, o_flags, file);
+ if (fdf.err)
+ return fdf.err;
+ get_file(file);
+
+ if (ufd) {
+ error = put_user(fd_prepare_fd(fdf), ufd);
+ if (error)
+ return error;
+ }
+
+ __receive_sock(fd_prepare_file(fdf));
+ return fd_publish(fdf);
+}
+
/**
* receive_fd() - Install received file into file descriptor table
* @file: struct file that was received from another process
@@ -1384,27 +1403,24 @@ int replace_fd(unsigned fd, struct file *file, unsigned flags)
*/
int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags)
{
- int error;
-
- error = security_file_receive(file);
+ int error = security_file_receive(file);
if (error)
return error;
+ return __receive_fd(file, ufd, o_flags);
+}
+EXPORT_SYMBOL_GPL(receive_fd);
- FD_PREPARE(fdf, o_flags, file);
- if (fdf.err)
- return fdf.err;
- get_file(file);
-
- if (ufd) {
- error = put_user(fd_prepare_fd(fdf), ufd);
- if (error)
- return error;
+int receive_fd_filtered(struct file *file, int __user *ufd, unsigned int o_flags,
+ bool *filtered)
+{
+ int error = security_file_receive(file);
+ if (error) {
+ *filtered = true;
+ return error;
}
-
- __receive_sock(fd_prepare_file(fdf));
- return fd_publish(fdf);
+ *filtered = false;
+ return __receive_fd(file, ufd, o_flags);
}
-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..748f08470bb4 100644
--- a/include/linux/file.h
+++ b/include/linux/file.h
@@ -119,6 +119,8 @@ 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_filtered(struct file *file, int __user *ufd, unsigned int o_flags,
+ bool *filtered);
int receive_fd_replace(int new_fd, struct file *file, unsigned int o_flags);
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-06-29 19:43 [PATCH net-next v3 0/5] af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
2026-06-29 19:43 ` [PATCH net-next v3 1/5] net: scm: move scm_detach_fds() from common path to scm_recv_unix() Jori Koolstra
2026-06-29 19:43 ` [PATCH net-next v3 2/5] vfs: add function receive_fd_filtered() that makes LSM filtering explicit Jori Koolstra
@ 2026-06-29 19:43 ` Jori Koolstra
2026-06-30 9:58 ` Christian Brauner
2026-06-30 16:43 ` Kuniyuki Iwashima
2026-06-29 19:43 ` [PATCH net-next v3 4/5] net: af_unix: replace copy_from_sockptr() with copy_safe_from_sockptr() Jori Koolstra
2026-06-29 19:43 ` [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
4 siblings, 2 replies; 16+ messages in thread
From: Jori Koolstra @ 2026-06-29 19:43 UTC (permalink / raw)
To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra
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 a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful
handling of LSM denials when receiving SCM_RIGHTS messages: instead of
truncating the message at the first blocked fd, keep every fd slot
and store the LSM errno in the blocked slot.
[1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
include/net/af_unix.h | 1 +
include/net/scm.h | 15 +++++++++++----
include/uapi/asm-generic/socket.h | 3 +++
net/compat.c | 4 ++--
net/core/scm.c | 16 +++++++++++-----
net/unix/af_unix.c | 9 +++++++++
6 files changed, 37 insertions(+), 11 deletions(-)
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 34f53dde65ce..bb1b3dee02e8 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -49,6 +49,7 @@ struct unix_sock {
struct scm_stat scm_stat;
int inq_len;
bool recvmsg_inq;
+ bool scm_rights_notrunc;
#if IS_ENABLED(CONFIG_AF_UNIX_OOB)
struct sk_buff *oob_skb;
#endif
diff --git a/include/net/scm.h b/include/net/scm.h
index c52519669349..761cda0803fb 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, bool notrunc);
+void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
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,18 @@ 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 flags, bool notrunc)
{
+ bool filtered;
+ int error;
+
if (!ufd)
return -EFAULT;
- return receive_fd(f, ufd, flags);
+
+ error = receive_fd_filtered(f, ufd, flags, &filtered);
+ if (filtered && notrunc)
+ return put_user(error, ufd);
+ return error;
}
#endif /* __LINUX_NET_SCM_H */
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 53b5a8c002b1..c5fb2ee96830 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -150,6 +150,9 @@
#define SO_INQ 84
#define SCM_INQ SO_INQ
+#define SO_RIGHTS_NOTRUNC 85
+#define SCM_RIGHTS_NOTRUNC SO_RIGHTS_NOTRUNC
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
diff --git a/net/compat.c b/net/compat.c
index d68cf9c3aad5..6bdf4a2c9077 100644
--- a/net/compat.c
+++ b/net/compat.c
@@ -286,7 +286,7 @@ 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, bool notrunc)
{
struct compat_cmsghdr __user *cm =
(struct compat_cmsghdr __user *)msg->msg_control_user;
@@ -296,7 +296,7 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)
int err = 0, i;
for (i = 0; i < fdmax; i++) {
- err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
+ err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
if (err < 0)
break;
}
diff --git a/net/core/scm.c b/net/core/scm.c
index a73b1eb30fd2..55bab203281a 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -351,7 +351,7 @@ 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, bool notrunc)
{
struct cmsghdr __user *cm =
(__force struct cmsghdr __user *)msg->msg_control_user;
@@ -365,12 +365,12 @@ 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, notrunc);
return;
}
for (i = 0; i < fdmax; i++) {
- err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
+ err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
if (err < 0)
break;
}
@@ -542,8 +542,14 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
if (!__scm_recv_common(sock->sk, msg, scm, flags))
return;
- if (scm->fp)
- scm_detach_fds(msg, scm);
+ if (scm->fp) {
+ struct unix_sock *u;
+ bool notrunc;
+
+ u = unix_sk(sock->sk);
+ notrunc = READ_ONCE(u->scm_rights_notrunc);
+ scm_detach_fds(msg, scm, notrunc);
+ }
if (sock->sk->sk_scm_pidfd)
scm_pidfd_recv(msg, scm);
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..83274ce18e06 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -921,6 +921,7 @@ static bool unix_custom_sockopt(int optname)
{
switch (optname) {
case SO_INQ:
+ case SO_RIGHTS_NOTRUNC:
return true;
default:
return false;
@@ -956,6 +957,14 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
WRITE_ONCE(u->recvmsg_inq, val);
break;
+
+ case SO_RIGHTS_NOTRUNC:
+ if (val > 1 || val < 0)
+ return -EINVAL;
+
+ WRITE_ONCE(u->scm_rights_notrunc, val);
+ break;
+
default:
return -ENOPROTOOPT;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-06-29 19:43 ` [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
@ 2026-06-30 9:58 ` Christian Brauner
2026-06-30 12:17 ` Jori Koolstra
2026-06-30 16:43 ` Kuniyuki Iwashima
1 sibling, 1 reply; 16+ messages in thread
From: Christian Brauner @ 2026-06-30 9:58 UTC (permalink / raw)
To: Jori Koolstra
Cc: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev, linux-fsdevel, linux-kernel
> 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 a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful
> handling of LSM denials when receiving SCM_RIGHTS messages: instead of
> truncating the message at the first blocked fd, keep every fd slot
> and store the LSM errno in the blocked slot.
>
> [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
>
> Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
>
> diff --git a/include/net/af_unix.h b/include/net/af_unix.h
> index 34f53dde65ce..bb1b3dee02e8 100644
> --- a/include/net/af_unix.h
> +++ b/include/net/af_unix.h
> @@ -49,6 +49,7 @@ struct unix_sock {
> struct scm_stat scm_stat;
> int inq_len;
> bool recvmsg_inq;
> + bool scm_rights_notrunc;
> #if IS_ENABLED(CONFIG_AF_UNIX_OOB)
> struct sk_buff *oob_skb;
> #endif
> diff --git a/include/net/scm.h b/include/net/scm.h
> index c52519669349..761cda0803fb 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, bool notrunc);
> +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
> 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,18 @@ 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 flags, bool notrunc)
> {
> + bool filtered;
> + int error;
> +
> if (!ufd)
> return -EFAULT;
> - return receive_fd(f, ufd, flags);
> +
> + error = receive_fd_filtered(f, ufd, flags, &filtered);
> + if (filtered && notrunc)
> + return put_user(error, ufd);
This helper makes no sense to me. The boolean return argument is just
really nasty and you need an additional put_user() as well. At this
point, just drop receive_fd() and open-code it instead of using another
custom helper. Something like the completely untested:
diff --git a/include/net/scm.h b/include/net/scm.h
index 761cda0803fb..171b5ccd0b77 100644
--- a/include/net/scm.h
+++ b/include/net/scm.h
@@ -116,10 +116,22 @@ static inline int scm_recv_one_fd(struct file *f, int __user *ufd,
if (!ufd)
return -EFAULT;
- error = receive_fd_filtered(f, ufd, flags, &filtered);
- if (filtered && notrunc)
- return put_user(error, ufd);
- return error;
+ error = security_file_receive(file);
+ if (error)
+ return notrunc ? put_user(error, ufd) : error;
+
+ FD_PREPARE(fdf, flags, f);
+ if (fdf.err)
+ return fdf.err;
+ get_file(f);
+
+ error = put_user(fd_prepare_fd(fdf), ufd);
+ if (error)
+ return error;
+
+ __receive_sock(f);
+ return fd_publish(fdf);
}
--
Christian Brauner <brauner@kernel.org>
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-06-30 9:58 ` Christian Brauner
@ 2026-06-30 12:17 ` Jori Koolstra
2026-07-01 7:44 ` Christian Brauner
0 siblings, 1 reply; 16+ messages in thread
From: Jori Koolstra @ 2026-06-30 12:17 UTC (permalink / raw)
To: Christian Brauner
Cc: Aleksa Sarai, Kuniyuki Iwashima, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-fsdevel,
linux-kernel
> Op 30-06-2026 11:58 CEST schreef Christian Brauner <brauner@kernel.org>:
>
>
> > 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 a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful
> > handling of LSM denials when receiving SCM_RIGHTS messages: instead of
> > truncating the message at the first blocked fd, keep every fd slot
> > and store the LSM errno in the blocked slot.
> >
> > [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
> >
> > Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
> >
> > diff --git a/include/net/af_unix.h b/include/net/af_unix.h
> > index 34f53dde65ce..bb1b3dee02e8 100644
> > --- a/include/net/af_unix.h
> > +++ b/include/net/af_unix.h
> > @@ -49,6 +49,7 @@ struct unix_sock {
> > struct scm_stat scm_stat;
> > int inq_len;
> > bool recvmsg_inq;
> > + bool scm_rights_notrunc;
> > #if IS_ENABLED(CONFIG_AF_UNIX_OOB)
> > struct sk_buff *oob_skb;
> > #endif
> > diff --git a/include/net/scm.h b/include/net/scm.h
> > index c52519669349..761cda0803fb 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, bool notrunc);
> > +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
> > 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,18 @@ 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 flags, bool notrunc)
> > {
> > + bool filtered;
> > + int error;
> > +
> > if (!ufd)
> > return -EFAULT;
> > - return receive_fd(f, ufd, flags);
> > +
> > + error = receive_fd_filtered(f, ufd, flags, &filtered);
> > + if (filtered && notrunc)
> > + return put_user(error, ufd);
>
> This helper makes no sense to me. The boolean return argument is just
> really nasty and you need an additional put_user() as well. At this
> point, just drop receive_fd() and open-code it instead of using another
> custom helper. Something like the completely untested:
>
It's not very pretty no. I thought about several different options but they
all kinda suck.
You could override the error returned from the LSM to -EACCES. Since nothing
else in receive_fd() produces this, if you get it you can be sure that you had
an LSM fd block. However, this masks the real returned error and is also a bit
fragile if receive_fd() does ever return -EACCES in another path (unlikely but still).
You can also signal blocking by setting the ufd to the security_file_receive() error
no matter the socket option. But this does change userspace.
But open-coding might be a better idea.
I also choose to not put -EPERM as sentinel as suggested first, but use the
actual LSM error. Agreed?
> diff --git a/include/net/scm.h b/include/net/scm.h
> index 761cda0803fb..171b5ccd0b77 100644
> --- a/include/net/scm.h
> +++ b/include/net/scm.h
> @@ -116,10 +116,22 @@ static inline int scm_recv_one_fd(struct file *f, int __user *ufd,
> if (!ufd)
> return -EFAULT;
>
> - error = receive_fd_filtered(f, ufd, flags, &filtered);
> - if (filtered && notrunc)
> - return put_user(error, ufd);
> - return error;
> + error = security_file_receive(file);
> + if (error)
> + return notrunc ? put_user(error, ufd) : error;
> +
> + FD_PREPARE(fdf, flags, f);
> + if (fdf.err)
> + return fdf.err;
> + get_file(f);
> +
> + error = put_user(fd_prepare_fd(fdf), ufd);
> + if (error)
> + return error;
> +
> + __receive_sock(f);
> + return fd_publish(fdf);
> }
>
> --
> Christian Brauner <brauner@kernel.org>
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-06-30 12:17 ` Jori Koolstra
@ 2026-07-01 7:44 ` Christian Brauner
0 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-07-01 7:44 UTC (permalink / raw)
To: Jori Koolstra
Cc: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev, linux-fsdevel, linux-kernel
> I also choose to not put -EPERM as sentinel as suggested first, but use the
> actual LSM error. Agreed?
Yes. We have to surface the actual error. In case the LSM is returning
some custom error (can easily happen from a bpf lsm).
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-06-29 19:43 ` [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
2026-06-30 9:58 ` Christian Brauner
@ 2026-06-30 16:43 ` Kuniyuki Iwashima
1 sibling, 0 replies; 16+ messages in thread
From: Kuniyuki Iwashima @ 2026-06-30 16:43 UTC (permalink / raw)
To: Jori Koolstra
Cc: Christian Brauner, Aleksa Sarai, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-fsdevel,
linux-kernel
On Mon, Jun 29, 2026 at 12:42 PM Jori Koolstra <jkoolstra@xs4all.nl> 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 a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful
> handling of LSM denials when receiving SCM_RIGHTS messages: instead of
> truncating the message at the first blocked fd, keep every fd slot
> and store the LSM errno in the blocked slot.
>
> [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
>
> Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
> ---
> include/net/af_unix.h | 1 +
> include/net/scm.h | 15 +++++++++++----
> include/uapi/asm-generic/socket.h | 3 +++
> net/compat.c | 4 ++--
> net/core/scm.c | 16 +++++++++++-----
> net/unix/af_unix.c | 9 +++++++++
> 6 files changed, 37 insertions(+), 11 deletions(-)
>
> diff --git a/include/net/af_unix.h b/include/net/af_unix.h
> index 34f53dde65ce..bb1b3dee02e8 100644
> --- a/include/net/af_unix.h
> +++ b/include/net/af_unix.h
> @@ -49,6 +49,7 @@ struct unix_sock {
> struct scm_stat scm_stat;
> int inq_len;
> bool recvmsg_inq;
> + bool scm_rights_notrunc;
> #if IS_ENABLED(CONFIG_AF_UNIX_OOB)
> struct sk_buff *oob_skb;
> #endif
> diff --git a/include/net/scm.h b/include/net/scm.h
> index c52519669349..761cda0803fb 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, bool notrunc);
> +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
> 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,18 @@ 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 flags, bool notrunc)
> {
> + bool filtered;
> + int error;
> +
> if (!ufd)
> return -EFAULT;
> - return receive_fd(f, ufd, flags);
> +
> + error = receive_fd_filtered(f, ufd, flags, &filtered);
> + if (filtered && notrunc)
> + return put_user(error, ufd);
> + return error;
> }
>
> #endif /* __LINUX_NET_SCM_H */
> diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
> index 53b5a8c002b1..c5fb2ee96830 100644
> --- a/include/uapi/asm-generic/socket.h
> +++ b/include/uapi/asm-generic/socket.h
> @@ -150,6 +150,9 @@
> #define SO_INQ 84
> #define SCM_INQ SO_INQ
>
> +#define SO_RIGHTS_NOTRUNC 85
> +#define SCM_RIGHTS_NOTRUNC SO_RIGHTS_NOTRUNC
SCM_RIGHTS_NOTRUNC is not needed as it's not used.
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
> diff --git a/net/compat.c b/net/compat.c
> index d68cf9c3aad5..6bdf4a2c9077 100644
> --- a/net/compat.c
> +++ b/net/compat.c
> @@ -286,7 +286,7 @@ 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, bool notrunc)
> {
> struct compat_cmsghdr __user *cm =
> (struct compat_cmsghdr __user *)msg->msg_control_user;
> @@ -296,7 +296,7 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)
> int err = 0, i;
>
> for (i = 0; i < fdmax; i++) {
> - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
> + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
> if (err < 0)
> break;
> }
> diff --git a/net/core/scm.c b/net/core/scm.c
> index a73b1eb30fd2..55bab203281a 100644
> --- a/net/core/scm.c
> +++ b/net/core/scm.c
> @@ -351,7 +351,7 @@ 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, bool notrunc)
> {
> struct cmsghdr __user *cm =
> (__force struct cmsghdr __user *)msg->msg_control_user;
> @@ -365,12 +365,12 @@ 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, notrunc);
> return;
> }
>
> for (i = 0; i < fdmax; i++) {
> - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
> + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
> if (err < 0)
> break;
> }
> @@ -542,8 +542,14 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
> if (!__scm_recv_common(sock->sk, msg, scm, flags))
> return;
>
> - if (scm->fp)
> - scm_detach_fds(msg, scm);
> + if (scm->fp) {
> + struct unix_sock *u;
> + bool notrunc;
> +
> + u = unix_sk(sock->sk);
> + notrunc = READ_ONCE(u->scm_rights_notrunc);
Does this build with CONFIG_UNIX=n ?
> + scm_detach_fds(msg, scm, notrunc);
> + }
>
> if (sock->sk->sk_scm_pidfd)
> scm_pidfd_recv(msg, scm);
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index f7a9d55eee8a..83274ce18e06 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -921,6 +921,7 @@ static bool unix_custom_sockopt(int optname)
> {
> switch (optname) {
> case SO_INQ:
> + case SO_RIGHTS_NOTRUNC:
> return true;
> default:
> return false;
> @@ -956,6 +957,14 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
>
> WRITE_ONCE(u->recvmsg_inq, val);
> break;
> +
> + case SO_RIGHTS_NOTRUNC:
> + if (val > 1 || val < 0)
> + return -EINVAL;
> +
> + WRITE_ONCE(u->scm_rights_notrunc, val);
> + break;
> +
> default:
> return -ENOPROTOOPT;
> }
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH net-next v3 4/5] net: af_unix: replace copy_from_sockptr() with copy_safe_from_sockptr()
2026-06-29 19:43 [PATCH net-next v3 0/5] af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
` (2 preceding siblings ...)
2026-06-29 19:43 ` [PATCH net-next v3 3/5] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
@ 2026-06-29 19:43 ` Jori Koolstra
2026-06-30 16:18 ` Kuniyuki Iwashima
2026-06-29 19:43 ` [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
4 siblings, 1 reply; 16+ messages in thread
From: Jori Koolstra @ 2026-06-29 19:43 UTC (permalink / raw)
To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra
Replace deprecated call to copy_from_sockptr() with
copy_safe_from_sockptr().
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
net/unix/af_unix.c | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 83274ce18e06..07a0b07cd65f 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -933,6 +933,7 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
{
struct unix_sock *u = unix_sk(sock->sk);
struct sock *sk = sock->sk;
+ int error;
int val;
if (level != SOL_SOCKET)
@@ -941,11 +942,9 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
if (!unix_custom_sockopt(optname))
return sock_setsockopt(sock, level, optname, optval, optlen);
- if (optlen != sizeof(int))
- return -EINVAL;
-
- if (copy_from_sockptr(&val, optval, sizeof(val)))
- return -EFAULT;
+ error = copy_safe_from_sockptr(&val, sizeof(val), optval, optlen);
+ if (error)
+ return error;
switch (optname) {
case SO_INQ:
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH net-next v3 4/5] net: af_unix: replace copy_from_sockptr() with copy_safe_from_sockptr()
2026-06-29 19:43 ` [PATCH net-next v3 4/5] net: af_unix: replace copy_from_sockptr() with copy_safe_from_sockptr() Jori Koolstra
@ 2026-06-30 16:18 ` Kuniyuki Iwashima
0 siblings, 0 replies; 16+ messages in thread
From: Kuniyuki Iwashima @ 2026-06-30 16:18 UTC (permalink / raw)
To: Jori Koolstra
Cc: Christian Brauner, Aleksa Sarai, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-fsdevel,
linux-kernel
On Mon, Jun 29, 2026 at 12:42 PM Jori Koolstra <jkoolstra@xs4all.nl> wrote:
>
> Replace deprecated call to copy_from_sockptr() with
> copy_safe_from_sockptr().
>
> Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
> ---
> net/unix/af_unix.c | 9 ++++-----
> 1 file changed, 4 insertions(+), 5 deletions(-)
>
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 83274ce18e06..07a0b07cd65f 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -933,6 +933,7 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
> {
> struct unix_sock *u = unix_sk(sock->sk);
> struct sock *sk = sock->sk;
> + int error;
> int val;
>
> if (level != SOL_SOCKET)
> @@ -941,11 +942,9 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
> if (!unix_custom_sockopt(optname))
> return sock_setsockopt(sock, level, optname, optval, optlen);
>
> - if (optlen != sizeof(int))
> - return -EINVAL;
> -
> - if (copy_from_sockptr(&val, optval, sizeof(val)))
> - return -EFAULT;
> + error = copy_safe_from_sockptr(&val, sizeof(val), optval, optlen);
Currently, only int is allowed, but this change unnecessarily loosens
the validation.
Please keep copy_from_sockptr() as is.
> + if (error)
> + return error;
>
> switch (optname) {
> case SO_INQ:
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-06-29 19:43 [PATCH net-next v3 0/5] af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
` (3 preceding siblings ...)
2026-06-29 19:43 ` [PATCH net-next v3 4/5] net: af_unix: replace copy_from_sockptr() with copy_safe_from_sockptr() Jori Koolstra
@ 2026-06-29 19:43 ` Jori Koolstra
2026-06-30 14:17 ` Jakub Kicinski
4 siblings, 1 reply; 16+ messages in thread
From: Jori Koolstra @ 2026-06-29 19:43 UTC (permalink / raw)
To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra
Tests SCM_RIGHTS fd passing on a socket with the new socket option
SO_RIGHTS_NOTRUNC turned on.
The test uses the following Smack labels:
"Sender" - label for the sending process
"Receiver" - label for the receiving process
"SecretX" - labels for the files being passed
Socket communication (Sender <-> Receiver) is always allowed.
The tests control whether Receiver can access "SecretX"-labeled fds.
When the LSM blocks an fd, we should see a sentinel that corresponds to
the error returned by the LSM, such as -EACCES.
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
.../testing/selftests/net/af_unix/.gitignore | 3 +
tools/testing/selftests/net/af_unix/Makefile | 4 +
.../selftests/net/af_unix/scm_rights_denial.c | 20 ++
.../selftests/net/af_unix/scm_rights_denial.h | 38 ++++
.../net/af_unix/scm_rights_denial.sh | 174 ++++++++++++++++
.../net/af_unix/scm_rights_denial_receiver.c | 195 ++++++++++++++++++
.../net/af_unix/scm_rights_denial_sender.c | 126 +++++++++++
7 files changed, 560 insertions(+)
create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial.c
create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial.h
create mode 100755 tools/testing/selftests/net/af_unix/scm_rights_denial.sh
create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_receiver.c
create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_sender.c
diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
index 240b26740c9e..2e14b025a6e8 100644
--- a/tools/testing/selftests/net/af_unix/.gitignore
+++ b/tools/testing/selftests/net/af_unix/.gitignore
@@ -3,6 +3,9 @@ msg_oob
scm_inq
scm_pidfd
scm_rights
+scm_rights_denial
+scm_rights_denial_receiver
+scm_rights_denial_sender
so_peek_off
unix_connect
unix_connreset
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index 4c0375e28bbe..86c5588caf28 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -11,9 +11,13 @@ TEST_GEN_PROGS := \
scm_inq \
scm_pidfd \
scm_rights \
+ scm_rights_denial \
so_peek_off \
unix_connect \
unix_connreset \
# end of TEST_GEN_PROGS
+TEST_GEN_FILES := scm_rights_denial_sender scm_rights_denial_receiver
+TEST_FILES := scm_rights_denial.sh
+
include ../../lib.mk
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial.c b/tools/testing/selftests/net/af_unix/scm_rights_denial.c
new file mode 100644
index 000000000000..3566f1d28b9a
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "kselftest_harness.h"
+
+TEST(scm_rights_denial)
+{
+ int ret = system("./scm_rights_denial.sh");
+
+ ASSERT_NE(-1, ret);
+ ASSERT_TRUE(WIFEXITED(ret));
+
+ if (WEXITSTATUS(ret) == KSFT_SKIP)
+ SKIP(return, "scm_rights_denial.sh prerequisites not met");
+
+ EXPECT_EQ(0, WEXITSTATUS(ret));
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial.h b/tools/testing/selftests/net/af_unix/scm_rights_denial.h
new file mode 100644
index 000000000000..2ecdf2b8b973
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifndef SO_RIGHTS_NOTRUNC
+#define SO_RIGHTS_NOTRUNC 85
+#endif
+
+#define CMSG_IS_SCM_RIGHTS(cmsg) ({ \
+ typeof(cmsg) _cmsg = (cmsg); \
+ _cmsg && \
+ _cmsg->cmsg_level == SOL_SOCKET && \
+ _cmsg->cmsg_type == SCM_RIGHTS; \
+})
+
+#define MIN(a, b) ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a < _b ? _a : _b; \
+})
+
+#define MAX_FDS 10
+
+static inline int read_current_label(char *label, size_t size)
+{
+ int fd = open("/proc/self/attr/current", O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ ssize_t r = read(fd, label, size - 1);
+ close(fd);
+ if (r < 0)
+ return r;
+
+ label[r] = '\0';
+
+ return 0;
+}
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial.sh b/tools/testing/selftests/net/af_unix/scm_rights_denial.sh
new file mode 100755
index 000000000000..74cafe1dfc62
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial.sh
@@ -0,0 +1,174 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# scm_rights_denial.sh - Test SCM_RIGHTS fd passing using Smack LSM blocking
+#
+# Must be run as root on a kernel with Smack enabled (security=smack).
+# Requires: capsh (libcap), setfattr/getfattr (attr)
+#
+# We use the following Smack labels:
+# "Sender" - label for the sending process
+# "Receiver" - label for the receiving process
+# "SecretX" - labels for the files being passed
+#
+# Socket communication (Sender <-> Receiver) is always allowed.
+# The tests control whether Receiver can access "SecretX"-labeled fds.
+#
+
+set -e
+
+readonly KSFT_SKIP=4
+
+readonly SENDER="./scm_rights_denial_sender"
+readonly RECEIVER="./scm_rights_denial_receiver"
+
+readonly TESTDIR="$(mktemp -d)"
+readonly SOCK="$TESTDIR/scm_test.sock"
+readonly TESTFILE1="$TESTDIR/secret_1"
+readonly TESTFILE2="$TESTDIR/secret_2"
+
+trap 'rm -rf "$TESTDIR"' EXIT
+
+run_tests() {
+
+ preflight
+ setup
+
+ run_test "TEST 1" \
+ "Receiver should NOT have access to Secret1." \
+ "Receiver Secret1 ---
+Receiver Secret2 ---" \
+ "$TESTFILE1" \
+ "BLOCKED"
+
+ run_test "TEST 2" \
+ "Receiver should have access to Secret1." \
+ "Receiver Secret1 r--
+Receiver Secret2 ---" \
+ "$TESTFILE1" \
+ "PASSED"
+
+ run_test "TEST 3" \
+ "Receiver should have access to Secret2, but NOT Secret1." \
+ "Receiver Secret1 ---
+Receiver Secret2 r--" \
+ "$TESTFILE1 $TESTFILE2" \
+ "BLOCKED PASSED"
+}
+
+run_test() {
+ local name="$1"
+ local description="$2"
+ local rules="$3"
+ local files="$4"
+ local expected="$5"
+
+ echo ""
+ echo "$name: $description"
+ echo "Rules:"
+ echo "$rules"
+ echo "Expected: $expected"
+ echo ""
+
+ while IFS= read -r rule; do
+ [ -n "$rule" ] && echo "$rule" > /sys/fs/smackfs/load2
+ done <<< "$rules"
+
+ local output status last_line
+ output=$(send_fds "$SOCK" $files)
+ status=$?
+ echo "$output"
+ last_line=$(echo "$output" | tail -n 1 | xargs)
+
+ if [ "$status" -ne 0 ]; then
+ echo "TEST FAILED: receiver returned $status"
+ return 1
+ fi
+
+ if [[ "$last_line" == "$expected" ]]; then
+ echo "TEST PASSED: outcome was $expected as expected"
+ return 0
+ else
+ echo "TEST FAILED: expected $expected, got '$last_line'"
+ return 1
+ fi
+}
+
+setup() {
+
+ printf "Secret 1" > "$TESTFILE1"
+ printf "Secret 2" > "$TESTFILE2"
+
+ setfattr -n security.SMACK64 -v "Secret1" "$TESTFILE1"
+ setfattr -n security.SMACK64 -v "Secret2" "$TESTFILE2"
+ setfattr -n security.SMACK64 -v "Tmp" /tmp
+ setfattr -n security.SMACK64 -v "Tmp" "$TESTDIR"
+
+ echo "Sender Receiver -w-" > /sys/fs/smackfs/load2
+ echo "Receiver Sender -w-" > /sys/fs/smackfs/load2
+ echo "Sender Tmp rwx" > /sys/fs/smackfs/load2
+ echo "Receiver Tmp rwx" > /sys/fs/smackfs/load2
+ echo "Sender Secret1 r--" > /sys/fs/smackfs/load2
+ echo "Sender Secret2 r--" > /sys/fs/smackfs/load2
+}
+
+send_fds() {
+
+ local sk="$1"
+ shift
+ local files="$*"
+
+ (
+ echo "Receiver" > /proc/self/attr/current
+ exec capsh --drop=cap_mac_override,cap_mac_admin -- -c "$RECEIVER $sk"
+ ) &
+ local recv_pid=$!
+ sleep 1
+
+ (
+ echo "Sender" > /proc/self/attr/current
+ exec capsh --drop=cap_mac_override,cap_mac_admin -- -c "$SENDER $sk $files"
+ ) || true
+
+ local recv_status=0
+ wait "$recv_pid" || recv_status=$?
+
+ if [ "$recv_status" -ne 0 ]; then
+ echo "receiver exited with $recv_status"
+ fi
+ return "$recv_status"
+}
+
+preflight() {
+
+ if [ "$(id -u)" -ne 0 ]; then
+ echo "SKIP: must be run as root"
+ exit $KSFT_SKIP
+ fi
+
+ if ! grep -q smack /sys/kernel/security/lsm 2>/dev/null; then
+ echo "SKIP: Smack is not active"
+ echo " Check: cat /sys/kernel/security/lsm"
+ echo " Boot with: security=smack"
+ exit $KSFT_SKIP
+ fi
+
+ if ! mountpoint -q /sys/fs/smackfs 2>/dev/null; then
+ echo "Mounting smackfs..."
+ mount -t smackfs smackfs /sys/fs/smackfs
+ fi
+
+ if ! command -v capsh &>/dev/null; then
+ echo "SKIP: capsh not found (install libcap)"
+ exit $KSFT_SKIP
+ fi
+
+ if [ ! -x "$SENDER" ] || [ ! -x "$RECEIVER" ]; then
+ echo "ERROR: $SENDER / $RECEIVER not built (run 'make' first)"
+ exit 1
+ fi
+
+}
+
+run_tests
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_receiver.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_receiver.c
new file mode 100644
index 000000000000..2965980f96ef
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_receiver.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * scm_rights_denial_receiver.c - Receive fds over a Unix socket via SCM_RIGHTS
+ *
+ * Usage: ./scm_rights_denial_receiver <socket_path>
+ *
+ * Listens on the given Unix socket path, accepts a connection, and
+ * attempts to receive file descriptors via SCM_RIGHTS. Reports
+ * whether the fds were delivered or blocked.
+ *
+ * Used for testing LSM (Smack) blocking of fd passing.
+ */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "scm_rights_denial.h"
+
+#define RECV_LOG(fmt, ...) printf("receiver: " fmt, ##__VA_ARGS__)
+#define RECV_ERR(fmt, ...) fprintf(stderr, "receiver: " fmt, ##__VA_ARGS__)
+
+static int recv_fds(int sock, int *fds)
+{
+ char buf[1];
+ char ctrl[CMSG_SPACE(MAX_FDS * sizeof(int))];
+
+ struct iovec iov = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf),
+ };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = ctrl,
+ .msg_controllen = sizeof(ctrl),
+ };
+
+ ssize_t bytes_read = recvmsg(sock, &msg, 0);
+ if (bytes_read < 0) {
+ perror("receiver: recvmsg");
+ return -1;
+ }
+ if (bytes_read == 0) {
+ RECV_ERR("connection closed, no data received\n");
+ return -1;
+ }
+
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+ if (!CMSG_IS_SCM_RIGHTS(cmsg)) {
+ RECV_ERR("no SCM_RIGHTS in control message\n");
+ return -1;
+ }
+
+ int num_fd_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ memcpy(fds, CMSG_DATA(cmsg), num_fd_slots * sizeof(int));
+
+ RECV_LOG("got %d fd slot(s):", num_fd_slots);
+ for (int i = 0; i < num_fd_slots ; i++) {
+ if (fds[i] < 0)
+ printf(" %s", strerrorname_np(-fds[i]));
+ else
+ printf(" %d", fds[i]);
+ }
+ putchar('\n');
+
+ return num_fd_slots;
+}
+
+static inline int print_current_label(void)
+{
+ char label[256];
+ if (!read_current_label(label, sizeof(label))) {
+ RECV_LOG("running with Smack label '%s'\n", label);
+ return 0;
+ }
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <socket_path>\n", argv[0]);
+ return -1;
+ }
+
+ if (print_current_label()) {
+ RECV_ERR("cannot read process Smack label");
+ return -1;
+ }
+
+ int listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (listen_sock < 0) {
+ perror("receiver: socket");
+ return -1;
+ }
+
+ struct sockaddr_un addr = {};
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1);
+
+ /* Remove any stale socket file */
+ unlink(argv[1]);
+
+ if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ perror("receiver: bind");
+ return -1;
+ }
+
+ if (listen(listen_sock, 1) < 0) {
+ perror("receiver: listen");
+ return -1;
+ }
+
+ RECV_LOG("listening on '%s'\n", argv[1]);
+
+ int conn_sock = accept(listen_sock, NULL, NULL);
+ if (conn_sock < 0) {
+ perror("receiver: accept");
+ return -1;
+ }
+
+ RECV_LOG("connection accepted\n");
+
+ int one = 1;
+ if (setsockopt(conn_sock, SOL_SOCKET, SO_RIGHTS_NOTRUNC,
+ &one, sizeof(one)) < 0) {
+ perror("receiver: setsockopt(SO_RIGHTS_NOTRUNC)");
+ goto out_sock;
+ }
+
+ /* Try to receive the fds */
+ int fds[MAX_FDS];
+ int num_fds = recv_fds(conn_sock, fds);
+ if (num_fds < 0)
+ goto out_sock;
+
+ /* Try to use the received fds -- read and print their contents */
+ RECV_LOG("attempting to read from received fds...\n");
+ int i;
+ for (i = 0; i < num_fds; ++i) {
+ char readbuf[256];
+
+ if (fds[i] < 0) {
+ RECV_LOG("fd in position %i blocked\n", i);
+ continue;
+ } else if (fds[i] == 0) {
+ RECV_LOG("bad fd in position %i\n", i);
+ goto out_recv;
+ }
+
+ ssize_t n = read(fds[i], readbuf, sizeof(readbuf) - 1);
+ if (n < 0) {
+ perror("receiver: read from received fd");
+ goto out_recv;
+ }
+
+ readbuf[n] = '\0';
+ RECV_LOG("read %zd bytes from fd at position %i: '%s'\n", n, i, readbuf);
+ }
+
+ RECV_LOG("final result:\n");
+ for (int j = 0; j < num_fds; ++j) {
+ if (fds[j] < 0) {
+ printf("BLOCKED");
+ } else {
+ printf("PASSED");
+ close(fds[j]);
+ }
+ putchar(' ');
+ }
+
+ close(conn_sock);
+ close(listen_sock);
+ unlink(argv[1]);
+ return 0;
+
+out_recv:
+ for (int j = 0; j < num_fds; ++j) {
+ if (fds[j] > 0)
+ close(fds[j]);
+ }
+
+out_sock:
+ close(conn_sock);
+ close(listen_sock);
+ unlink(argv[1]);
+ return -1;
+}
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_sender.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_sender.c
new file mode 100644
index 000000000000..9742eb25f0b5
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_sender.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * scm_rights_denial_sender.c - Send fds over a Unix socket via SCM_RIGHTS
+ *
+ * Usage: ./scm_rights_denial_sender <socket_path> <file_to_send> [<file_to_send>...]
+ *
+ * Opens the specified files and sends their fds to a receiver connected
+ * on the given Unix socket path. Used for testing LSM blocking of fd
+ * passing.
+ */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "scm_rights_denial.h"
+
+#define SEND_LOG(fmt, ...) fprintf(stdout, "sender: " fmt, ##__VA_ARGS__)
+#define SEND_ERR(fmt, ...) fprintf(stderr, "sender: " fmt, ##__VA_ARGS__)
+
+static int send_fds(int sock, int *fds, int num_fds)
+{
+ if (num_fds > MAX_FDS)
+ return -1;
+
+ char buf[1] = { 'X' };
+ char ctrl[CMSG_SPACE(MAX_FDS * sizeof(int))] = { 0 };
+
+ struct iovec iov = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf),
+ };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = ctrl,
+ .msg_controllen = CMSG_SPACE(num_fds * sizeof(int)),
+ };
+
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(num_fds * sizeof(int));
+ memcpy(CMSG_DATA(cmsg), fds, num_fds * sizeof(int));
+
+ ssize_t bytes_send = sendmsg(sock, &msg, 0);
+ if (bytes_send < 0) {
+ perror("sender: sendmsg");
+ return -1;
+ }
+
+ return 0;
+}
+
+static inline int print_current_label(void)
+{
+ char label[256];
+ if (!read_current_label(label, sizeof(label))) {
+ SEND_LOG("running with Smack label '%s'\n", label);
+ return 0;
+ }
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 3 || argc > 2 + MAX_FDS) {
+ fprintf(stderr, "Usage: %s <socket_path> <file_to_send> [<file_to_send>...]\\n",
+ argv[0]);
+ fprintf(stderr, "Up to a maximum of %d files", MAX_FDS);
+ return -1;
+ }
+
+ if (print_current_label()) {
+ SEND_ERR("cannot read process Smack label");
+ return -1;
+ }
+
+ int sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ perror("sender: socket");
+ return -1;
+ }
+
+ struct sockaddr_un addr = {};
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1);
+
+ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ perror("sender: connect");
+ goto out_sock;
+ }
+
+ SEND_LOG("connected to '%s'\n", argv[1]);
+
+ int num_files = argc - 2;
+ int fds[MAX_FDS];
+ int i;
+ for (i = 0; i < num_files; i++) {
+ fds[i] = open(argv[2 + i], O_RDONLY);
+ if (fds[i] < 0) {
+ perror("sender: open file");
+ goto out_opened;
+ }
+ SEND_LOG("opened '%s' as fd %d\n", argv[2 + i], fds[i]);
+ }
+
+ if (send_fds(sock, fds, num_files) < 0)
+ goto out_opened;
+
+ SEND_LOG("fds successfully sent:");
+ for (int j = 0; j < num_files; j++)
+ printf(" %d", fds[j]);
+ putchar('\n');
+
+out_opened:
+ for (int j = 0; j < i; j++)
+ close(fds[j]);
+out_sock:
+ close(sock);
+ return -1;
+}
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-06-29 19:43 ` [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
@ 2026-06-30 14:17 ` Jakub Kicinski
2026-06-30 14:35 ` Jori Koolstra
0 siblings, 1 reply; 16+ messages in thread
From: Jakub Kicinski @ 2026-06-30 14:17 UTC (permalink / raw)
To: Jori Koolstra
Cc: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Paolo Abeni, Simon Horman, netdev,
linux-fsdevel, linux-kernel
On Mon, 29 Jun 2026 21:43:27 +0200 Jori Koolstra wrote:
> The test uses the following Smack labels:
>
> "Sender" - label for the sending process
> "Receiver" - label for the receiving process
> "SecretX" - labels for the files being passed
Not sure this test belongs in net/
99.9% of people running this test do not use Smack.
At the very least you need to use XFAIL instead of SKIP
we use skip for problems with the env which are fixable,
like a command missing.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-06-30 14:17 ` Jakub Kicinski
@ 2026-06-30 14:35 ` Jori Koolstra
2026-06-30 16:23 ` Kuniyuki Iwashima
2026-07-01 7:38 ` Christian Brauner
0 siblings, 2 replies; 16+ messages in thread
From: Jori Koolstra @ 2026-06-30 14:35 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
David S . Miller, Eric Dumazet, Paolo Abeni, Simon Horman, netdev,
linux-fsdevel, linux-kernel
> Op 30-06-2026 16:17 CEST schreef Jakub Kicinski <kuba@kernel.org>:
>
>
> On Mon, 29 Jun 2026 21:43:27 +0200 Jori Koolstra wrote:
> > The test uses the following Smack labels:
> >
> > "Sender" - label for the sending process
> > "Receiver" - label for the receiving process
> > "SecretX" - labels for the files being passed
>
> Not sure this test belongs in net/
> 99.9% of people running this test do not use Smack.
> At the very least you need to use XFAIL instead of SKIP
> we use skip for problems with the env which are fixable,
> like a command missing.
Ah, right, because you can only use one of these LSMs at a time?
I mean one of AppArmour, SELinux, Smack, TOMOYO.
I just need some LSM to trigger the reject of security_file_receive()
and Smack was the easiest to get going. The series is totally agnostic
to the used LSM. I am fine with moving the tests elsewhere or porting
them to SELinux if that is really necessary. We could also drop them
altogether.
What do you propose?
Thanks,
Jori.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-06-30 14:35 ` Jori Koolstra
@ 2026-06-30 16:23 ` Kuniyuki Iwashima
2026-07-01 7:38 ` Christian Brauner
1 sibling, 0 replies; 16+ messages in thread
From: Kuniyuki Iwashima @ 2026-06-30 16:23 UTC (permalink / raw)
To: Jori Koolstra
Cc: Jakub Kicinski, Christian Brauner, Aleksa Sarai, David S . Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, netdev, linux-fsdevel,
linux-kernel
On Tue, Jun 30, 2026 at 7:35 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote:
>
>
> > Op 30-06-2026 16:17 CEST schreef Jakub Kicinski <kuba@kernel.org>:
> >
> >
> > On Mon, 29 Jun 2026 21:43:27 +0200 Jori Koolstra wrote:
> > > The test uses the following Smack labels:
> > >
> > > "Sender" - label for the sending process
> > > "Receiver" - label for the receiving process
> > > "SecretX" - labels for the files being passed
> >
> > Not sure this test belongs in net/
> > 99.9% of people running this test do not use Smack.
> > At the very least you need to use XFAIL instead of SKIP
> > we use skip for problems with the env which are fixable,
> > like a command missing.
>
> Ah, right, because you can only use one of these LSMs at a time?
> I mean one of AppArmour, SELinux, Smack, TOMOYO.
>
> I just need some LSM to trigger the reject of security_file_receive()
> and Smack was the easiest to get going. The series is totally agnostic
> to the used LSM. I am fine with moving the tests elsewhere or porting
> them to SELinux if that is really necessary. We could also drop them
> altogether.
>
> What do you propose?
Maybe tools/testing/selftests/lsm ?
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-06-30 14:35 ` Jori Koolstra
2026-06-30 16:23 ` Kuniyuki Iwashima
@ 2026-07-01 7:38 ` Christian Brauner
2026-07-01 9:31 ` Jori Koolstra
1 sibling, 1 reply; 16+ messages in thread
From: Christian Brauner @ 2026-07-01 7:38 UTC (permalink / raw)
To: Jori Koolstra
Cc: Jakub Kicinski, Christian Brauner, Aleksa Sarai,
Kuniyuki Iwashima, David S . Miller, Eric Dumazet, Paolo Abeni,
Simon Horman, netdev, linux-fsdevel, linux-kernel
On 2026-06-30 16:35 +0200, Jori Koolstra wrote:
>
> > Op 30-06-2026 16:17 CEST schreef Jakub Kicinski <kuba@kernel.org>:
> >
> >
> > On Mon, 29 Jun 2026 21:43:27 +0200 Jori Koolstra wrote:
> > > The test uses the following Smack labels:
> > >
> > > "Sender" - label for the sending process
> > > "Receiver" - label for the receiving process
> > > "SecretX" - labels for the files being passed
> >
> > Not sure this test belongs in net/
> > 99.9% of people running this test do not use Smack.
> > At the very least you need to use XFAIL instead of SKIP
> > we use skip for problems with the env which are fixable,
> > like a command missing.
>
> Ah, right, because you can only use one of these LSMs at a time?
> I mean one of AppArmour, SELinux, Smack, TOMOYO.
>
> I just need some LSM to trigger the reject of security_file_receive()
> and Smack was the easiest to get going. The series is totally agnostic
> to the used LSM. I am fine with moving the tests elsewhere or porting
> them to SELinux if that is really necessary. We could also drop them
> altogether.
>
> What do you propose?
I'm pretty sure the easiest will be to use a tiny bpf program to reject
security_file_receive().
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH net-next v3 5/5] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-07-01 7:38 ` Christian Brauner
@ 2026-07-01 9:31 ` Jori Koolstra
0 siblings, 0 replies; 16+ messages in thread
From: Jori Koolstra @ 2026-07-01 9:31 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Aleksa Sarai, Kuniyuki Iwashima, David S . Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, netdev, linux-fsdevel,
linux-kernel
> Op 01-07-2026 09:38 CEST schreef Christian Brauner <brauner@kernel.org>:
>
> >
> > I just need some LSM to trigger the reject of security_file_receive()
> > and Smack was the easiest to get going. The series is totally agnostic
> > to the used LSM. I am fine with moving the tests elsewhere or porting
> > them to SELinux if that is really necessary. We could also drop them
> > altogether.
> >
> > What do you propose?
>
> I'm pretty sure the easiest will be to use a tiny bpf program to reject
> security_file_receive().
Ah. Well, that's a testament to how much there's still to learn for me.
I didn't even know that bpf could hook into LSM calls :)
^ permalink raw reply [flat|nested] 16+ messages in thread