AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/02 20:09 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "UNIX",
    "COMPAT",
    "SECURITY"
  ],
  "FocusSymbols": [
    "unix_setsockopt",
    "scm_recv_one_fd",
    "scm_detach_fds",
    "scm_detach_fds_compat",
    "scm_recv_unix"
  ],
  "Reasoning": "The patch introduces a new socket option SO_RIGHTS_NOTRUNC for AF_UNIX sockets. This option modifies the behavior of SCM_RIGHTS when a file descriptor receive is denied by a security module (LSM). Instead of truncating the file descriptor array and setting MSG_CTRUNC, it places the error code in the user's file descriptor array. This involves changes to core AF_UNIX socket option parsing (unix_setsockopt), socket connection state propagation, and the SCM message receiving logic (scm_recv_one_fd, scm_detach_fds, scm_detach_fds_compat, scm_recv_unix). These are reachable core networking paths that should be fuzzed to ensure the new error handling and array manipulation logic does not introduce memory safety issues or leaks.",
  "WorthFuzzing": true
}

1/1 2026/08/02 20:09 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit e5cdb4a936e1a7d68f7361ff775d961416477a35\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Aug 2 20:09:22 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h\nindex 5ef57f88df6b3..946a5fad26912 100644\n--- a/arch/alpha/include/uapi/asm/socket.h\n+++ b/arch/alpha/include/uapi/asm/socket.h\n@@ -155,6 +155,8 @@\n #define SO_INQ\t\t\t84\n #define SCM_INQ\t\t\tSO_INQ\n \n+#define SO_RIGHTS_NOTRUNC      85\n+\n #if !defined(__KERNEL__)\n \n #if __BITS_PER_LONG == 64\ndiff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h\nindex 72fb1b006da93..f1641dde135fc 100644\n--- a/arch/mips/include/uapi/asm/socket.h\n+++ b/arch/mips/include/uapi/asm/socket.h\n@@ -166,6 +166,8 @@\n #define SO_INQ\t\t\t84\n #define SCM_INQ\t\t\tSO_INQ\n \n+#define SO_RIGHTS_NOTRUNC      85\n+\n #if !defined(__KERNEL__)\n \n #if __BITS_PER_LONG == 64\ndiff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h\nindex c16ec36dfee6b..f3a3815c7dc29 100644\n--- a/arch/parisc/include/uapi/asm/socket.h\n+++ b/arch/parisc/include/uapi/asm/socket.h\n@@ -147,6 +147,8 @@\n #define SO_INQ\t\t\t0x4052\n #define SCM_INQ\t\t\tSO_INQ\n \n+#define SO_RIGHTS_NOTRUNC\t0x4053\n+\n #if !defined(__KERNEL__)\n \n #if __BITS_PER_LONG == 64\ndiff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h\nindex 71befa109e1cf..7907f3b1f0ee0 100644\n--- a/arch/sparc/include/uapi/asm/socket.h\n+++ b/arch/sparc/include/uapi/asm/socket.h\n@@ -148,6 +148,8 @@\n #define SO_INQ                   0x005d\n #define SCM_INQ                  SO_INQ\n \n+#define SO_RIGHTS_NOTRUNC        0x005e\n+\n #if !defined(__KERNEL__)\n \n \ndiff --git a/include/net/af_unix.h b/include/net/af_unix.h\nindex 34f53dde65cea..bb1b3dee02e82 100644\n--- a/include/net/af_unix.h\n+++ b/include/net/af_unix.h\n@@ -49,6 +49,7 @@ struct unix_sock {\n \tstruct scm_stat\t\tscm_stat;\n \tint\t\t\tinq_len;\n \tbool\t\t\trecvmsg_inq;\n+\tbool\t\t\tscm_rights_notrunc;\n #if IS_ENABLED(CONFIG_AF_UNIX_OOB)\n \tstruct sk_buff\t\t*oob_skb;\n #endif\ndiff --git a/include/net/scm.h b/include/net/scm.h\nindex c52519669349f..86ae6bc109ec6 100644\n--- a/include/net/scm.h\n+++ b/include/net/scm.h\n@@ -50,8 +50,8 @@ struct scm_cookie {\n #endif\n };\n \n-void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm);\n-void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm);\n+void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);\n+void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);\n int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm);\n void __scm_destroy(struct scm_cookie *scm);\n struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl);\n@@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg,\n void scm_recv_unix(struct socket *sock, struct msghdr *msg,\n \t\t   struct scm_cookie *scm, int flags);\n \n-static inline int scm_recv_one_fd(struct file *f, int __user *ufd,\n-\t\t\t\t  unsigned int flags)\n-{\n-\tif (!ufd)\n-\t\treturn -EFAULT;\n-\treturn receive_fd(f, ufd, flags);\n-}\n+int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,\n+\t\t    bool notrunc);\n \n #endif /* __LINUX_NET_SCM_H */\n \ndiff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h\nindex 53b5a8c002b1e..84ea7b92936e2 100644\n--- a/include/uapi/asm-generic/socket.h\n+++ b/include/uapi/asm-generic/socket.h\n@@ -150,6 +150,8 @@\n #define SO_INQ\t\t\t84\n #define SCM_INQ\t\t\tSO_INQ\n \n+#define SO_RIGHTS_NOTRUNC\t85\n+\n #if !defined(__KERNEL__)\n \n #if __BITS_PER_LONG == 64 || (defined(__x86_64__) \u0026\u0026 defined(__ILP32__))\ndiff --git a/net/compat.c b/net/compat.c\nindex d68cf9c3aad5f..6bdf4a2c9077d 100644\n--- a/net/compat.c\n+++ b/net/compat.c\n@@ -286,7 +286,7 @@ static int scm_max_fds_compat(struct msghdr *msg)\n \treturn (msg-\u003emsg_controllen - sizeof(struct compat_cmsghdr)) / sizeof(int);\n }\n \n-void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)\n+void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)\n {\n \tstruct compat_cmsghdr __user *cm =\n \t\t(struct compat_cmsghdr __user *)msg-\u003emsg_control_user;\n@@ -296,7 +296,7 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)\n \tint err = 0, i;\n \n \tfor (i = 0; i \u003c fdmax; i++) {\n-\t\terr = scm_recv_one_fd(scm-\u003efp-\u003efp[i], cmsg_data + i, o_flags);\n+\t\terr = scm_recv_one_fd(scm-\u003efp-\u003efp[i], cmsg_data + i, o_flags, notrunc);\n \t\tif (err \u003c 0)\n \t\t\tbreak;\n \t}\ndiff --git a/net/core/scm.c b/net/core/scm.c\nindex eec13f50ecaf6..f0d44ecdb11fc 100644\n--- a/net/core/scm.c\n+++ b/net/core/scm.c\n@@ -351,7 +351,31 @@ static int scm_max_fds(struct msghdr *msg)\n \treturn (msg-\u003emsg_controllen - sizeof(struct cmsghdr)) / sizeof(int);\n }\n \n-void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)\n+int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,\n+\t\t    bool notrunc)\n+{\n+\tint error;\n+\n+\tif (!ufd)\n+\t\treturn -EFAULT;\n+\n+\terror = security_file_receive(f);\n+\tif (error)\n+\t\treturn notrunc ? put_user(error, ufd) : error;\n+\n+\tFD_PREPARE(fdf, flags, get_file(f));\n+\tif (fdf.err)\n+\t\treturn fdf.err;\n+\n+\terror = put_user(fd_prepare_fd(fdf), ufd);\n+\tif (error)\n+\t\treturn error;\n+\n+\t__receive_sock(fd_prepare_file(fdf));\n+\treturn fd_publish(fdf);\n+}\n+\n+void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)\n {\n \tstruct cmsghdr __user *cm =\n \t\t(__force struct cmsghdr __user *)msg-\u003emsg_control_user;\n@@ -365,12 +389,12 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)\n \t\treturn;\n \n \tif (msg-\u003emsg_flags \u0026 MSG_CMSG_COMPAT) {\n-\t\tscm_detach_fds_compat(msg, scm);\n+\t\tscm_detach_fds_compat(msg, scm, notrunc);\n \t\treturn;\n \t}\n \n \tfor (i = 0; i \u003c fdmax; i++) {\n-\t\terr = scm_recv_one_fd(scm-\u003efp-\u003efp[i], cmsg_data + i, o_flags);\n+\t\terr = scm_recv_one_fd(scm-\u003efp-\u003efp[i], cmsg_data + i, o_flags, notrunc);\n \t\tif (err \u003c 0)\n \t\t\tbreak;\n \t}\n@@ -523,9 +547,6 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,\n \n \tscm_passec(sk, msg, scm);\n \n-\tif (scm-\u003efp)\n-\t\tscm_detach_fds(msg, scm);\n-\n \treturn true;\n }\n \n@@ -545,6 +566,13 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,\n \tif (!__scm_recv_common(sock-\u003esk, msg, scm, flags))\n \t\treturn;\n \n+\tif (scm-\u003efp) {\n+\t\tstruct unix_sock *u;\n+\n+\t\tu = unix_sk(sock-\u003esk);\n+\t\tscm_detach_fds(msg, scm, READ_ONCE(u-\u003escm_rights_notrunc));\n+\t}\n+\n \tif (sock-\u003esk-\u003esk_scm_pidfd)\n \t\tscm_pidfd_recv(msg, scm);\n \ndiff --git a/net/unix/af_unix.c b/net/unix/af_unix.c\nindex 10ed9421e43aa..03ce23a4ebee1 100644\n--- a/net/unix/af_unix.c\n+++ b/net/unix/af_unix.c\n@@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname)\n {\n \tswitch (optname) {\n \tcase SO_INQ:\n+\tcase SO_RIGHTS_NOTRUNC:\n \t\treturn true;\n \tdefault:\n \t\treturn false;\n@@ -950,13 +951,21 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,\n \tswitch (optname) {\n \tcase SO_INQ:\n \t\tif (sk-\u003esk_type != SOCK_STREAM)\n-\t\t\treturn -EINVAL;\n+\t\t\treturn -ENOPROTOOPT;\n \n \t\tif (val \u003e 1 || val \u003c 0)\n \t\t\treturn -EINVAL;\n \n \t\tWRITE_ONCE(u-\u003erecvmsg_inq, val);\n \t\tbreak;\n+\n+\tcase SO_RIGHTS_NOTRUNC:\n+\t\tif (val \u003e 1 || val \u003c 0)\n+\t\t\treturn -EINVAL;\n+\n+\t\tWRITE_ONCE(u-\u003escm_rights_notrunc, val);\n+\t\tbreak;\n+\n \tdefault:\n \t\treturn -ENOPROTOOPT;\n \t}\n@@ -1006,6 +1015,7 @@ static const struct proto_ops unix_dgram_ops = {\n #endif\n \t.listen =\tsock_no_listen,\n \t.shutdown =\tunix_shutdown,\n+\t.setsockopt =\tunix_setsockopt,\n \t.sendmsg =\tunix_dgram_sendmsg,\n \t.read_skb =\tunix_read_skb,\n \t.recvmsg =\tunix_dgram_recvmsg,\n@@ -1030,6 +1040,7 @@ static const struct proto_ops unix_seqpacket_ops = {\n #endif\n \t.listen =\tunix_listen,\n \t.shutdown =\tunix_shutdown,\n+\t.setsockopt =\tunix_setsockopt,\n \t.sendmsg =\tunix_seqpacket_sendmsg,\n \t.recvmsg =\tunix_seqpacket_recvmsg,\n \t.mmap =\t\tsock_no_mmap,\n@@ -1143,9 +1154,10 @@ static int unix_create(struct net *net, struct socket *sock, int protocol,\n \tif (protocol \u0026\u0026 protocol != PF_UNIX)\n \t\treturn -EPROTONOSUPPORT;\n \n+\tset_bit(SOCK_CUSTOM_SOCKOPT, \u0026sock-\u003eflags);\n+\n \tswitch (sock-\u003etype) {\n \tcase SOCK_STREAM:\n-\t\tset_bit(SOCK_CUSTOM_SOCKOPT, \u0026sock-\u003eflags);\n \t\tsock-\u003eops = \u0026unix_stream_ops;\n \t\tbreak;\n \t\t/*\n@@ -1743,9 +1755,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad\n \tinit_peercred(newsk, \u0026peercred);\n \n \tnewu = unix_sk(newsk);\n+\totheru = unix_sk(other);\n \tnewu-\u003elistener = other;\n+\tnewu-\u003escm_rights_notrunc = otheru-\u003escm_rights_notrunc;\n \tRCU_INIT_POINTER(newsk-\u003esk_wq, \u0026newu-\u003epeer_wq);\n-\totheru = unix_sk(other);\n \n \t/* copy address information from listening to new sock\n \t *\n@@ -1865,8 +1878,7 @@ static int unix_accept(struct socket *sock, struct socket *newsock,\n \tskb_free_datagram(sk, skb);\n \twake_up_interruptible(\u0026unix_sk(sk)-\u003epeer_wait);\n \n-\tif (tsk-\u003esk_type == SOCK_STREAM)\n-\t\tset_bit(SOCK_CUSTOM_SOCKOPT, \u0026newsock-\u003eflags);\n+\tset_bit(SOCK_CUSTOM_SOCKOPT, \u0026newsock-\u003eflags);\n \n \t/* attach accepted sock to socket */\n \tunix_state_lock(tsk);\ndiff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore\nindex 9731766441036..954f0958dd034 100644\n--- a/tools/testing/selftests/net/af_unix/.gitignore\n+++ b/tools/testing/selftests/net/af_unix/.gitignore\n@@ -3,6 +3,8 @@ msg_oob\n scm_inq\n scm_pidfd\n scm_rights\n+scm_rights_denial_lsm\n+scm_rights_denial_lsm.bpf.o\n so_peek_off\n unix_connect\n unix_connreset\ndiff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile\nindex 57d159803a3ab..a66f10fb0c231 100644\n--- a/tools/testing/selftests/net/af_unix/Makefile\n+++ b/tools/testing/selftests/net/af_unix/Makefile\n@@ -11,10 +11,18 @@ TEST_GEN_PROGS := \\\n \tscm_inq \\\n \tscm_pidfd \\\n \tscm_rights \\\n+\tscm_rights_denial_lsm \\\n \tso_peek_off \\\n \tunix_connect \\\n \tunix_connreset \\\n \tunix_listen \\\n # end of TEST_GEN_PROGS\n \n+TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o\n+\n include ../../lib.mk\n+include ../bpf.mk\n+\n+$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)\n+$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include\n+$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz\ndiff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config\nindex 41dbb03c747eb..468f7a0bb64e6 100644\n--- a/tools/testing/selftests/net/af_unix/config\n+++ b/tools/testing/selftests/net/af_unix/config\n@@ -2,3 +2,10 @@ CONFIG_AF_UNIX_OOB=y\n CONFIG_UNIX=y\n CONFIG_UNIX_DIAG=m\n CONFIG_USER_NS=y\n+CONFIG_BPF=y\n+CONFIG_BPF_SYSCALL=y\n+CONFIG_BPF_EVENTS=y\n+CONFIG_BPF_JIT=y\n+CONFIG_SECURITY=y\n+CONFIG_BPF_LSM=y\n+CONFIG_DEBUG_INFO_BTF=y\ndiff --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\nnew file mode 100644\nindex 0000000000000..4f2414465bfd3\n--- /dev/null\n+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c\n@@ -0,0 +1,36 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003clinux/bpf.h\u003e\n+#include \u003clinux/errno.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+struct inode {\n+\tunsigned long i_ino;\n+} __attribute__((preserve_access_index));\n+\n+struct file {\n+\tstruct inode *f_inode;\n+} __attribute__((preserve_access_index));\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_HASH);\n+\t__uint(max_entries, 16);\n+\t__type(key, __u64);\t/* inode number */\n+\t__type(value, __u32);\t/* tgid of the receiver being tested */\n+} denied_inodes SEC(\".maps\");\n+\n+SEC(\"lsm/file_receive\")\n+int BPF_PROG(scm_rights_deny, struct file *file)\n+{\n+\t__u32 tgid = bpf_get_current_pid_tgid() \u003e\u003e 32;\n+\t__u64 ino = file-\u003ef_inode-\u003ei_ino;\n+\t__u32 *owner;\n+\n+\towner = bpf_map_lookup_elem(\u0026denied_inodes, \u0026ino);\n+\tif (owner \u0026\u0026 *owner == tgid)\n+\t\treturn -EPERM;\n+\n+\treturn 0;\n+}\ndiff --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\nnew file mode 100644\nindex 0000000000000..941b7decf7980\n--- /dev/null\n+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c\n@@ -0,0 +1,285 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#define _GNU_SOURCE\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003csys/socket.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/types.h\u003e\n+\n+#include \u003cbpf/bpf.h\u003e\n+#include \u003cbpf/libbpf.h\u003e\n+\n+#include \"kselftest_harness.h\"\n+\n+#ifndef SO_RIGHTS_NOTRUNC\n+#define SO_RIGHTS_NOTRUNC 85\n+#endif\n+\n+#define NR_FILES 2\n+\n+/* Per-file content, so a received fd can be matched to the file sent */\n+#define SECRET(n) \"secret %d\", (n)\n+\n+/* Indices into the socketpair */\n+#define SK_SENDER 0\n+#define SK_RECEIVER 1\n+\n+FIXTURE(scm_rights_denial_bpf)\n+{\n+\tstruct bpf_object *obj;\n+\tstruct bpf_link *link;\n+\tint map_fd;\n+\tint sk[2];\n+\tint files[NR_FILES];\n+\t__u64 inos[NR_FILES];\n+\tchar paths[NR_FILES][64];\n+};\n+\n+FIXTURE_VARIANT(scm_rights_denial_bpf)\n+{\n+\tint sock_type;\n+};\n+\n+FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, stream)\n+{\n+\t.sock_type = SOCK_STREAM,\n+};\n+\n+FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, dgram)\n+{\n+\t.sock_type = SOCK_DGRAM,\n+};\n+\n+FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, seqpacket)\n+{\n+\t.sock_type = SOCK_SEQPACKET,\n+};\n+\n+FIXTURE_SETUP(scm_rights_denial_bpf)\n+{\n+\tstruct bpf_program *prog;\n+\tchar lsms[256] = {};\n+\tint i, fd;\n+\n+\tif (geteuid() != 0)\n+\t\tSKIP(return, \"requires root\");\n+\n+\tfd = open(\"/sys/kernel/security/lsm\", O_RDONLY);\n+\tASSERT_GE(fd, 0);\n+\tASSERT_LT(0, read(fd, lsms, sizeof(lsms) - 1));\n+\tclose(fd);\n+\n+\tif (!strstr(lsms, \"bpf\"))\n+\t\tSKIP(return, \"BPF LSM not active (boot with lsm=...,bpf)\");\n+\n+\tself-\u003eobj = bpf_object__open_file(\"scm_rights_denial_lsm.bpf.o\", NULL);\n+\tASSERT_NE(NULL, self-\u003eobj);\n+\tASSERT_EQ(0, bpf_object__load(self-\u003eobj));\n+\n+\tprog = bpf_object__find_program_by_name(self-\u003eobj, \"scm_rights_deny\");\n+\tASSERT_NE(NULL, prog);\n+\n+\tself-\u003elink = bpf_program__attach_lsm(prog);\n+\tASSERT_NE(NULL, self-\u003elink);\n+\n+\tself-\u003emap_fd = bpf_object__find_map_fd_by_name(self-\u003eobj,\n+\t\t\t\t\t\t       \"denied_inodes\");\n+\tASSERT_GE(self-\u003emap_fd, 0);\n+\n+\tASSERT_EQ(0, socketpair(AF_UNIX, variant-\u003esock_type, 0, self-\u003esk));\n+\n+\tfor (i = 0; i \u003c NR_FILES; i++) {\n+\t\tstruct stat st;\n+\n+\t\tsnprintf(self-\u003epaths[i], sizeof(self-\u003epaths[i]),\n+\t\t\t \"/tmp/scm_rights_denial_bpf.%d.XXXXXX\", i);\n+\t\tself-\u003efiles[i] = mkstemp(self-\u003epaths[i]);\n+\t\tASSERT_GE(self-\u003efiles[i], 0);\n+\n+\t\tASSERT_LT(0, dprintf(self-\u003efiles[i], SECRET(i)));\n+\n+\t\tASSERT_EQ(0, fstat(self-\u003efiles[i], \u0026st));\n+\t\tself-\u003einos[i] = st.st_ino;\n+\t}\n+}\n+\n+FIXTURE_TEARDOWN(scm_rights_denial_bpf)\n+{\n+\tbpf_link__destroy(self-\u003elink);\n+\tbpf_object__close(self-\u003eobj);\n+\n+\tfor (int i = 0; i \u003c NR_FILES; i++) {\n+\t\tif (self-\u003efiles[i] \u003e= 0) {\n+\t\t\tclose(self-\u003efiles[i]);\n+\t\t\tunlink(self-\u003epaths[i]);\n+\t\t}\n+\t}\n+\n+\tclose(self-\u003esk[SK_SENDER]);\n+\tclose(self-\u003esk[SK_RECEIVER]);\n+}\n+\n+static int deny_inode(int map_fd, __u64 ino)\n+{\n+\t__u32 tgid = getpid();\n+\n+\treturn bpf_map_update_elem(map_fd, \u0026ino, \u0026tgid, BPF_ANY);\n+}\n+\n+static int set_notrunc(int sk)\n+{\n+\tint one = 1;\n+\n+\treturn setsockopt(sk, SOL_SOCKET, SO_RIGHTS_NOTRUNC,\n+\t\t\t  \u0026one, sizeof(one));\n+}\n+\n+static int send_fds(int sk, int *fds, int n)\n+{\n+\tchar ctrl[CMSG_SPACE(NR_FILES * sizeof(int))] = {};\n+\tchar data = 'x';\n+\tstruct iovec iov = {\n+\t\t.iov_base = \u0026data,\n+\t\t.iov_len = sizeof(data),\n+\t};\n+\tstruct msghdr msg = {\n+\t\t.msg_iov = \u0026iov,\n+\t\t.msg_iovlen = 1,\n+\t\t.msg_control = ctrl,\n+\t\t.msg_controllen = CMSG_SPACE(n * sizeof(int)),\n+\t};\n+\tstruct cmsghdr *cmsg = CMSG_FIRSTHDR(\u0026msg);\n+\n+\tcmsg-\u003ecmsg_level = SOL_SOCKET;\n+\tcmsg-\u003ecmsg_type = SCM_RIGHTS;\n+\tcmsg-\u003ecmsg_len = CMSG_LEN(n * sizeof(int));\n+\tmemcpy(CMSG_DATA(cmsg), fds, n * sizeof(int));\n+\n+\treturn sendmsg(sk, \u0026msg, 0);\n+}\n+\n+static int recv_fd_slots(int sk, int *slots, int *msg_flags)\n+{\n+\tint nr_slots;\n+\tchar ctrl[CMSG_SPACE(NR_FILES * sizeof(int))];\n+\tchar data;\n+\tstruct iovec iov = {\n+\t\t.iov_base = \u0026data,\n+\t\t.iov_len = sizeof(data),\n+\t};\n+\tstruct msghdr msg = {\n+\t\t.msg_iov = \u0026iov,\n+\t\t.msg_iovlen = 1,\n+\t\t.msg_control = ctrl,\n+\t\t.msg_controllen = sizeof(ctrl),\n+\t};\n+\tstruct cmsghdr *cmsg;\n+\n+\tif (recvmsg(sk, \u0026msg, 0) \u003c 0)\n+\t\treturn -1;\n+\n+\t*msg_flags = msg.msg_flags;\n+\n+\tcmsg = CMSG_FIRSTHDR(\u0026msg);\n+\tif (!cmsg)\n+\t\treturn 0;\n+\n+\tnr_slots = (cmsg-\u003ecmsg_len - CMSG_LEN(0)) / sizeof(int);\n+\tmemcpy(slots, CMSG_DATA(cmsg), nr_slots * sizeof(int));\n+\n+\treturn nr_slots;\n+}\n+\n+/* Prove a received fd works by reading back the file's content. */\n+static int check_secret(int fd, int idx)\n+{\n+\tchar want[32], got[32] = {};\n+\n+\tsnprintf(want, sizeof(want), SECRET(idx));\n+\tif (pread(fd, got, sizeof(got) - 1, 0) \u003c 0)\n+\t\treturn -1;\n+\n+\treturn strcmp(want, got);\n+}\n+\n+TEST_F(scm_rights_denial_bpf, all_allowed)\n+{\n+\tint slots[NR_FILES], nr_slots, flags, i;\n+\n+\tASSERT_EQ(0, set_notrunc(self-\u003esk[SK_RECEIVER]));\n+\tASSERT_NE(-1, send_fds(self-\u003esk[SK_SENDER], self-\u003efiles, NR_FILES));\n+\tnr_slots = recv_fd_slots(self-\u003esk[SK_RECEIVER], slots, \u0026flags);\n+\n+\tASSERT_EQ(NR_FILES, nr_slots);\n+\tEXPECT_EQ(0, flags \u0026 MSG_CTRUNC);\n+\n+\tfor (i = 0; i \u003c NR_FILES; i++) {\n+\t\tASSERT_GE(slots[i], 0);\n+\t\tEXPECT_EQ(0, check_secret(slots[i], i));\n+\t\tclose(slots[i]);\n+\t}\n+}\n+\n+TEST_F(scm_rights_denial_bpf, first_denied)\n+{\n+\tint slots[NR_FILES], nr_slots, flags;\n+\n+\tASSERT_EQ(0, deny_inode(self-\u003emap_fd, self-\u003einos[0]));\n+\n+\tASSERT_EQ(0, set_notrunc(self-\u003esk[SK_RECEIVER]));\n+\tASSERT_NE(-1, send_fds(self-\u003esk[SK_SENDER], self-\u003efiles, NR_FILES));\n+\tnr_slots = recv_fd_slots(self-\u003esk[SK_RECEIVER], slots, \u0026flags);\n+\n+\tASSERT_EQ(NR_FILES, nr_slots);\n+\tEXPECT_EQ(0, flags \u0026 MSG_CTRUNC);\n+\tEXPECT_EQ(-EPERM, slots[0]);\n+\n+\tASSERT_GE(slots[1], 0);\n+\tEXPECT_EQ(0, check_secret(slots[1], 1));\n+\tclose(slots[1]);\n+}\n+\n+TEST_F(scm_rights_denial_bpf, all_denied)\n+{\n+\tint slots[NR_FILES], nr_slots, flags, i;\n+\n+\tfor (i = 0; i \u003c NR_FILES; i++)\n+\t\tASSERT_EQ(0, deny_inode(self-\u003emap_fd, self-\u003einos[i]));\n+\n+\tASSERT_EQ(0, set_notrunc(self-\u003esk[SK_RECEIVER]));\n+\tASSERT_NE(-1, send_fds(self-\u003esk[SK_SENDER], self-\u003efiles, NR_FILES));\n+\tnr_slots = recv_fd_slots(self-\u003esk[SK_RECEIVER], slots, \u0026flags);\n+\n+\tASSERT_EQ(NR_FILES, nr_slots);\n+\tEXPECT_EQ(0, flags \u0026 MSG_CTRUNC);\n+\n+\tfor (i = 0; i \u003c NR_FILES; i++)\n+\t\tEXPECT_EQ(-EPERM, slots[i]);\n+}\n+\n+TEST_F(scm_rights_denial_bpf, denied_without_notrunc)\n+{\n+\tint slots[NR_FILES], nr_slots, flags;\n+\n+\t/*\n+\t * Baseline behaviour without SO_RIGHTS_NOTRUNC: the fd array is\n+\t * truncated at the first denied fd and MSG_CTRUNC is set.\n+\t */\n+\tASSERT_EQ(0, deny_inode(self-\u003emap_fd, self-\u003einos[1]));\n+\n+\tASSERT_NE(-1, send_fds(self-\u003esk[SK_SENDER], self-\u003efiles, NR_FILES));\n+\tnr_slots = recv_fd_slots(self-\u003esk[SK_RECEIVER], slots, \u0026flags);\n+\n+\tASSERT_EQ(1, nr_slots);\n+\tEXPECT_NE(0, flags \u0026 MSG_CTRUNC);\n+\n+\tASSERT_GE(slots[0], 0);\n+\tEXPECT_EQ(0, check_secret(slots[0], 0));\n+\tclose(slots[0]);\n+}\n+\n+TEST_HARNESS_MAIN\n"
}

