git: c08a86e0c6d9 - main - tests: unix: add SCM_RIGHTS tests for SO_PASSRIGHTS

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Fri, 19 Jun 2026 04:05:12 UTC
The branch main has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=c08a86e0c6d989c926c2777c978155dde7d0697a

commit c08a86e0c6d989c926c2777c978155dde7d0697a
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2026-06-19 04:03:30 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2026-06-19 04:03:30 +0000

    tests: unix: add SCM_RIGHTS tests for SO_PASSRIGHTS
    
    We test both the standard case where we want to reject any SCM_RIGHTS
    message, as well as the case where the kernel discards the unwanted file
    upon receipt.
    
    Reviewed by:    glebius (previous version), markj
    Differential Revision:  https://reviews.freebsd.org/D57426
---
 tests/sys/kern/unix_passfd_test.c | 150 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 150 insertions(+)

diff --git a/tests/sys/kern/unix_passfd_test.c b/tests/sys/kern/unix_passfd_test.c
index 2ad40582d058..c600b0937b0d 100644
--- a/tests/sys/kern/unix_passfd_test.c
+++ b/tests/sys/kern/unix_passfd_test.c
@@ -208,6 +208,19 @@ localcreds(int sockfd)
 	return (val != 0);
 }
 
+static bool
+passrights(int sockfd)
+{
+	socklen_t sz;
+	int rc, val;
+
+	sz = sizeof(val);
+	rc = getsockopt(sockfd, SOL_SOCKET, SO_PASSRIGHTS, &val, &sz);
+	ATF_REQUIRE_MSG(rc != -1, "getsockopt(SO_PASSRIGHTS) failed: %s",
+	    strerror(errno));
+	return (val != 0);
+}
+
 static ssize_t
 recvfd_payload_cmsg(int sockfd, void *buf, size_t buflen, struct msghdr *msghdr,
     int recvmsg_flags)
@@ -580,6 +593,140 @@ ATF_TC_BODY(send_overflow, tc)
 	closesocketpair(fd);
 }
 
