* [PATCH net-next v6 1/4] net: af_unix: enable custom setsockopt for all socket types
2026-08-02 15:12 [PATCH net-next v6 0/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
@ 2026-08-02 15:12 ` Jori Koolstra
2026-08-04 16:49 ` Kuniyuki Iwashima
2026-08-02 15:12 ` [PATCH net-next v6 2/4] net: scm: move scm_detach_fds() from common path to scm_recv_unix() Jori Koolstra
` (2 subsequent siblings)
3 siblings, 1 reply; 12+ messages in thread
From: Jori Koolstra @ 2026-08-02 15:12 UTC (permalink / raw)
To: brauner, cyphar, kuniyu, davem, edumazet, kuba, pabeni, horms
Cc: netdev, linux-fsdevel, linux-kernel, jkoolstra
unix_setsockopt() and the SOCK_CUSTOM_SOCKOPT flag were only wired up
for SOCK_STREAM (introduced along with the stream-only SO_INQ).
Consequently custom AF_UNIX options are unreachable on SOCK_DGRAM and
SOCK_SEQPACKET: those setsockopt() calls bypass unix_setsockopt() and
fall through to the generic sock_setsockopt(), failing with
-ENOPROTOOPT.
Set SOCK_CUSTOM_SOCKOPT for every AF_UNIX socket type in unix_create(), and
also for accepted sockets in unix_accept() (reachable for stream and
seqpacket).
This is a prerequisite for making SO_RIGHTS_NOTRUNC settable on all AF_UNIX
socket types.
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
net/unix/af_unix.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 10ed9421e43a..51cbf920130d 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -950,7 +950,7 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
switch (optname) {
case SO_INQ:
if (sk->sk_type != SOCK_STREAM)
- return -EINVAL;
+ return -ENOPROTOOPT;
if (val > 1 || val < 0)
return -EINVAL;
@@ -1006,6 +1006,7 @@ static const struct proto_ops unix_dgram_ops = {
#endif
.listen = sock_no_listen,
.shutdown = unix_shutdown,
+ .setsockopt = unix_setsockopt,
.sendmsg = unix_dgram_sendmsg,
.read_skb = unix_read_skb,
.recvmsg = unix_dgram_recvmsg,
@@ -1030,6 +1031,7 @@ static const struct proto_ops unix_seqpacket_ops = {
#endif
.listen = unix_listen,
.shutdown = unix_shutdown,
+ .setsockopt = unix_setsockopt,
.sendmsg = unix_seqpacket_sendmsg,
.recvmsg = unix_seqpacket_recvmsg,
.mmap = sock_no_mmap,
@@ -1143,9 +1145,10 @@ static int unix_create(struct net *net, struct socket *sock, int protocol,
if (protocol && protocol != PF_UNIX)
return -EPROTONOSUPPORT;
+ set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags);
+
switch (sock->type) {
case SOCK_STREAM:
- set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags);
sock->ops = &unix_stream_ops;
break;
/*
@@ -1865,8 +1868,7 @@ static int unix_accept(struct socket *sock, struct socket *newsock,
skb_free_datagram(sk, skb);
wake_up_interruptible(&unix_sk(sk)->peer_wait);
- if (tsk->sk_type == SOCK_STREAM)
- set_bit(SOCK_CUSTOM_SOCKOPT, &newsock->flags);
+ set_bit(SOCK_CUSTOM_SOCKOPT, &newsock->flags);
/* attach accepted sock to socket */
unix_state_lock(tsk);
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH net-next v6 1/4] net: af_unix: enable custom setsockopt for all socket types
2026-08-02 15:12 ` [PATCH net-next v6 1/4] net: af_unix: enable custom setsockopt for all socket types Jori Koolstra
@ 2026-08-04 16:49 ` Kuniyuki Iwashima
0 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-08-04 16:49 UTC (permalink / raw)
To: Jori Koolstra
Cc: brauner, cyphar, davem, edumazet, kuba, pabeni, horms, netdev,
linux-fsdevel, linux-kernel
On Sun, Aug 2, 2026 at 8:11 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote:
>
> unix_setsockopt() and the SOCK_CUSTOM_SOCKOPT flag were only wired up
> for SOCK_STREAM (introduced along with the stream-only SO_INQ).
> Consequently custom AF_UNIX options are unreachable on SOCK_DGRAM and
> SOCK_SEQPACKET: those setsockopt() calls bypass unix_setsockopt() and
> fall through to the generic sock_setsockopt(), failing with
> -ENOPROTOOPT.
>
> Set SOCK_CUSTOM_SOCKOPT for every AF_UNIX socket type in unix_create(), and
> also for accepted sockets in unix_accept() (reachable for stream and
> seqpacket).
>
> This is a prerequisite for making SO_RIGHTS_NOTRUNC settable on all AF_UNIX
> socket types.
>
> Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v6 2/4] net: scm: move scm_detach_fds() from common path to scm_recv_unix()
2026-08-02 15:12 [PATCH net-next v6 0/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
2026-08-02 15:12 ` [PATCH net-next v6 1/4] net: af_unix: enable custom setsockopt for all socket types Jori Koolstra
@ 2026-08-02 15:12 ` Jori Koolstra
2026-08-02 15:12 ` [PATCH net-next v6 3/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
2026-08-02 15:12 ` [PATCH net-next v6 4/4] selftest: Add tests for " Jori Koolstra
3 siblings, 0 replies; 12+ messages in thread
From: Jori Koolstra @ 2026-08-02 15:12 UTC (permalink / raw)
To: brauner, cyphar, kuniyu, davem, edumazet, kuba, pabeni, horms
Cc: netdev, linux-fsdevel, linux-kernel, jkoolstra
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().
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
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.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH net-next v6 3/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-08-02 15:12 [PATCH net-next v6 0/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
2026-08-02 15:12 ` [PATCH net-next v6 1/4] net: af_unix: enable custom setsockopt for all socket types Jori Koolstra
2026-08-02 15:12 ` [PATCH net-next v6 2/4] net: scm: move scm_detach_fds() from common path to scm_recv_unix() Jori Koolstra
@ 2026-08-02 15:12 ` Jori Koolstra
2026-08-04 17:16 ` Kuniyuki Iwashima
2026-08-02 15:12 ` [PATCH net-next v6 4/4] selftest: Add tests for " Jori Koolstra
3 siblings, 1 reply; 12+ messages in thread
From: Jori Koolstra @ 2026-08-02 15:12 UTC (permalink / raw)
To: brauner, cyphar, kuniyu, davem, edumazet, kuba, pabeni, horms
Cc: netdev, linux-fsdevel, linux-kernel, jkoolstra
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. The socket option is
inherited by the child accept() socket if set on the listen() socket.
[1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
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/net/af_unix.h | 1 +
include/net/scm.h | 13 +++------
include/uapi/asm-generic/socket.h | 2 ++
net/compat.c | 4 +--
net/core/scm.c | 38 +++++++++++++++++++++++----
net/unix/af_unix.c | 12 ++++++++-
10 files changed, 61 insertions(+), 17 deletions(-)
diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 5ef57f88df6b..946a5fad2691 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -155,6 +155,8 @@
#define SO_INQ 84
#define SCM_INQ SO_INQ
+#define SO_RIGHTS_NOTRUNC 85
+
#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 72fb1b006da9..f1641dde135f 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -166,6 +166,8 @@
#define SO_INQ 84
#define SCM_INQ SO_INQ
+#define SO_RIGHTS_NOTRUNC 85
+
#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 c16ec36dfee6..f3a3815c7dc2 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -147,6 +147,8 @@
#define SO_INQ 0x4052
#define SCM_INQ SO_INQ
+#define SO_RIGHTS_NOTRUNC 0x4053
+
#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 71befa109e1c..7907f3b1f0ee 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -148,6 +148,8 @@
#define SO_INQ 0x005d
#define SCM_INQ SO_INQ
+#define SO_RIGHTS_NOTRUNC 0x005e
+
#if !defined(__KERNEL__)
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..86ae6bc109ec 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);
@@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg,
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)
-{
- if (!ufd)
- return -EFAULT;
- return receive_fd(f, ufd, flags);
-}
+int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
+ bool notrunc);
#endif /* __LINUX_NET_SCM_H */
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 53b5a8c002b1..84ea7b92936e 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -150,6 +150,8 @@
#define SO_INQ 84
#define SCM_INQ SO_INQ
+#define SO_RIGHTS_NOTRUNC 85
+
#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..f0d44ecdb11f 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -351,7 +351,31 @@ 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)
+int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
+ bool notrunc)
+{
+ int error;
+
+ if (!ufd)
+ return -EFAULT;
+
+ error = security_file_receive(f);
+ if (error)
+ return notrunc ? put_user(error, ufd) : error;
+
+ FD_PREPARE(fdf, flags, get_file(f));
+ if (fdf.err)
+ return fdf.err;
+
+ error = put_user(fd_prepare_fd(fdf), ufd);
+ if (error)
+ return error;
+
+ __receive_sock(fd_prepare_file(fdf));
+ return fd_publish(fdf);
+}
+
+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 +389,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 +566,12 @@ 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;
+
+ u = unix_sk(sock->sk);
+ scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_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 51cbf920130d..03ce23a4ebee 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname)
{
switch (optname) {
case SO_INQ:
+ case SO_RIGHTS_NOTRUNC:
return true;
default:
return false;
@@ -957,6 +958,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;
}
@@ -1746,9 +1755,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
init_peercred(newsk, &peercred);
newu = unix_sk(newsk);
+ otheru = unix_sk(other);
newu->listener = other;
+ newu->scm_rights_notrunc = otheru->scm_rights_notrunc;
RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
- otheru = unix_sk(other);
/* copy address information from listening to new sock
*
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH net-next v6 3/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-08-02 15:12 ` [PATCH net-next v6 3/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
@ 2026-08-04 17:16 ` Kuniyuki Iwashima
2026-08-05 10:09 ` Jori Koolstra
0 siblings, 1 reply; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-08-04 17:16 UTC (permalink / raw)
To: Jori Koolstra
Cc: brauner, cyphar, davem, edumazet, kuba, pabeni, horms, netdev,
linux-fsdevel, linux-kernel
On Sun, Aug 2, 2026 at 8:11 AM 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. The socket option is
> inherited by the child accept() socket if set on the listen() socket.
>
> [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
>
> Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org>
> Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
> ---
> 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/net/af_unix.h | 1 +
> include/net/scm.h | 13 +++------
> include/uapi/asm-generic/socket.h | 2 ++
> net/compat.c | 4 +--
> net/core/scm.c | 38 +++++++++++++++++++++++----
> net/unix/af_unix.c | 12 ++++++++-
> 10 files changed, 61 insertions(+), 17 deletions(-)
>
> diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
> index 5ef57f88df6b..946a5fad2691 100644
> --- a/arch/alpha/include/uapi/asm/socket.h
> +++ b/arch/alpha/include/uapi/asm/socket.h
> @@ -155,6 +155,8 @@
> #define SO_INQ 84
> #define SCM_INQ SO_INQ
>
> +#define SO_RIGHTS_NOTRUNC 85
> +
> #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 72fb1b006da9..f1641dde135f 100644
> --- a/arch/mips/include/uapi/asm/socket.h
> +++ b/arch/mips/include/uapi/asm/socket.h
> @@ -166,6 +166,8 @@
> #define SO_INQ 84
> #define SCM_INQ SO_INQ
>
> +#define SO_RIGHTS_NOTRUNC 85
> +
> #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 c16ec36dfee6..f3a3815c7dc2 100644
> --- a/arch/parisc/include/uapi/asm/socket.h
> +++ b/arch/parisc/include/uapi/asm/socket.h
> @@ -147,6 +147,8 @@
> #define SO_INQ 0x4052
> #define SCM_INQ SO_INQ
>
> +#define SO_RIGHTS_NOTRUNC 0x4053
> +
> #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 71befa109e1c..7907f3b1f0ee 100644
> --- a/arch/sparc/include/uapi/asm/socket.h
> +++ b/arch/sparc/include/uapi/asm/socket.h
> @@ -148,6 +148,8 @@
> #define SO_INQ 0x005d
> #define SCM_INQ SO_INQ
>
> +#define SO_RIGHTS_NOTRUNC 0x005e
> +
> #if !defined(__KERNEL__)
>
>
> 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..86ae6bc109ec 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);
> @@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg,
> 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)
> -{
> - if (!ufd)
> - return -EFAULT;
> - return receive_fd(f, ufd, flags);
> -}
> +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> + bool notrunc);
>
> #endif /* __LINUX_NET_SCM_H */
>
> diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
> index 53b5a8c002b1..84ea7b92936e 100644
> --- a/include/uapi/asm-generic/socket.h
> +++ b/include/uapi/asm-generic/socket.h
> @@ -150,6 +150,8 @@
> #define SO_INQ 84
> #define SCM_INQ SO_INQ
>
> +#define SO_RIGHTS_NOTRUNC 85
> +
> #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..f0d44ecdb11f 100644
> --- a/net/core/scm.c
> +++ b/net/core/scm.c
> @@ -351,7 +351,31 @@ 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)
> +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> + bool notrunc)
> +{
> + int error;
> +
> + if (!ufd)
> + return -EFAULT;
> +
> + error = security_file_receive(f);
> + if (error)
> + return notrunc ? put_user(error, ufd) : error;
> +
> + FD_PREPARE(fdf, flags, get_file(f));
> + if (fdf.err)
> + return fdf.err;
> +
> + error = put_user(fd_prepare_fd(fdf), ufd);
> + if (error)
> + return error;
> +
> + __receive_sock(fd_prepare_file(fdf));
> + return fd_publish(fdf);
> +}
> +
> +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 +389,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 +566,12 @@ 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;
> +
> + u = unix_sk(sock->sk);
> + scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_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 51cbf920130d..03ce23a4ebee 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname)
> {
> switch (optname) {
> case SO_INQ:
> + case SO_RIGHTS_NOTRUNC:
> return true;
> default:
> return false;
> @@ -957,6 +958,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;
> }
> @@ -1746,9 +1755,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
> init_peercred(newsk, &peercred);
>
> newu = unix_sk(newsk);
> + otheru = unix_sk(other);
> newu->listener = other;
> + newu->scm_rights_notrunc = otheru->scm_rights_notrunc;
nit: READ_ONCE() is needed here.
> RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
> - otheru = unix_sk(other);
>
> /* copy address information from listening to new sock
> *
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH net-next v6 3/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-08-04 17:16 ` Kuniyuki Iwashima
@ 2026-08-05 10:09 ` Jori Koolstra
2026-08-05 16:27 ` Kuniyuki Iwashima
0 siblings, 1 reply; 12+ messages in thread
From: Jori Koolstra @ 2026-08-05 10:09 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: brauner, cyphar, davem, edumazet, kuba, pabeni, horms, netdev,
linux-fsdevel, linux-kernel
> Op 04-08-2026 19:16 CEST schreef Kuniyuki Iwashima <kuniyu@google.com>:
>
>
> On Sun, Aug 2, 2026 at 8:11 AM 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. The socket option is
> > inherited by the child accept() socket if set on the listen() socket.
> >
> > [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
> >
> > Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org>
> > Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
> > ---
> > 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/net/af_unix.h | 1 +
> > include/net/scm.h | 13 +++------
> > include/uapi/asm-generic/socket.h | 2 ++
> > net/compat.c | 4 +--
> > net/core/scm.c | 38 +++++++++++++++++++++++----
> > net/unix/af_unix.c | 12 ++++++++-
> > 10 files changed, 61 insertions(+), 17 deletions(-)
> >
> > diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
> > index 5ef57f88df6b..946a5fad2691 100644
> > --- a/arch/alpha/include/uapi/asm/socket.h
> > +++ b/arch/alpha/include/uapi/asm/socket.h
> > @@ -155,6 +155,8 @@
> > #define SO_INQ 84
> > #define SCM_INQ SO_INQ
> >
> > +#define SO_RIGHTS_NOTRUNC 85
> > +
> > #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 72fb1b006da9..f1641dde135f 100644
> > --- a/arch/mips/include/uapi/asm/socket.h
> > +++ b/arch/mips/include/uapi/asm/socket.h
> > @@ -166,6 +166,8 @@
> > #define SO_INQ 84
> > #define SCM_INQ SO_INQ
> >
> > +#define SO_RIGHTS_NOTRUNC 85
> > +
> > #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 c16ec36dfee6..f3a3815c7dc2 100644
> > --- a/arch/parisc/include/uapi/asm/socket.h
> > +++ b/arch/parisc/include/uapi/asm/socket.h
> > @@ -147,6 +147,8 @@
> > #define SO_INQ 0x4052
> > #define SCM_INQ SO_INQ
> >
> > +#define SO_RIGHTS_NOTRUNC 0x4053
> > +
> > #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 71befa109e1c..7907f3b1f0ee 100644
> > --- a/arch/sparc/include/uapi/asm/socket.h
> > +++ b/arch/sparc/include/uapi/asm/socket.h
> > @@ -148,6 +148,8 @@
> > #define SO_INQ 0x005d
> > #define SCM_INQ SO_INQ
> >
> > +#define SO_RIGHTS_NOTRUNC 0x005e
> > +
> > #if !defined(__KERNEL__)
> >
> >
> > 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..86ae6bc109ec 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);
> > @@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg,
> > 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)
> > -{
> > - if (!ufd)
> > - return -EFAULT;
> > - return receive_fd(f, ufd, flags);
> > -}
> > +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> > + bool notrunc);
> >
> > #endif /* __LINUX_NET_SCM_H */
> >
> > diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
> > index 53b5a8c002b1..84ea7b92936e 100644
> > --- a/include/uapi/asm-generic/socket.h
> > +++ b/include/uapi/asm-generic/socket.h
> > @@ -150,6 +150,8 @@
> > #define SO_INQ 84
> > #define SCM_INQ SO_INQ
> >
> > +#define SO_RIGHTS_NOTRUNC 85
> > +
> > #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..f0d44ecdb11f 100644
> > --- a/net/core/scm.c
> > +++ b/net/core/scm.c
> > @@ -351,7 +351,31 @@ 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)
> > +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> > + bool notrunc)
> > +{
> > + int error;
> > +
> > + if (!ufd)
> > + return -EFAULT;
> > +
> > + error = security_file_receive(f);
> > + if (error)
> > + return notrunc ? put_user(error, ufd) : error;
> > +
> > + FD_PREPARE(fdf, flags, get_file(f));
> > + if (fdf.err)
> > + return fdf.err;
> > +
> > + error = put_user(fd_prepare_fd(fdf), ufd);
> > + if (error)
> > + return error;
> > +
> > + __receive_sock(fd_prepare_file(fdf));
> > + return fd_publish(fdf);
> > +}
> > +
> > +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 +389,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 +566,12 @@ 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;
> > +
> > + u = unix_sk(sock->sk);
> > + scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_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 51cbf920130d..03ce23a4ebee 100644
> > --- a/net/unix/af_unix.c
> > +++ b/net/unix/af_unix.c
> > @@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname)
> > {
> > switch (optname) {
> > case SO_INQ:
> > + case SO_RIGHTS_NOTRUNC:
> > return true;
> > default:
> > return false;
> > @@ -957,6 +958,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;
> > }
> > @@ -1746,9 +1755,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
> > init_peercred(newsk, &peercred);
> >
> > newu = unix_sk(newsk);
> > + otheru = unix_sk(other);
> > newu->listener = other;
> > + newu->scm_rights_notrunc = otheru->scm_rights_notrunc;
>
> nit: READ_ONCE() is needed here.
>
Yeah, I was thinking whether to add that or not. We have
newsk->sk_scm_recv_flags = other->sk_scm_recv_flags;
earlier, so without READ_ONCE(), and earlier there is
unix_state_lock(other);
which should make this assignment safe. However, that does not protect necessarily
against compiler mangling. Then again, we are reading a bool here, so isn't this
a theoretical concern mostly?
I am happy to change it though (if only to signal intent), but then the access to
sk_scm_recv_flags should also be READ_ONCE(), I believe, or it will be needlessly
confusing why it is there for one access but not the other.
Thanks,
Jori.
>
>
> > RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
> > - otheru = unix_sk(other);
> >
> > /* copy address information from listening to new sock
> > *
> > --
> > 2.55.0
> >
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH net-next v6 3/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
2026-08-05 10:09 ` Jori Koolstra
@ 2026-08-05 16:27 ` Kuniyuki Iwashima
0 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-08-05 16:27 UTC (permalink / raw)
To: Jori Koolstra
Cc: brauner, cyphar, davem, edumazet, kuba, pabeni, horms, netdev,
linux-fsdevel, linux-kernel
On Wed, Aug 5, 2026 at 3:09 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote:
>
>
> > Op 04-08-2026 19:16 CEST schreef Kuniyuki Iwashima <kuniyu@google.com>:
> >
> >
> > On Sun, Aug 2, 2026 at 8:11 AM 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. The socket option is
> > > inherited by the child accept() socket if set on the listen() socket.
> > >
> > > [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights
> > >
> > > Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org>
> > > Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
> > > ---
> > > 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/net/af_unix.h | 1 +
> > > include/net/scm.h | 13 +++------
> > > include/uapi/asm-generic/socket.h | 2 ++
> > > net/compat.c | 4 +--
> > > net/core/scm.c | 38 +++++++++++++++++++++++----
> > > net/unix/af_unix.c | 12 ++++++++-
> > > 10 files changed, 61 insertions(+), 17 deletions(-)
> > >
> > > diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
> > > index 5ef57f88df6b..946a5fad2691 100644
> > > --- a/arch/alpha/include/uapi/asm/socket.h
> > > +++ b/arch/alpha/include/uapi/asm/socket.h
> > > @@ -155,6 +155,8 @@
> > > #define SO_INQ 84
> > > #define SCM_INQ SO_INQ
> > >
> > > +#define SO_RIGHTS_NOTRUNC 85
> > > +
> > > #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 72fb1b006da9..f1641dde135f 100644
> > > --- a/arch/mips/include/uapi/asm/socket.h
> > > +++ b/arch/mips/include/uapi/asm/socket.h
> > > @@ -166,6 +166,8 @@
> > > #define SO_INQ 84
> > > #define SCM_INQ SO_INQ
> > >
> > > +#define SO_RIGHTS_NOTRUNC 85
> > > +
> > > #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 c16ec36dfee6..f3a3815c7dc2 100644
> > > --- a/arch/parisc/include/uapi/asm/socket.h
> > > +++ b/arch/parisc/include/uapi/asm/socket.h
> > > @@ -147,6 +147,8 @@
> > > #define SO_INQ 0x4052
> > > #define SCM_INQ SO_INQ
> > >
> > > +#define SO_RIGHTS_NOTRUNC 0x4053
> > > +
> > > #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 71befa109e1c..7907f3b1f0ee 100644
> > > --- a/arch/sparc/include/uapi/asm/socket.h
> > > +++ b/arch/sparc/include/uapi/asm/socket.h
> > > @@ -148,6 +148,8 @@
> > > #define SO_INQ 0x005d
> > > #define SCM_INQ SO_INQ
> > >
> > > +#define SO_RIGHTS_NOTRUNC 0x005e
> > > +
> > > #if !defined(__KERNEL__)
> > >
> > >
> > > 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..86ae6bc109ec 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);
> > > @@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg,
> > > 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)
> > > -{
> > > - if (!ufd)
> > > - return -EFAULT;
> > > - return receive_fd(f, ufd, flags);
> > > -}
> > > +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> > > + bool notrunc);
> > >
> > > #endif /* __LINUX_NET_SCM_H */
> > >
> > > diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
> > > index 53b5a8c002b1..84ea7b92936e 100644
> > > --- a/include/uapi/asm-generic/socket.h
> > > +++ b/include/uapi/asm-generic/socket.h
> > > @@ -150,6 +150,8 @@
> > > #define SO_INQ 84
> > > #define SCM_INQ SO_INQ
> > >
> > > +#define SO_RIGHTS_NOTRUNC 85
> > > +
> > > #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..f0d44ecdb11f 100644
> > > --- a/net/core/scm.c
> > > +++ b/net/core/scm.c
> > > @@ -351,7 +351,31 @@ 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)
> > > +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> > > + bool notrunc)
> > > +{
> > > + int error;
> > > +
> > > + if (!ufd)
> > > + return -EFAULT;
> > > +
> > > + error = security_file_receive(f);
> > > + if (error)
> > > + return notrunc ? put_user(error, ufd) : error;
> > > +
> > > + FD_PREPARE(fdf, flags, get_file(f));
> > > + if (fdf.err)
> > > + return fdf.err;
> > > +
> > > + error = put_user(fd_prepare_fd(fdf), ufd);
> > > + if (error)
> > > + return error;
> > > +
> > > + __receive_sock(fd_prepare_file(fdf));
> > > + return fd_publish(fdf);
> > > +}
> > > +
> > > +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 +389,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 +566,12 @@ 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;
> > > +
> > > + u = unix_sk(sock->sk);
> > > + scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_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 51cbf920130d..03ce23a4ebee 100644
> > > --- a/net/unix/af_unix.c
> > > +++ b/net/unix/af_unix.c
> > > @@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname)
> > > {
> > > switch (optname) {
> > > case SO_INQ:
> > > + case SO_RIGHTS_NOTRUNC:
> > > return true;
> > > default:
> > > return false;
> > > @@ -957,6 +958,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;
> > > }
> > > @@ -1746,9 +1755,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
> > > init_peercred(newsk, &peercred);
> > >
> > > newu = unix_sk(newsk);
> > > + otheru = unix_sk(other);
> > > newu->listener = other;
> > > + newu->scm_rights_notrunc = otheru->scm_rights_notrunc;
> >
> > nit: READ_ONCE() is needed here.
> >
>
> Yeah, I was thinking whether to add that or not. We have
>
> newsk->sk_scm_recv_flags = other->sk_scm_recv_flags;
>
> earlier, so without READ_ONCE(), and earlier there is
>
> unix_state_lock(other);
>
> which should make this assignment safe.
unix_setsockopt() is called without any lock, and unix_state_lock()
does not protect the values, so that's why we need WRITE_ONCE()
and READ_ONCE().
> However, that does not protect necessarily
> against compiler mangling. Then again, we are reading a bool here, so isn't this
> a theoretical concern mostly?
Yes, but KCSAN will detect it (and syzbot will complain).
>
> I am happy to change it though (if only to signal intent), but then the access to
> sk_scm_recv_flags should also be READ_ONCE(),
This has a bit different context as its actual value, each
sk_scm_XXX in union, is a bit field, so we don't need to care
about any load-tearing even though the writer is under yet another
lock, lock_sock().
> I believe, or it will be needlessly
> confusing why it is there for one access but not the other.
>
> Thanks,
> Jori.
>
> >
> >
> > > RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
> > > - otheru = unix_sk(other);
> > >
> > > /* copy address information from listening to new sock
> > > *
> > > --
> > > 2.55.0
> > >
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v6 4/4] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-08-02 15:12 [PATCH net-next v6 0/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
` (2 preceding siblings ...)
2026-08-02 15:12 ` [PATCH net-next v6 3/4] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Jori Koolstra
@ 2026-08-02 15:12 ` Jori Koolstra
2026-08-04 17:26 ` Kuniyuki Iwashima
3 siblings, 1 reply; 12+ messages in thread
From: Jori Koolstra @ 2026-08-02 15:12 UTC (permalink / raw)
To: brauner, cyphar, kuniyu, davem, edumazet, kuba, pabeni, horms
Cc: netdev, linux-fsdevel, linux-kernel, jkoolstra
Tests SCM_RIGHTS fd passing on a socket with the new socket option
SO_RIGHTS_NOTRUNC turned on. To hook into the security_file_receive()
call, BPF is used. The BPF program shares a hashmap with userspace that
lists the inos to be blocked (of the receiver tgid).
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
.../testing/selftests/net/af_unix/.gitignore | 2 +
tools/testing/selftests/net/af_unix/Makefile | 8 +
tools/testing/selftests/net/af_unix/config | 7 +
.../net/af_unix/scm_rights_denial_lsm.bpf.c | 36 +++
.../net/af_unix/scm_rights_denial_lsm.c | 285 ++++++++++++++++++
5 files changed, 338 insertions(+)
create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
index 973176644103..954f0958dd03 100644
--- a/tools/testing/selftests/net/af_unix/.gitignore
+++ b/tools/testing/selftests/net/af_unix/.gitignore
@@ -3,6 +3,8 @@ msg_oob
scm_inq
scm_pidfd
scm_rights
+scm_rights_denial_lsm
+scm_rights_denial_lsm.bpf.o
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 57d159803a3a..a66f10fb0c23 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -11,10 +11,18 @@ TEST_GEN_PROGS := \
scm_inq \
scm_pidfd \
scm_rights \
+ scm_rights_denial_lsm \
so_peek_off \
unix_connect \
unix_connreset \
unix_listen \
# end of TEST_GEN_PROGS
+TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o
+
include ../../lib.mk
+include ../bpf.mk
+
+$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)
+$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include
+$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz
diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config
index 41dbb03c747e..468f7a0bb64e 100644
--- a/tools/testing/selftests/net/af_unix/config
+++ b/tools/testing/selftests/net/af_unix/config
@@ -2,3 +2,10 @@ CONFIG_AF_UNIX_OOB=y
CONFIG_UNIX=y
CONFIG_UNIX_DIAG=m
CONFIG_USER_NS=y
+CONFIG_BPF=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_BPF_EVENTS=y
+CONFIG_BPF_JIT=y
+CONFIG_SECURITY=y
+CONFIG_BPF_LSM=y
+CONFIG_DEBUG_INFO_BTF=y
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
new file mode 100644
index 000000000000..4f2414465bfd
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+struct inode {
+ unsigned long i_ino;
+} __attribute__((preserve_access_index));
+
+struct file {
+ struct inode *f_inode;
+} __attribute__((preserve_access_index));
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 16);
+ __type(key, __u64); /* inode number */
+ __type(value, __u32); /* tgid of the receiver being tested */
+} denied_inodes SEC(".maps");
+
+SEC("lsm/file_receive")
+int BPF_PROG(scm_rights_deny, struct file *file)
+{
+ __u32 tgid = bpf_get_current_pid_tgid() >> 32;
+ __u64 ino = file->f_inode->i_ino;
+ __u32 *owner;
+
+ owner = bpf_map_lookup_elem(&denied_inodes, &ino);
+ if (owner && *owner == tgid)
+ return -EPERM;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
new file mode 100644
index 000000000000..941b7decf798
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+
+#include "kselftest_harness.h"
+
+#ifndef SO_RIGHTS_NOTRUNC
+#define SO_RIGHTS_NOTRUNC 85
+#endif
+
+#define NR_FILES 2
+
+/* Per-file content, so a received fd can be matched to the file sent */
+#define SECRET(n) "secret %d", (n)
+
+/* Indices into the socketpair */
+#define SK_SENDER 0
+#define SK_RECEIVER 1
+
+FIXTURE(scm_rights_denial_bpf)
+{
+ struct bpf_object *obj;
+ struct bpf_link *link;
+ int map_fd;
+ int sk[2];
+ int files[NR_FILES];
+ __u64 inos[NR_FILES];
+ char paths[NR_FILES][64];
+};
+
+FIXTURE_VARIANT(scm_rights_denial_bpf)
+{
+ int sock_type;
+};
+
+FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, stream)
+{
+ .sock_type = SOCK_STREAM,
+};
+
+FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, dgram)
+{
+ .sock_type = SOCK_DGRAM,
+};
+
+FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, seqpacket)
+{
+ .sock_type = SOCK_SEQPACKET,
+};
+
+FIXTURE_SETUP(scm_rights_denial_bpf)
+{
+ struct bpf_program *prog;
+ char lsms[256] = {};
+ int i, fd;
+
+ if (geteuid() != 0)
+ SKIP(return, "requires root");
+
+ fd = open("/sys/kernel/security/lsm", O_RDONLY);
+ ASSERT_GE(fd, 0);
+ ASSERT_LT(0, read(fd, lsms, sizeof(lsms) - 1));
+ close(fd);
+
+ if (!strstr(lsms, "bpf"))
+ SKIP(return, "BPF LSM not active (boot with lsm=...,bpf)");
+
+ self->obj = bpf_object__open_file("scm_rights_denial_lsm.bpf.o", NULL);
+ ASSERT_NE(NULL, self->obj);
+ ASSERT_EQ(0, bpf_object__load(self->obj));
+
+ prog = bpf_object__find_program_by_name(self->obj, "scm_rights_deny");
+ ASSERT_NE(NULL, prog);
+
+ self->link = bpf_program__attach_lsm(prog);
+ ASSERT_NE(NULL, self->link);
+
+ self->map_fd = bpf_object__find_map_fd_by_name(self->obj,
+ "denied_inodes");
+ ASSERT_GE(self->map_fd, 0);
+
+ ASSERT_EQ(0, socketpair(AF_UNIX, variant->sock_type, 0, self->sk));
+
+ for (i = 0; i < NR_FILES; i++) {
+ struct stat st;
+
+ snprintf(self->paths[i], sizeof(self->paths[i]),
+ "/tmp/scm_rights_denial_bpf.%d.XXXXXX", i);
+ self->files[i] = mkstemp(self->paths[i]);
+ ASSERT_GE(self->files[i], 0);
+
+ ASSERT_LT(0, dprintf(self->files[i], SECRET(i)));
+
+ ASSERT_EQ(0, fstat(self->files[i], &st));
+ self->inos[i] = st.st_ino;
+ }
+}
+
+FIXTURE_TEARDOWN(scm_rights_denial_bpf)
+{
+ bpf_link__destroy(self->link);
+ bpf_object__close(self->obj);
+
+ for (int i = 0; i < NR_FILES; i++) {
+ if (self->files[i] >= 0) {
+ close(self->files[i]);
+ unlink(self->paths[i]);
+ }
+ }
+
+ close(self->sk[SK_SENDER]);
+ close(self->sk[SK_RECEIVER]);
+}
+
+static int deny_inode(int map_fd, __u64 ino)
+{
+ __u32 tgid = getpid();
+
+ return bpf_map_update_elem(map_fd, &ino, &tgid, BPF_ANY);
+}
+
+static int set_notrunc(int sk)
+{
+ int one = 1;
+
+ return setsockopt(sk, SOL_SOCKET, SO_RIGHTS_NOTRUNC,
+ &one, sizeof(one));
+}
+
+static int send_fds(int sk, int *fds, int n)
+{
+ char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))] = {};
+ char data = 'x';
+ struct iovec iov = {
+ .iov_base = &data,
+ .iov_len = sizeof(data),
+ };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = ctrl,
+ .msg_controllen = CMSG_SPACE(n * sizeof(int)),
+ };
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(n * sizeof(int));
+ memcpy(CMSG_DATA(cmsg), fds, n * sizeof(int));
+
+ return sendmsg(sk, &msg, 0);
+}
+
+static int recv_fd_slots(int sk, int *slots, int *msg_flags)
+{
+ int nr_slots;
+ char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))];
+ char data;
+ struct iovec iov = {
+ .iov_base = &data,
+ .iov_len = sizeof(data),
+ };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = ctrl,
+ .msg_controllen = sizeof(ctrl),
+ };
+ struct cmsghdr *cmsg;
+
+ if (recvmsg(sk, &msg, 0) < 0)
+ return -1;
+
+ *msg_flags = msg.msg_flags;
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (!cmsg)
+ return 0;
+
+ nr_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ memcpy(slots, CMSG_DATA(cmsg), nr_slots * sizeof(int));
+
+ return nr_slots;
+}
+
+/* Prove a received fd works by reading back the file's content. */
+static int check_secret(int fd, int idx)
+{
+ char want[32], got[32] = {};
+
+ snprintf(want, sizeof(want), SECRET(idx));
+ if (pread(fd, got, sizeof(got) - 1, 0) < 0)
+ return -1;
+
+ return strcmp(want, got);
+}
+
+TEST_F(scm_rights_denial_bpf, all_allowed)
+{
+ int slots[NR_FILES], nr_slots, flags, i;
+
+ ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
+ ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
+ nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
+
+ ASSERT_EQ(NR_FILES, nr_slots);
+ EXPECT_EQ(0, flags & MSG_CTRUNC);
+
+ for (i = 0; i < NR_FILES; i++) {
+ ASSERT_GE(slots[i], 0);
+ EXPECT_EQ(0, check_secret(slots[i], i));
+ close(slots[i]);
+ }
+}
+
+TEST_F(scm_rights_denial_bpf, first_denied)
+{
+ int slots[NR_FILES], nr_slots, flags;
+
+ ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[0]));
+
+ ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
+ ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
+ nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
+
+ ASSERT_EQ(NR_FILES, nr_slots);
+ EXPECT_EQ(0, flags & MSG_CTRUNC);
+ EXPECT_EQ(-EPERM, slots[0]);
+
+ ASSERT_GE(slots[1], 0);
+ EXPECT_EQ(0, check_secret(slots[1], 1));
+ close(slots[1]);
+}
+
+TEST_F(scm_rights_denial_bpf, all_denied)
+{
+ int slots[NR_FILES], nr_slots, flags, i;
+
+ for (i = 0; i < NR_FILES; i++)
+ ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[i]));
+
+ ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
+ ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
+ nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
+
+ ASSERT_EQ(NR_FILES, nr_slots);
+ EXPECT_EQ(0, flags & MSG_CTRUNC);
+
+ for (i = 0; i < NR_FILES; i++)
+ EXPECT_EQ(-EPERM, slots[i]);
+}
+
+TEST_F(scm_rights_denial_bpf, denied_without_notrunc)
+{
+ int slots[NR_FILES], nr_slots, flags;
+
+ /*
+ * Baseline behaviour without SO_RIGHTS_NOTRUNC: the fd array is
+ * truncated at the first denied fd and MSG_CTRUNC is set.
+ */
+ ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[1]));
+
+ ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
+ nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
+
+ ASSERT_EQ(1, nr_slots);
+ EXPECT_NE(0, flags & MSG_CTRUNC);
+
+ ASSERT_GE(slots[0], 0);
+ EXPECT_EQ(0, check_secret(slots[0], 0));
+ close(slots[0]);
+}
+
+TEST_HARNESS_MAIN
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH net-next v6 4/4] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-08-02 15:12 ` [PATCH net-next v6 4/4] selftest: Add tests for " Jori Koolstra
@ 2026-08-04 17:26 ` Kuniyuki Iwashima
2026-08-05 10:18 ` Jori Koolstra
0 siblings, 1 reply; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-08-04 17:26 UTC (permalink / raw)
To: Jori Koolstra
Cc: brauner, cyphar, davem, edumazet, kuba, pabeni, horms, netdev,
linux-fsdevel, linux-kernel
On Sun, Aug 2, 2026 at 8:11 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote:
>
> Tests SCM_RIGHTS fd passing on a socket with the new socket option
> SO_RIGHTS_NOTRUNC turned on. To hook into the security_file_receive()
> call, BPF is used. The BPF program shares a hashmap with userspace that
> lists the inos to be blocked (of the receiver tgid).
>
> Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
> ---
> .../testing/selftests/net/af_unix/.gitignore | 2 +
> tools/testing/selftests/net/af_unix/Makefile | 8 +
> tools/testing/selftests/net/af_unix/config | 7 +
> .../net/af_unix/scm_rights_denial_lsm.bpf.c | 36 +++
> .../net/af_unix/scm_rights_denial_lsm.c | 285 ++++++++++++++++++
> 5 files changed, 338 insertions(+)
> create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
> create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
>
> diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
> index 973176644103..954f0958dd03 100644
> --- a/tools/testing/selftests/net/af_unix/.gitignore
> +++ b/tools/testing/selftests/net/af_unix/.gitignore
> @@ -3,6 +3,8 @@ msg_oob
> scm_inq
> scm_pidfd
> scm_rights
> +scm_rights_denial_lsm
> +scm_rights_denial_lsm.bpf.o
> 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 57d159803a3a..a66f10fb0c23 100644
> --- a/tools/testing/selftests/net/af_unix/Makefile
> +++ b/tools/testing/selftests/net/af_unix/Makefile
> @@ -11,10 +11,18 @@ TEST_GEN_PROGS := \
> scm_inq \
> scm_pidfd \
> scm_rights \
> + scm_rights_denial_lsm \
> so_peek_off \
> unix_connect \
> unix_connreset \
> unix_listen \
> # end of TEST_GEN_PROGS
>
> +TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o
> +
> include ../../lib.mk
> +include ../bpf.mk
> +
> +$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)
> +$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include
> +$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz
> diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config
> index 41dbb03c747e..468f7a0bb64e 100644
> --- a/tools/testing/selftests/net/af_unix/config
> +++ b/tools/testing/selftests/net/af_unix/config
> @@ -2,3 +2,10 @@ CONFIG_AF_UNIX_OOB=y
> CONFIG_UNIX=y
> CONFIG_UNIX_DIAG=m
> CONFIG_USER_NS=y
> +CONFIG_BPF=y
> +CONFIG_BPF_SYSCALL=y
> +CONFIG_BPF_EVENTS=y
> +CONFIG_BPF_JIT=y
> +CONFIG_SECURITY=y
> +CONFIG_BPF_LSM=y
> +CONFIG_DEBUG_INFO_BTF=y
Please sort configs as CI complains.
https://patchwork.kernel.org/project/netdevbpf/patch/20260802151212.3294591-5-jkoolstra@xs4all.nl/
> diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
> new file mode 100644
> index 000000000000..4f2414465bfd
> --- /dev/null
> +++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
> @@ -0,0 +1,36 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <linux/bpf.h>
> +#include <linux/errno.h>
> +#include <bpf/bpf_helpers.h>
> +#include <bpf/bpf_tracing.h>
> +
> +char _license[] SEC("license") = "GPL";
> +
> +struct inode {
> + unsigned long i_ino;
> +} __attribute__((preserve_access_index));
> +
> +struct file {
> + struct inode *f_inode;
> +} __attribute__((preserve_access_index));
> +
> +struct {
> + __uint(type, BPF_MAP_TYPE_HASH);
> + __uint(max_entries, 16);
> + __type(key, __u64); /* inode number */
> + __type(value, __u32); /* tgid of the receiver being tested */
> +} denied_inodes SEC(".maps");
> +
> +SEC("lsm/file_receive")
> +int BPF_PROG(scm_rights_deny, struct file *file)
> +{
> + __u32 tgid = bpf_get_current_pid_tgid() >> 32;
> + __u64 ino = file->f_inode->i_ino;
> + __u32 *owner;
> +
> + owner = bpf_map_lookup_elem(&denied_inodes, &ino);
> + if (owner && *owner == tgid)
> + return -EPERM;
> +
> + return 0;
> +}
> diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
> new file mode 100644
> index 000000000000..941b7decf798
> --- /dev/null
> +++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
> @@ -0,0 +1,285 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#define _GNU_SOURCE
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/socket.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +
> +#include <bpf/bpf.h>
> +#include <bpf/libbpf.h>
> +
> +#include "kselftest_harness.h"
> +
> +#ifndef SO_RIGHTS_NOTRUNC
> +#define SO_RIGHTS_NOTRUNC 85
> +#endif
> +
> +#define NR_FILES 2
> +
> +/* Per-file content, so a received fd can be matched to the file sent */
> +#define SECRET(n) "secret %d", (n)
> +
> +/* Indices into the socketpair */
> +#define SK_SENDER 0
> +#define SK_RECEIVER 1
> +
> +FIXTURE(scm_rights_denial_bpf)
> +{
> + struct bpf_object *obj;
> + struct bpf_link *link;
> + int map_fd;
> + int sk[2];
> + int files[NR_FILES];
> + __u64 inos[NR_FILES];
> + char paths[NR_FILES][64];
> +};
> +
> +FIXTURE_VARIANT(scm_rights_denial_bpf)
> +{
> + int sock_type;
> +};
> +
> +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, stream)
> +{
> + .sock_type = SOCK_STREAM,
> +};
> +
> +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, dgram)
> +{
> + .sock_type = SOCK_DGRAM,
> +};
> +
> +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, seqpacket)
> +{
> + .sock_type = SOCK_SEQPACKET,
> +};
> +
> +FIXTURE_SETUP(scm_rights_denial_bpf)
> +{
> + struct bpf_program *prog;
> + char lsms[256] = {};
> + int i, fd;
> +
> + if (geteuid() != 0)
> + SKIP(return, "requires root");
> +
> + fd = open("/sys/kernel/security/lsm", O_RDONLY);
> + ASSERT_GE(fd, 0);
> + ASSERT_LT(0, read(fd, lsms, sizeof(lsms) - 1));
> + close(fd);
> +
> + if (!strstr(lsms, "bpf"))
> + SKIP(return, "BPF LSM not active (boot with lsm=...,bpf)");
> +
> + self->obj = bpf_object__open_file("scm_rights_denial_lsm.bpf.o", NULL);
> + ASSERT_NE(NULL, self->obj);
> + ASSERT_EQ(0, bpf_object__load(self->obj));
> +
> + prog = bpf_object__find_program_by_name(self->obj, "scm_rights_deny");
> + ASSERT_NE(NULL, prog);
> +
> + self->link = bpf_program__attach_lsm(prog);
> + ASSERT_NE(NULL, self->link);
> +
> + self->map_fd = bpf_object__find_map_fd_by_name(self->obj,
> + "denied_inodes");
> + ASSERT_GE(self->map_fd, 0);
> +
> + ASSERT_EQ(0, socketpair(AF_UNIX, variant->sock_type, 0, self->sk));
> +
> + for (i = 0; i < NR_FILES; i++) {
> + struct stat st;
> +
> + snprintf(self->paths[i], sizeof(self->paths[i]),
> + "/tmp/scm_rights_denial_bpf.%d.XXXXXX", i);
> + self->files[i] = mkstemp(self->paths[i]);
> + ASSERT_GE(self->files[i], 0);
> +
> + ASSERT_LT(0, dprintf(self->files[i], SECRET(i)));
> +
> + ASSERT_EQ(0, fstat(self->files[i], &st));
> + self->inos[i] = st.st_ino;
> + }
> +}
> +
> +FIXTURE_TEARDOWN(scm_rights_denial_bpf)
> +{
> + bpf_link__destroy(self->link);
> + bpf_object__close(self->obj);
> +
> + for (int i = 0; i < NR_FILES; i++) {
> + if (self->files[i] >= 0) {
> + close(self->files[i]);
> + unlink(self->paths[i]);
> + }
> + }
> +
> + close(self->sk[SK_SENDER]);
> + close(self->sk[SK_RECEIVER]);
> +}
> +
> +static int deny_inode(int map_fd, __u64 ino)
> +{
> + __u32 tgid = getpid();
> +
> + return bpf_map_update_elem(map_fd, &ino, &tgid, BPF_ANY);
> +}
> +
> +static int set_notrunc(int sk)
> +{
> + int one = 1;
> +
> + return setsockopt(sk, SOL_SOCKET, SO_RIGHTS_NOTRUNC,
> + &one, sizeof(one));
> +}
> +
> +static int send_fds(int sk, int *fds, int n)
> +{
> + char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))] = {};
> + char data = 'x';
> + struct iovec iov = {
> + .iov_base = &data,
> + .iov_len = sizeof(data),
> + };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + .msg_control = ctrl,
> + .msg_controllen = CMSG_SPACE(n * sizeof(int)),
> + };
> + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
> +
> + cmsg->cmsg_level = SOL_SOCKET;
> + cmsg->cmsg_type = SCM_RIGHTS;
> + cmsg->cmsg_len = CMSG_LEN(n * sizeof(int));
> + memcpy(CMSG_DATA(cmsg), fds, n * sizeof(int));
> +
> + return sendmsg(sk, &msg, 0);
ASSERT_EQ(1, ret) and return 0/-1 explicitly, and..
> +}
> +
> +static int recv_fd_slots(int sk, int *slots, int *msg_flags)
> +{
> + int nr_slots;
> + char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))];
> + char data;
> + struct iovec iov = {
> + .iov_base = &data,
> + .iov_len = sizeof(data),
> + };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + .msg_control = ctrl,
> + .msg_controllen = sizeof(ctrl),
> + };
> + struct cmsghdr *cmsg;
> +
> + if (recvmsg(sk, &msg, 0) < 0)
> + return -1;
> +
> + *msg_flags = msg.msg_flags;
> +
> + cmsg = CMSG_FIRSTHDR(&msg);
> + if (!cmsg)
> + return 0;
> +
> + nr_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
> + memcpy(slots, CMSG_DATA(cmsg), nr_slots * sizeof(int));
> +
> + return nr_slots;
> +}
> +
> +/* Prove a received fd works by reading back the file's content. */
> +static int check_secret(int fd, int idx)
> +{
> + char want[32], got[32] = {};
> +
> + snprintf(want, sizeof(want), SECRET(idx));
> + if (pread(fd, got, sizeof(got) - 1, 0) < 0)
> + return -1;
> +
> + return strcmp(want, got);
> +}
> +
> +TEST_F(scm_rights_denial_bpf, all_allowed)
> +{
> + int slots[NR_FILES], nr_slots, flags, i;
> +
> + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
> + ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
then here we can use ASSERT_EQ(0, ..)
> + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
> +
> + ASSERT_EQ(NR_FILES, nr_slots);
> + EXPECT_EQ(0, flags & MSG_CTRUNC);
> +
> + for (i = 0; i < NR_FILES; i++) {
> + ASSERT_GE(slots[i], 0);
> + EXPECT_EQ(0, check_secret(slots[i], i));
> + close(slots[i]);
> + }
> +}
> +
> +TEST_F(scm_rights_denial_bpf, first_denied)
> +{
> + int slots[NR_FILES], nr_slots, flags;
> +
> + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[0]));
> +
> + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
> + ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
> + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
> +
> + ASSERT_EQ(NR_FILES, nr_slots);
> + EXPECT_EQ(0, flags & MSG_CTRUNC);
> + EXPECT_EQ(-EPERM, slots[0]);
> +
> + ASSERT_GE(slots[1], 0);
> + EXPECT_EQ(0, check_secret(slots[1], 1));
Check secrets from 1 to NR_FILES just in case.
> + close(slots[1]);
> +}
> +
> +TEST_F(scm_rights_denial_bpf, all_denied)
> +{
> + int slots[NR_FILES], nr_slots, flags, i;
> +
> + for (i = 0; i < NR_FILES; i++)
> + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[i]));
> +
> + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
> + ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
> + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
> +
> + ASSERT_EQ(NR_FILES, nr_slots);
> + EXPECT_EQ(0, flags & MSG_CTRUNC);
> +
> + for (i = 0; i < NR_FILES; i++)
> + EXPECT_EQ(-EPERM, slots[i]);
> +}
> +
> +TEST_F(scm_rights_denial_bpf, denied_without_notrunc)
> +{
> + int slots[NR_FILES], nr_slots, flags;
> +
> + /*
> + * Baseline behaviour without SO_RIGHTS_NOTRUNC: the fd array is
> + * truncated at the first denied fd and MSG_CTRUNC is set.
> + */
> + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[1]));
> +
> + ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
> + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
> +
> + ASSERT_EQ(1, nr_slots);
> + EXPECT_NE(0, flags & MSG_CTRUNC);
> +
> + ASSERT_GE(slots[0], 0);
> + EXPECT_EQ(0, check_secret(slots[0], 0));
> + close(slots[0]);
> +}
> +
> +TEST_HARNESS_MAIN
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH net-next v6 4/4] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-08-04 17:26 ` Kuniyuki Iwashima
@ 2026-08-05 10:18 ` Jori Koolstra
2026-08-05 16:30 ` Kuniyuki Iwashima
0 siblings, 1 reply; 12+ messages in thread
From: Jori Koolstra @ 2026-08-05 10:18 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: brauner, cyphar, davem, edumazet, kuba, pabeni, horms, netdev,
linux-fsdevel, linux-kernel
> Op 04-08-2026 19:26 CEST schreef Kuniyuki Iwashima <kuniyu@google.com>:
>
> > +TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o
> > +
> > include ../../lib.mk
> > +include ../bpf.mk
> > +
> > +$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)
> > +$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include
> > +$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz
> > diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config
> > index 41dbb03c747e..468f7a0bb64e 100644
> > --- a/tools/testing/selftests/net/af_unix/config
> > +++ b/tools/testing/selftests/net/af_unix/config
> > @@ -2,3 +2,10 @@ CONFIG_AF_UNIX_OOB=y
> > CONFIG_UNIX=y
> > CONFIG_UNIX_DIAG=m
> > CONFIG_USER_NS=y
> > +CONFIG_BPF=y
> > +CONFIG_BPF_SYSCALL=y
> > +CONFIG_BPF_EVENTS=y
> > +CONFIG_BPF_JIT=y
> > +CONFIG_SECURITY=y
> > +CONFIG_BPF_LSM=y
> > +CONFIG_DEBUG_INFO_BTF=y
>
> Please sort configs as CI complains.
> https://patchwork.kernel.org/project/netdevbpf/patch/20260802151212.3294591-5-jkoolstra@xs4all.nl/
>
>
Oh that is pretty neat!
I wasn't aware there was a canonical sorting for the config. This should fix it,
LC_ALL=C sort -c tools/testing/selftests/net/af_unix/config
correct?
If I fix these issues today, any chance we can still get this in before the merge window
starts again?
Best,
Jori.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next v6 4/4] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
2026-08-05 10:18 ` Jori Koolstra
@ 2026-08-05 16:30 ` Kuniyuki Iwashima
0 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-08-05 16:30 UTC (permalink / raw)
To: Jori Koolstra
Cc: brauner, cyphar, davem, edumazet, kuba, pabeni, horms, netdev,
linux-fsdevel, linux-kernel
On Wed, Aug 5, 2026 at 3:18 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote:
>
>
> > Op 04-08-2026 19:26 CEST schreef Kuniyuki Iwashima <kuniyu@google.com>:
> >
> > > +TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o
> > > +
> > > include ../../lib.mk
> > > +include ../bpf.mk
> > > +
> > > +$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)
> > > +$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include
> > > +$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz
> > > diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config
> > > index 41dbb03c747e..468f7a0bb64e 100644
> > > --- a/tools/testing/selftests/net/af_unix/config
> > > +++ b/tools/testing/selftests/net/af_unix/config
> > > @@ -2,3 +2,10 @@ CONFIG_AF_UNIX_OOB=y
> > > CONFIG_UNIX=y
> > > CONFIG_UNIX_DIAG=m
> > > CONFIG_USER_NS=y
> > > +CONFIG_BPF=y
> > > +CONFIG_BPF_SYSCALL=y
> > > +CONFIG_BPF_EVENTS=y
> > > +CONFIG_BPF_JIT=y
> > > +CONFIG_SECURITY=y
> > > +CONFIG_BPF_LSM=y
> > > +CONFIG_DEBUG_INFO_BTF=y
> >
> > Please sort configs as CI complains.
> > https://patchwork.kernel.org/project/netdevbpf/patch/20260802151212.3294591-5-jkoolstra@xs4all.nl/
> >
> >
>
> Oh that is pretty neat!
>
> I wasn't aware there was a canonical sorting for the config. This should fix it,
>
> LC_ALL=C sort -c tools/testing/selftests/net/af_unix/config
>
> correct?
Yes, assuming you meant without -c.
>
> If I fix these issues today, any chance we can still get this in before the merge window
> starts again?
I think so.
From the Linus's email, it seems we still have next week for rc8.
https://lore.kernel.org/lkml/CAHk-=whoz5Uy-kmrdzjrpCJCVWW3fni31c-zsHcH7TkOhRhfOA@mail.gmail.com/T/#u
^ permalink raw reply [flat|nested] 12+ messages in thread