2/1 2026/08/02 20:09 agent patch-evaluator
1m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "UNIX",
    "COMPAT",
    "SECURITY"
  ],
  "FocusSymbols": [
    "unix_setsockopt",
    "scm_recv_one_fd",
    "scm_detach_fds",
    "scm_detach_fds_compat",
    "scm_recv_unix"
  ],
  "Reasoning": "The patch introduces a new socket option SO_RIGHTS_NOTRUNC for AF_UNIX sockets. This option modifies the behavior of SCM_RIGHTS when a file descriptor receive is denied by a security module (LSM). Instead of truncating the file descriptor array and setting MSG_CTRUNC, it places the error code in the user's file descriptor array. This involves changes to core AF_UNIX socket option parsing (unix_setsockopt), socket connection state propagation, and the SCM message receiving logic (scm_recv_one_fd, scm_detach_fds, scm_detach_fds_compat, scm_recv_unix). These are reachable core networking paths that should be fuzzed to ensure the new error handling and array manipulation logic does not introduce memory safety issues or leaks.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit e5cdb4a936e1a7d68f7361ff775d961416477a35
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Aug 2 20:09:22 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 5ef57f88df6b3..946a5fad26912 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 72fb1b006da93..f1641dde135fc 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 c16ec36dfee6b..f3a3815c7dc29 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 71befa109e1cf..7907f3b1f0ee0 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 34f53dde65cea..bb1b3dee02e82 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 c52519669349f..86ae6bc109ec6 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 53b5a8c002b1e..84ea7b92936e2 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 d68cf9c3aad5f..6bdf4a2c9077d 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 eec13f50ecaf6..f0d44ecdb11fc 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;
 	}
