The branch main has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=c08a86e0c6d989c926c2777c978155dde7d0697a
commit c08a86e0c6d989c926c2777c978155dde7d0697a Author: Kyle Evans <[email protected]> AuthorDate: 2026-06-19 04:03:30 +0000 Commit: Kyle Evans <[email protected]> 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);