+ATF_TC_WITHOUT_HEAD(send_rejected);
+ATF_TC_BODY(send_rejected, tc)
+{
+	ssize_t len;
+	int fd[2], optval, putfd, rc;
+	char ch;
+
+	domainsocketpair(fd);
+	ATF_REQUIRE_MSG(passrights(fd[0]),
+	    "socketpair socket not initialized with SO_PASSRIGHTS");
+	ATF_REQUIRE_MSG(passrights(fd[1]),
+	    "socketpair socket not initialized with SO_PASSRIGHTS");
+
+	tempfile(&putfd);
+
+	/* Toggle SO_PASSRIGHTS off on the receiver side. */
+	optval = 0;
+	rc = setsockopt(fd[1], SOL_SOCKET, SO_PASSRIGHTS, &optval,
+	    sizeof(optval));
+	ATF_REQUIRE_MSG(rc != -1, "setsockopt(SO_PASSRIGHTS) failed: %s",
+	    strerror(errno));
+	/* Confirm that the sender-side didn't reflect that... */
+	ATF_REQUIRE_MSG(passrights(fd[0]),
+	    "setsockopt(SO_PASSRIGHTS) switched the sender");
+	/* ... and that the receiver-side did. */
+	ATF_REQUIRE_MSG(!passrights(fd[1]),
+	    "setsockopt(SO_PASSRIGHTS) did not switch the receiver");
+
+	ch = 0;
+	len = sendfd_payload(fd[0], putfd, &ch, sizeof(ch));
+	ATF_REQUIRE_MSG(len == -1,
+	    "sending SCM_RIGHTS with SO_PASSRIGHTS disabled on peer did not fail");
+	ATF_REQUIRE_MSG(errno == EPERM,
+	    "bad SCM_RIGHTS failure: %s", strerror(errno));
+	closesocketpair(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(send_rejected_late);
+ATF_TC_BODY(send_rejected_late, tc)
+{
+	struct cmsghdr *cmsghdr;
+	struct msghdr msghdr;
+	ssize_t len;
+	char cmsgbuf[CMSG_SPACE(sizeof(int))];
+	int fd[2], nfds, optval, putfd, rc;
+	char ch;
+#define	MAGIC_VAL	42
+
+	domainsocketpair(fd);
+	tempfile(&putfd);
+
+	nfds = getnfds();
+
+	ch = MAGIC_VAL;
+	len = sendfd_payload(fd[0], putfd, &ch, sizeof(ch));
+	ATF_REQUIRE_MSG(len == sizeof(ch),
+	    "valid send of SCM_RIGHTS failed: %s", strerror(errno));
+
+	/*
+	 * Toggle SO_PASSRIGHTS off on the receiver side.  The subsequent recv
+	 * should actually succeed, we just won't have an fd installed.
+	 */
+	optval = 0;
+	rc = setsockopt(fd[1], SOL_SOCKET, SO_PASSRIGHTS, &optval,
+	    sizeof(optval));
+	ATF_REQUIRE_MSG(rc != -1, "setsockopt(SO_PASSRIGHTS) failed: %s",
+	    strerror(errno));
+
+	bzero(&msghdr, sizeof(msghdr));
+	msghdr.msg_control = &cmsgbuf[0];
+	msghdr.msg_controllen = sizeof(cmsgbuf);
+
+	ch = 0;
+	len = recvfd_payload_cmsg(fd[1], &ch, sizeof(ch), &msghdr, 0);
+	/* We want to confirm that we still received the sent byte... */
+	ATF_REQUIRE_MSG(len == sizeof(ch), "recvmsg should have returned data");
+	ATF_REQUIRE_MSG(ch == MAGIC_VAL, "recvmsg returned garbage? %d", ch);
+
+	/* ... and make sure we did not receive any descriptors! */
+	cmsghdr = CMSG_FIRSTHDR(&msghdr);
+	ATF_REQUIRE_MSG(cmsghdr == NULL || cmsghdr->cmsg_type != SCM_RIGHTS,
+	    "recvmsg unexpectedly received a descriptor");
+	ATF_REQUIRE(getnfds() == nfds);
+	closesocketpair(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(send_rejected_noinherit);
+ATF_TC_BODY(send_rejected_noinherit, tc)
+{
+	struct sockaddr_un sun;
+	int clsock, connsock, ls, optval, rc;
+
+	ls = socket(AF_UNIX, SOCK_STREAM, 0);
+	ATF_REQUIRE(ls != -1);
+
+	optval = 0;
+	rc = setsockopt(ls, SOL_SOCKET, SO_PASSRIGHTS, &optval,
+	    sizeof(optval));
+	ATF_REQUIRE_MSG(rc != -1, "setsockopt(SO_PASSRIGHTS) failed: %s",
+	    strerror(errno));
+
+	memset(&sun, 0, sizeof(sun));
+	sun.sun_len = sizeof(sun);
+	sun.sun_family = AF_UNIX;
+	snprintf(sun.sun_path, sizeof(sun.sun_path), "listen.sock");
+	rc = bind(ls, (struct sockaddr *)&sun, sizeof(sun));
+	ATF_REQUIRE_MSG(rc == 0, "bind failed: %s", strerror(errno));
+	rc = listen(ls, 0);
+	ATF_REQUIRE_MSG(rc == 0, "listen failed: %s", strerror(errno));
+
+	ATF_REQUIRE_MSG(!passrights(ls),
+	    "listening socket lost its SO_PASSRIGHTS setting");
+
+	connsock = socket(AF_UNIX, SOCK_STREAM, 0);
+	ATF_REQUIRE_MSG(connsock != -1, "connect failed: %s", strerror(errno));
+
+	rc = connect(connsock, (const struct sockaddr *)&sun,
+	    sizeof(sun));
+	ATF_REQUIRE_MSG(rc == 0, "connect failed: %s", strerror(errno));
+
+	/*
+	 * Finally, accept(4) and confirm that the new socket has
+	 * SO_PASSRIGHTS set.
+	 */
+	clsock = accept(ls, NULL, NULL);
+	ATF_REQUIRE_MSG(clsock >= 0, "accept failed: %s", strerror(errno));
+
+	ATF_REQUIRE_MSG(passrights(clsock),
+	    "inherited socket should enable SO_PASSRIGHTS");
+	close(clsock);
+	close(connsock);
+	close(ls);
+}
+
 /*
  * Make sure that we do not receive descriptors with MSG_PEEK.
  */
@@ -1236,6 +1383,9 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, send_and_shutdown);
 	ATF_TP_ADD_TC(tp, send_a_lot);
 	ATF_TP_ADD_TC(tp, send_overflow);
+	ATF_TP_ADD_TC(tp, send_rejected);
+	ATF_TP_ADD_TC(tp, send_rejected_late);
+	ATF_TP_ADD_TC(tp, send_rejected_noinherit);
 	ATF_TP_ADD_TC(tp, peek);
 	ATF_TP_ADD_TC(tp, two_files);
 	ATF_TP_ADD_TC(tp, bundle);