@@ -523,9 +547,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 +566,13 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
 	if (!__scm_recv_common(sock->sk, msg, scm, flags))
 		return;
 
+	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 10ed9421e43aa..03ce23a4ebee1 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;
@@ -950,13 +951,21 @@ 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;
 
 		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;
 	}
@@ -1006,6 +1015,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 +1040,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 +1154,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;
 		/*
@@ -1743,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
 	 *
@@ -1865,8 +1878,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);
diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
index 9731766441036..954f0958dd034 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 57d159803a3ab..a66f10fb0c231 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 41dbb03c747eb..468f7a0bb64e6 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 0000000000000..4f2414465bfd3
--- /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 0000000000000..941b7decf7980
--- /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

3/2 2026/08/02 20:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 10333
							output: 21
							thoughts: 774
						

Thoughts:
**Analyzing the New SO_RIGHTS_NOTRUNC Socket Option**

Okay, so I'm looking at this patch introducing `SO_RIGHTS_NOTRUNC` for AF_UNIX sockets. The core idea is that when a security module like an LSM denies the receipt of a file descriptor via `SCM_RIGHTS`, instead of truncating the FD array and setting `MSG_CTRUNC`, we now have the option to *not* truncate. Instead, the error code (like `-EPERM`) gets placed directly into the user's FD array, essentially signaling the denial more explicitly. This is a pretty significant change to the standard behavior, although targeted.

Let's break down the diff to see the specifics. We've got:

*   The obvious header file updates in `arch/*/include/uapi/asm/socket.h` and `include/uapi/asm-generic/socket.h` to define the new `SO_RIGHTS_NOTRUNC` option (value 85).
*   In `include/net/af_unix.h`, a new `bool scm_rights_notrunc;` member has been added to the `unix_sock` structure.
*   The `include/net/scm.h` modifications are interesting. The `scm_detach_fds`, `scm_detach_fds_compat`, and `scm_recv_one_fd` functions now accept a `bool notrunc` argument, which is exactly what the new socket option controls.
*   `net/compat.c` has been updated to correctly pass the `notrunc` argument to `scm_recv_one_fd`.
*   The crucial change is in `net/core/scm.c`, where `scm_recv_one_fd` now handles the error return correctly if `notrunc` is true, by putting the error into the user's FD array.
*   Finally, `net/unix/af_unix.c` integrates `SO_RIGHTS_NOTRUNC` into `unix_custom_sockopt` and `unix_setsockopt`, and also propagates the `scm_rights_notrunc` flag during `unix_stream_connect`, which makes sense.
*   And, good, there's a selftest `tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c`. This is essential to show correct behavior.

This is a functional change to the core networking and AF_UNIX logic, so it's definitely reachable and a solid candidate for fuzzing.

My primary focus symbols are clearly:

*   `unix_setsockopt` – because it’s where the socket option is set.
*   `scm_recv_one_fd` – this is the core of the new logic, how the error is handled.
*   `scm_detach_fds` and `scm_detach_fds_compat` – both are core to FD handling and the logic flow.
*   `unix_stream_connect` - the path might be valuable.

I'll double-check `net/core/scm.c` for `scm_recv_one_fd` and `scm_detach_fds`, and `net/unix/af_unix.c` for `unix_setsockopt` for more detail.

I need to confirm the required `CONFIG_` options: `CONFIG_UNIX` (obvious), `CONFIG_SECURITY` (for the LSM, although the feature itself doesn't inherently depend on it, but the denial makes the most sense), and `CONFIG_COMPAT` (for the compat function).

Then I'll run some tooling to test this out thoroughly.



4/2 2026/08/02 20:09 tool grepper
0m Args:
{
  "Expression": "scm_recv_one_fd"
}

Results:
{
  "Output": "include/net/scm.h=107=void scm_recv_unix(struct socket *sock, struct msghdr *msg,\n--\ninclude/net/scm.h-109-\ninclude/net/scm.h:110:int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,\ninclude/net/scm.h-111-\t\t    bool notrunc);\n--\nnet/compat.c=289=void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)\n--\nnet/compat.c-298-\tfor (i = 0; i \u003c fdmax; i++) {\nnet/compat.c:299:\t\terr = scm_recv_one_fd(scm-\u003efp-\u003efp[i], cmsg_data + i, o_flags, notrunc);\nnet/compat.c-300-\t\tif (err \u003c 0)\n--\nnet/core/scm.c=347=static int scm_max_fds(struct msghdr *msg)\n--\nnet/core/scm.c-353-\nnet/core/scm.c:354:int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,\nnet/core/scm.c-355-\t\t    bool notrunc)\n--\nnet/core/scm.c=378=void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)\n--\nnet/core/scm.c-396-\tfor (i = 0; i \u003c fdmax; i++) {\nnet/core/scm.c:397:\t\terr = scm_recv_one_fd(scm-\u003efp-\u003efp[i], cmsg_data + i, o_flags, notrunc);\nnet/core/scm.c-398-\t\tif (err \u003c 0)\n"
}

5/2 2026/08/02 20:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12440
							output: 19
							thoughts: 412
						

Thoughts:
**Analyzing the Patch's Scope**

Okay, so this patch is touching a few key areas: `scm_recv_one_fd`, `scm_detach_fds`, `scm_detach_fds_compat`, `unix_setsockopt`, and `scm_recv_unix`. These are all pretty focused entry points, which is good. Let's see if there are any conditional compilation aspects to keep in mind.

The big one is `CONFIG_UNIX`, obviously, as this seems central to the core functionality. `CONFIG_COMPAT` is linked to `scm_detach_fds_compat`, which is as expected. Are there any other relevant configurations? Looking at the selftest introduces a range of `CONFIG_BPF` options, along with `CONFIG_SECURITY` and `CONFIG_DEBUG_INFO_BTF`.

However, focusing on the core feature I'm assessing (`SO_RIGHTS_NOTRUNC`), the code itself in `net/unix/af_unix.c` and `net/core/scm.c` only strictly relies on `CONFIG_UNIX` for compilation. It's not inherently dependent on BPF or LSM for the code to be compiled and reachable.

But, now that I'm considering error paths, specifically within `scm_recv_one_fd`:

```c
	error = security_file_receive(f);
	if (error)
		return notrunc ? put_user(error, ufd) : error;
