[PATCH net 4/4] selftest: af_unix: Test SIOCINQ and scm_fds with OOB and dgram disconnect.

From: Siddharth Chintamaneni

Date: Fri Nov 06 2026 - 00:23:44 EST


Verify in msg_oob's __resetpair() that ioctl(SIOCINQ) reports 0 unread
bytes after draining the stream receive queue, and add oob_fdinfo and
dgram_disconnect_fdinfo tests in scm_rights.c verifying that
/proc/self/fdinfo/<fd> (scm_fds) returns to 0 when OOB skbs carrying
SCM_RIGHTS are skipped or freed and when a dgram socket disconnects with
queued SCM_RIGHTS skbs.

Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@xxxxxxxxx>
---
tools/testing/selftests/net/af_unix/msg_oob.c | 12 +-
.../selftests/net/af_unix/scm_rights.c | 108 ++++++++++++++++++
2 files changed, 119 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/net/af_unix/msg_oob.c b/tools/testing/selftests/net/af_unix/msg_oob.c
index f051d79f7a8e..7d5a25485048 100644
--- a/tools/testing/selftests/net/af_unix/msg_oob.c
+++ b/tools/testing/selftests/net/af_unix/msg_oob.c
@@ -5,6 +5,7 @@
#include <string.h>
#include <unistd.h>

+#include <linux/sockios.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
@@ -336,8 +337,17 @@ static void __resetpair(struct __test_metadata *_metadata,
{
int i;

- for (i = 0; i < 2; i++)
+ for (i = 0; i < 2; i++) {
+ if (!reset) {
+ int inq, ret;
+
+ ret = ioctl(self->fd[i * 2 + 1], SIOCINQ, &inq);
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(inq, 0);
+ }
+
close(self->fd[i * 2 + 1]);
+ }

__recvpair(_metadata, self, "", reset ? -ECONNRESET : 0, 1,
variant->peek ? MSG_PEEK : 0, true);
diff --git a/tools/testing/selftests/net/af_unix/scm_rights.c b/tools/testing/selftests/net/af_unix/scm_rights.c
index c165f250220a..38a125d9f6e4 100644
--- a/tools/testing/selftests/net/af_unix/scm_rights.c
+++ b/tools/testing/selftests/net/af_unix/scm_rights.c
@@ -378,6 +378,36 @@ TEST_F(scm_rights, backtrack_from_scc)
close_sockets(10);
}

+static int count_scm_fds(struct __test_metadata *_metadata, int fd)
+{
+ char path[64], *line = NULL;
+ int scm_fds = -1, ret;
+ size_t unused;
+ FILE *f;
+
+ snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", fd);
+
+ f = fopen(path, "r");
+ ASSERT_NE(NULL, f);
+
+ while (getline(&line, &unused, f) != -1) {
+ if (strncmp(line, "scm_fds:", 8))
+ continue;
+
+ ret = sscanf(line + 8, "%d", &scm_fds);
+ ASSERT_EQ(1, ret);
+
+ break;
+ }
+
+ free(line);
+
+ ret = fclose(f);
+ ASSERT_EQ(0, ret);
+
+ return scm_fds;
+}
+
TEST_F(scm_rights, mixed_lowpoint)
{
create_sockets(6);
@@ -395,4 +425,82 @@ TEST_F(scm_rights, mixed_lowpoint)
close_sockets(6);
}

+TEST_F(scm_rights, oob_fdinfo)
+{
+ int recv_fd, ret;
+ char buf[2];
+
+ if (!(variant->flags & MSG_OOB) || variant->disabled)
+ return;
+
+ create_sockets(2);
+
+ if (variant->test_listener) {
+ recv_fd = accept(self->fd[0], NULL, NULL);
+ ASSERT_LE(0, recv_fd);
+ } else {
+ recv_fd = self->fd[0];
+ }
+
+ /* 1. Unconsumed OOB skb skipped by normal recv() in manage_oob() */
+ send_fd(0, 0);
+ ASSERT_EQ(2, count_scm_fds(_metadata, recv_fd));
+ ret = send(self->fd[1], "a", 1, 0);
+ ASSERT_EQ(1, ret);
+ ret = recv(recv_fd, buf, 1, 0);
+ ASSERT_EQ(1, ret);
+ ASSERT_EQ(0, count_scm_fds(_metadata, recv_fd));
+
+ /* 2. Consumed OOB skb freed by normal recv() in manage_oob() */
+ send_fd(0, 0);
+ ASSERT_EQ(2, count_scm_fds(_metadata, recv_fd));
+ ret = recv(recv_fd, buf, 1, MSG_OOB);
+ ASSERT_EQ(1, ret);
+ ret = send(self->fd[1], "a", 1, 0);
+ ASSERT_EQ(1, ret);
+ ret = recv(recv_fd, buf, 1, 0);
+ ASSERT_EQ(1, ret);
+ ASSERT_EQ(0, count_scm_fds(_metadata, recv_fd));
+
+ /* 3. Consumed OOB skb freed by consecutive recv(MSG_OOB) */
+ send_fd(0, 0);
+ ASSERT_EQ(2, count_scm_fds(_metadata, recv_fd));
+ ret = recv(recv_fd, buf, 1, MSG_OOB);
+ ASSERT_EQ(1, ret);
+ ret = send(self->fd[1], "y", 1, MSG_OOB);
+ ASSERT_EQ(1, ret);
+ ret = recv(recv_fd, buf, 1, MSG_OOB);
+ ASSERT_EQ(1, ret);
+ ASSERT_EQ(0, count_scm_fds(_metadata, recv_fd));
+
+ if (variant->test_listener) {
+ ret = close(recv_fd);
+ ASSERT_EQ(0, ret);
+ }
+
+ close_sockets(2);
+}
+
+TEST_F(scm_rights, dgram_disconnect_fdinfo)
+{
+ struct sockaddr addr = {
+ .sa_family = AF_UNSPEC,
+ };
+ int ret;
+
+ if (variant->type != SOCK_DGRAM || variant->disabled)
+ return;
+
+ create_sockets(2);
+
+ send_fd(0, 0);
+ ASSERT_EQ(2, count_scm_fds(_metadata, self->fd[0]));
+
+ ret = connect(self->fd[0], &addr, sizeof(addr.sa_family));
+ ASSERT_EQ(0, ret);
+ ASSERT_EQ(0, count_scm_fds(_metadata, self->fd[0]));
+
+ close_sockets(2);
+}
+
TEST_HARNESS_MAIN
--
2.43.0