```

To *trigger* the error path here, `security_file_receive` needs to return an error. That could be due to an LSM denying access or some other failure within the `security_file_receive` function. So, while `CONFIG_UNIX` is the essential requirement, it would be beneficial to have `CONFIG_SECURITY` and possibly an LSM enabled for a comprehensive test, but for the code to be compiled `CONFIG_UNIX` is the only strict requirement.

Now, let me shift gears and check `net/unix/af_unix.c` for any specifics on `unix_setsockopt`.



6/2 2026/08/02 20:10 tool grepper
0m Args:
{
  "Expression": "unix_setsockopt"
}

Results:
{
  "Output": "net/unix/af_unix.c=921=static bool unix_custom_sockopt(int optname)\n--\nnet/unix/af_unix.c-931-\nnet/unix/af_unix.c:932:static int unix_setsockopt(struct socket *sock, int level, int optname,\nnet/unix/af_unix.c-933-\t\t\t   sockptr_t optval, unsigned int optlen)\n--\nnet/unix/af_unix.c=976=static const struct proto_ops unix_stream_ops = {\n--\nnet/unix/af_unix.c-991-\t.shutdown =\tunix_shutdown,\nnet/unix/af_unix.c:992:\t.setsockopt =\tunix_setsockopt,\nnet/unix/af_unix.c-993-\t.sendmsg =\tunix_stream_sendmsg,\n--\nnet/unix/af_unix.c=1002=static const struct proto_ops unix_dgram_ops = {\n--\nnet/unix/af_unix.c-1017-\t.shutdown =\tunix_shutdown,\nnet/unix/af_unix.c:1018:\t.setsockopt =\tunix_setsockopt,\nnet/unix/af_unix.c-1019-\t.sendmsg =\tunix_dgram_sendmsg,\n--\nnet/unix/af_unix.c=1027=static const struct proto_ops unix_seqpacket_ops = {\n--\nnet/unix/af_unix.c-1042-\t.shutdown =\tunix_shutdown,\nnet/unix/af_unix.c:1043:\t.setsockopt =\tunix_setsockopt,\nnet/unix/af_unix.c-1044-\t.sendmsg =\tunix_seqpacket_sendmsg,\n"
}

7/2 2026/08/02 20:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13794
							output: 20
							thoughts: 124
						

Thoughts:
**My Deep Dive into Socket Security**

Okay, so I'm really digging into the security aspects of socket communication, specifically with UNIX sockets. I need to get a rock-solid understanding of where things can go wrong and what checks are in place. My key focus areas right now are: `unix_setsockopt`, `scm_recv_one_fd`, `scm_detach_fds`, `scm_detach_fds_compat`, and `scm_recv_unix`. These are the functions at the heart of transferring file descriptors over UNIX domain sockets, and therefore represent the most likely spots for vulnerability.

I'm also paying close attention to the configurations that dictate how these functions behave. I have `UNIX` and `COMPAT` flags in mind as these seem to be the most relevant. I need to make sure I don't miss anything, are there any other related configurations that I should be aware of? I need a complete picture.

I know the selftest leverages BPF LSM to deny file descriptor reception, acting as a kind of canary. That got me thinking about other failure paths for `security_file_receive`. Could there be other ways this check might fail, perhaps due to different configurations or conditions? I need to know all the angles here. I will scrutinize `security_file_receive` to get to the bottom of this.



8/2 2026/08/02 20:10 tool grepper
0m Args:
{
  "Expression": "int security_file_receive"
}

Results:
{
  "Output": "include/linux/security.h=492=int security_file_send_sigiotask(struct task_struct *tsk,\ninclude/linux/security.h-493-\t\t\t\t struct fown_struct *fown, int sig);\ninclude/linux/security.h:494:int security_file_receive(struct file *file);\ninclude/linux/security.h-495-int security_file_open(struct file *file);\n--\ninclude/linux/security.h=1214=static inline int security_file_send_sigiotask(struct task_struct *tsk,\n--\ninclude/linux/security.h-1220-\ninclude/linux/security.h:1221:static inline int security_file_receive(struct file *file)\ninclude/linux/security.h-1222-{\n--\nsecurity/security.c=2704=int security_file_send_sigiotask(struct task_struct *tsk,\n--\nsecurity/security.c-2718- */\nsecurity/security.c:2719:int security_file_receive(struct file *file)\nsecurity/security.c-2720-{\n"
}

9/2 2026/08/02 20:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14518
							output: 34
							thoughts: 21
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into security_file_receive**

Alright, let's dissect `security_file_receive` in `security/security.c`. I need to understand the specifics of how this function handles file reception from a security perspective. My initial questions are: what checks are performed on the incoming data? What assumptions are being made about the source, size, and content? Are there any potential vulnerabilities related to buffer overflows, path traversal, or code injection that immediately jump out? I want to see how it interacts with other security primitives. Is there an integration with any mandatory access control mechanisms or is it handling security policy itself? Let's check the function's parameter list, type declarations and the data it accesses, looking for anything that seems unusual and should be documented. From there I'll trace through the core logic, examining any calls to other security-related functions or system calls involved in file handling (e.g., `open`, `read`, `write`, `chmod`, `chown`, etc.) to get a good sense of its role within the wider system.



10/2 2026/08/02 20:10 tool read-file
0m Args:
{
  "File": "security/security.c",
  "FirstLine": 2719,
  "LineCount": 20
}

Results:
{
  "Contents": "2719:\tint security_file_receive(struct file *file)\n2720:\t{\n2721:\t\treturn call_int_hook(file_receive, file);\n2722:\t}\n2723:\t\n2724:\t/**\n2725:\t * security_file_open() - Save open() time state for late use by the LSM\n2726:\t * @file:\n2727:\t *\n2728:\t * Save open-time permission checking state for later use upon file_permission,\n2729:\t * and recheck access if anything has changed since inode_permission.\n2730:\t *\n2731:\t * We can check if a file is opened for execution (e.g. execve(2) call), either\n2732:\t * directly or indirectly (e.g. ELF's ld.so) by checking file-\u003ef_flags \u0026\n2733:\t * __FMODE_EXEC .\n2734:\t *\n2735:\t * Return: Returns 0 if permission is granted.\n2736:\t */\n2737:\tint security_file_open(struct file *file)\n2738:\t{\n"
}

11/2 2026/08/02 20:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15131
							output: 258
							thoughts: 487
						

Thoughts:
**My Assessment of the `SO_RIGHTS_NOTRUNC` Patch**

Okay, so this patch is all about handling errors when receiving file descriptors over UNIX sockets with the `SO_RIGHTS_NOTRUNC` option. It boils down to this: the code calls the LSM (Linux Security Module) hook. If no LSM denies the file descriptor receive, it returns 0. But to truly test the error path where `error != 0`, we need an LSM that *can* deny `file_receive`.

AppArmor, SELinux, Smack, TOMOYO, and BPF LSM are the possibilities. The selftest uses BPF LSM, and that seems like a sensible approach. So, to ensure we can hit the error path, especially if syzkaller is throwing BPF programs at the kernel, enabling `SECURITY` and `BPF_LSM` is a must.

Now, I need to consider how syzkaller operates. It's not straightforward for it to *deliberately* generate BPF LSM programs to deny specific hooks without descriptions for the hooks. However, it *could* trigger the error by chance, or if another LSM, like SELinux or AppArmor, is active and already denies file descriptor transfer across domains. Still, adding `SECURITY` is a good practice.

At any rate, `UNIX` and `COMPAT` are crucial configs to enable, given the target code. Adding `SECURITY` seems like a useful precaution.

Let's quickly review the core focus symbols: `unix_setsockopt`, `scm_recv_one_fd`, `scm_detach_fds`, `scm_detach_fds_compat`, and `scm_recv_unix`. Yep, those all look right on point. They directly relate to the new logic.

Now, to solidify the reasoning: This patch introduces `SO_RIGHTS_NOTRUNC` for AF_UNIX sockets. This option modifies the `SCM_RIGHTS` behavior. When the file descriptor receive is denied by a security module, the patch modifies `SCM_RIGHTS`. Instead of truncating the file descriptor array and setting `MSG_CTRUNC`, it places the error code in the user's file descriptor array. This involves changes to core AF_UNIX socket option parsing (`unix_setsockopt`), socket connection state propagation, and the SCM message receiving logic (`scm_recv_one_fd`, `scm_detach_fds`, `scm_detach_fds_compat`, `scm_recv_unix`). These are reachable core networking paths that *must* be fuzzed to verify that the new error handling and array manipulation logic doesn't introduce memory safety issues or leaks.

So, the key configs for fuzzing: `UNIX`, `COMPAT`, and `SECURITY`.

Time to call `set-results`.



12/2 2026/08/02 20:10 tool set-results
0m Args:
{
  "EnableConfigs": [
    "UNIX",
    "COMPAT",
    "SECURITY"
  ],
  "FocusSymbols": [
    "unix_setsockopt",
    "scm_recv_one_fd",
    "scm_detach_fds",
    "scm_detach_fds_compat",
    "scm_recv_unix"
  ],
  "Reasoning": "The patch introduces a new socket option SO_RIGHTS_NOTRUNC for AF_UNIX sockets. This option modifies the behavior of SCM_RIGHTS when a file descriptor receive is denied by a security module (LSM). Instead of truncating the file descriptor array and setting MSG_CTRUNC, it places the error code in the user's file descriptor array. This involves changes to core AF_UNIX socket option parsing (unix_setsockopt), socket connection state propagation, and the SCM message receiving logic (scm_recv_one_fd, scm_detach_fds, scm_detach_fds_compat, scm_recv_unix). These are reachable core networking paths that should be fuzzed to ensure the new error handling and array manipulation logic does not introduce memory safety issues or leaks.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "UNIX",
    "COMPAT",
    "SECURITY"
  ],
  "FocusSymbols": [
    "unix_setsockopt",
    "scm_recv_one_fd",
    "scm_detach_fds",
    "scm_detach_fds_compat",
    "scm_recv_unix"
  ],
  "Reasoning": "The patch introduces a new socket option SO_RIGHTS_NOTRUNC for AF_UNIX sockets. This option modifies the behavior of SCM_RIGHTS when a file descriptor receive is denied by a security module (LSM). Instead of truncating the file descriptor array and setting MSG_CTRUNC, it places the error code in the user's file descriptor array. This involves changes to core AF_UNIX socket option parsing (unix_setsockopt), socket connection state propagation, and the SCM message receiving logic (scm_recv_one_fd, scm_detach_fds, scm_detach_fds_compat, scm_recv_unix). These are reachable core networking paths that should be fuzzed to ensure the new error handling and array manipulation logic does not introduce memory safety issues or leaks.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)