Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,9 @@
diag_uid
msg_oob
scm_inq
scm_pidfd
scm_rights
so_peek_off
unix_connect
unix_connreset
unix_listen
@@ -0,0 +1,20 @@
top_srcdir := ../../../../..
include $(top_srcdir)/scripts/Makefile.compiler
cc-option = $(call __cc-option, $(CC),,$(1),$(2))
CFLAGS += $(KHDR_INCLUDES) -Wall $(call cc-option,-Wflex-array-member-not-at-end)
TEST_GEN_PROGS := \
diag_uid \
msg_oob \
scm_inq \
scm_pidfd \
scm_rights \
so_peek_off \
unix_connect \
unix_connreset \
unix_listen \
# end of TEST_GEN_PROGS
include ../../lib.mk
@@ -0,0 +1,4 @@
CONFIG_AF_UNIX_OOB=y
CONFIG_UNIX=y
CONFIG_UNIX_DIAG=m
CONFIG_USER_NS=y
@@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright Amazon.com Inc. or its affiliates. */
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/sock_diag.h>
#include <linux/unix_diag.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include "kselftest_harness.h"
FIXTURE(diag_uid)
{
int netlink_fd;
int unix_fd;
__u32 inode;
__u64 cookie;
};
FIXTURE_VARIANT(diag_uid)
{
int unshare;
int udiag_show;
};
FIXTURE_VARIANT_ADD(diag_uid, uid)
{
.unshare = 0,
.udiag_show = UDIAG_SHOW_UID
};
FIXTURE_VARIANT_ADD(diag_uid, uid_unshare)
{
.unshare = CLONE_NEWUSER,
.udiag_show = UDIAG_SHOW_UID
};
FIXTURE_SETUP(diag_uid)
{
struct stat file_stat;
socklen_t optlen;
int ret;
if (variant->unshare)
ASSERT_EQ(unshare(variant->unshare), 0);
self->netlink_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
ASSERT_NE(self->netlink_fd, -1);
self->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_NE(self->unix_fd, -1);
ret = fstat(self->unix_fd, &file_stat);
ASSERT_EQ(ret, 0);
self->inode = file_stat.st_ino;
optlen = sizeof(self->cookie);
ret = getsockopt(self->unix_fd, SOL_SOCKET, SO_COOKIE, &self->cookie, &optlen);
ASSERT_EQ(ret, 0);
}
FIXTURE_TEARDOWN(diag_uid)
{
close(self->netlink_fd);
close(self->unix_fd);
}
int send_request(struct __test_metadata *_metadata,
FIXTURE_DATA(diag_uid) *self,
const FIXTURE_VARIANT(diag_uid) *variant)
{
struct {
struct nlmsghdr nlh;
struct unix_diag_req udr;
} req = {
.nlh = {
.nlmsg_len = sizeof(req),
.nlmsg_type = SOCK_DIAG_BY_FAMILY,
.nlmsg_flags = NLM_F_REQUEST
},
.udr = {
.sdiag_family = AF_UNIX,
.udiag_ino = self->inode,
.udiag_cookie = {
(__u32)self->cookie,
(__u32)(self->cookie >> 32)
},
.udiag_show = variant->udiag_show
}
};
struct sockaddr_nl nladdr = {
.nl_family = AF_NETLINK
};
struct iovec iov = {
.iov_base = &req,
.iov_len = sizeof(req)
};
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1
};
return sendmsg(self->netlink_fd, &msg, 0);
}
void render_response(struct __test_metadata *_metadata,
struct unix_diag_req *udr, __u32 len)
{
unsigned int rta_len = len - NLMSG_LENGTH(sizeof(*udr));
struct rtattr *attr;
uid_t uid;
ASSERT_GT(len, sizeof(*udr));
ASSERT_EQ(udr->sdiag_family, AF_UNIX);
attr = (struct rtattr *)(udr + 1);
ASSERT_NE(RTA_OK(attr, rta_len), 0);
ASSERT_EQ(attr->rta_type, UNIX_DIAG_UID);
uid = *(uid_t *)RTA_DATA(attr);
ASSERT_EQ(uid, getuid());
}
void receive_response(struct __test_metadata *_metadata,
FIXTURE_DATA(diag_uid) *self)
{
long buf[8192 / sizeof(long)];
struct sockaddr_nl nladdr = {
.nl_family = AF_NETLINK
};
struct iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf)
};
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1
};
struct nlmsghdr *nlh;
int ret;
ret = recvmsg(self->netlink_fd, &msg, 0);
ASSERT_GT(ret, 0);
nlh = (struct nlmsghdr *)buf;
ASSERT_NE(NLMSG_OK(nlh, ret), 0);
ASSERT_EQ(nlh->nlmsg_type, SOCK_DIAG_BY_FAMILY);
render_response(_metadata, NLMSG_DATA(nlh), nlh->nlmsg_len);
nlh = NLMSG_NEXT(nlh, ret);
ASSERT_EQ(NLMSG_OK(nlh, ret), 0);
}
TEST_F(diag_uid, 1)
{
int ret;
ret = send_request(_metadata, self, variant);
ASSERT_GT(ret, 0);
receive_response(_metadata, self);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,891 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright Amazon.com Inc. or its affiliates. */
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
#include "kselftest_harness.h"
#define BUF_SZ 32
FIXTURE(msg_oob)
{
int fd[4]; /* 0: AF_UNIX sender
* 1: AF_UNIX receiver
* 2: TCP sender
* 3: TCP receiver
*/
int signal_fd;
int epoll_fd[2]; /* 0: AF_UNIX receiver
* 1: TCP receiver
*/
bool tcp_compliant;
};
FIXTURE_VARIANT(msg_oob)
{
bool peek;
};
FIXTURE_VARIANT_ADD(msg_oob, no_peek)
{
.peek = false,
};
FIXTURE_VARIANT_ADD(msg_oob, peek)
{
.peek = true
};
static void create_unix_socketpair(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self)
{
int ret;
ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, self->fd);
ASSERT_EQ(ret, 0);
}
static void create_tcp_socketpair(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self)
{
struct sockaddr_in addr;
socklen_t addrlen;
int listen_fd;
int ret;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_GE(listen_fd, 0);
ret = listen(listen_fd, -1);
ASSERT_EQ(ret, 0);
addrlen = sizeof(addr);
ret = getsockname(listen_fd, (struct sockaddr *)&addr, &addrlen);
ASSERT_EQ(ret, 0);
self->fd[2] = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_GE(self->fd[2], 0);
ret = connect(self->fd[2], (struct sockaddr *)&addr, addrlen);
ASSERT_EQ(ret, 0);
self->fd[3] = accept(listen_fd, (struct sockaddr *)&addr, &addrlen);
ASSERT_GE(self->fd[3], 0);
ret = fcntl(self->fd[3], F_SETFL, O_NONBLOCK);
ASSERT_EQ(ret, 0);
}
static void setup_sigurg(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self)
{
struct signalfd_siginfo siginfo;
int pid = getpid();
sigset_t mask;
int i, ret;
for (i = 0; i < 2; i++) {
ret = ioctl(self->fd[i * 2 + 1], FIOSETOWN, &pid);
ASSERT_EQ(ret, 0);
}
ret = sigemptyset(&mask);
ASSERT_EQ(ret, 0);
ret = sigaddset(&mask, SIGURG);
ASSERT_EQ(ret, 0);
ret = sigprocmask(SIG_BLOCK, &mask, NULL);
ASSERT_EQ(ret, 0);
self->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK);
ASSERT_GE(self->signal_fd, 0);
ret = read(self->signal_fd, &siginfo, sizeof(siginfo));
ASSERT_EQ(ret, -1);
}
static void setup_epollpri(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self)
{
struct epoll_event event = {
.events = EPOLLPRI,
};
int i;
for (i = 0; i < 2; i++) {
int ret;
self->epoll_fd[i] = epoll_create1(0);
ASSERT_GE(self->epoll_fd[i], 0);
ret = epoll_ctl(self->epoll_fd[i], EPOLL_CTL_ADD, self->fd[i * 2 + 1], &event);
ASSERT_EQ(ret, 0);
}
}
static void close_sockets(FIXTURE_DATA(msg_oob) *self)
{
int i;
for (i = 0; i < 4; i++)
close(self->fd[i]);
}
FIXTURE_SETUP(msg_oob)
{
create_unix_socketpair(_metadata, self);
create_tcp_socketpair(_metadata, self);
setup_sigurg(_metadata, self);
setup_epollpri(_metadata, self);
self->tcp_compliant = true;
}
FIXTURE_TEARDOWN(msg_oob)
{
close_sockets(self);
}
static void __epollpair(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self,
bool oob_remaining)
{
struct epoll_event event[2] = {};
int i, ret[2];
for (i = 0; i < 2; i++)
ret[i] = epoll_wait(self->epoll_fd[i], &event[i], 1, 0);
ASSERT_EQ(ret[0], oob_remaining);
if (self->tcp_compliant)
ASSERT_EQ(ret[0], ret[1]);
if (oob_remaining) {
ASSERT_EQ(event[0].events, EPOLLPRI);
if (self->tcp_compliant)
ASSERT_EQ(event[0].events, event[1].events);
}
}
static void __sendpair(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self,
const void *buf, size_t len, int flags)
{
int i, ret[2];
for (i = 0; i < 2; i++) {
struct signalfd_siginfo siginfo = {};
int bytes;
ret[i] = send(self->fd[i * 2], buf, len, flags);
bytes = read(self->signal_fd, &siginfo, sizeof(siginfo));
if (flags & MSG_OOB) {
ASSERT_EQ(bytes, sizeof(siginfo));
ASSERT_EQ(siginfo.ssi_signo, SIGURG);
bytes = read(self->signal_fd, &siginfo, sizeof(siginfo));
}
ASSERT_EQ(bytes, -1);
}
ASSERT_EQ(ret[0], len);
ASSERT_EQ(ret[0], ret[1]);
}
static void __recvpair(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self,
const char *expected_buf, int expected_len,
int buf_len, int flags, bool is_sender)
{
int i, ret[2], recv_errno[2], expected_errno = 0;
char recv_buf[2][BUF_SZ] = {};
bool printed = false;
ASSERT_GE(BUF_SZ, buf_len);
errno = 0;
for (i = 0; i < 2; i++) {
int index = is_sender ? i * 2 : i * 2 + 1;
ret[i] = recv(self->fd[index], recv_buf[i], buf_len, flags);
recv_errno[i] = errno;
}
if (expected_len < 0) {
expected_errno = -expected_len;
expected_len = -1;
}
if (ret[0] != expected_len || recv_errno[0] != expected_errno) {
TH_LOG("AF_UNIX :%s", ret[0] < 0 ? strerror(recv_errno[0]) : recv_buf[0]);
TH_LOG("Expected:%s", expected_errno ? strerror(expected_errno) : expected_buf);
ASSERT_EQ(ret[0], expected_len);
ASSERT_EQ(recv_errno[0], expected_errno);
}
if (ret[0] != ret[1] || recv_errno[0] != recv_errno[1]) {
TH_LOG("AF_UNIX :%s", ret[0] < 0 ? strerror(recv_errno[0]) : recv_buf[0]);
TH_LOG("TCP :%s", ret[1] < 0 ? strerror(recv_errno[1]) : recv_buf[1]);
printed = true;
if (self->tcp_compliant) {
ASSERT_EQ(ret[0], ret[1]);
ASSERT_EQ(recv_errno[0], recv_errno[1]);
}
}
if (expected_len >= 0) {
int cmp;
cmp = strncmp(expected_buf, recv_buf[0], expected_len);
if (cmp) {
TH_LOG("AF_UNIX :%s", ret[0] < 0 ? strerror(recv_errno[0]) : recv_buf[0]);
TH_LOG("Expected:%s", expected_errno ? strerror(expected_errno) : expected_buf);
ASSERT_EQ(cmp, 0);
}
cmp = strncmp(recv_buf[0], recv_buf[1], expected_len);
if (cmp) {
if (!printed) {
TH_LOG("AF_UNIX :%s", ret[0] < 0 ? strerror(recv_errno[0]) : recv_buf[0]);
TH_LOG("TCP :%s", ret[1] < 0 ? strerror(recv_errno[1]) : recv_buf[1]);
}
if (self->tcp_compliant)
ASSERT_EQ(cmp, 0);
}
}
}
static void __setinlinepair(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self)
{
int i, oob_inline = 1;
for (i = 0; i < 2; i++) {
int ret;
ret = setsockopt(self->fd[i * 2 + 1], SOL_SOCKET, SO_OOBINLINE,
&oob_inline, sizeof(oob_inline));
ASSERT_EQ(ret, 0);
}
}
static void __siocatmarkpair(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self,
bool oob_head)
{
int answ[2] = {};
int i;
for (i = 0; i < 2; i++) {
int ret;
ret = ioctl(self->fd[i * 2 + 1], SIOCATMARK, &answ[i]);
ASSERT_EQ(ret, 0);
}
ASSERT_EQ(answ[0], oob_head);
if (self->tcp_compliant)
ASSERT_EQ(answ[0], answ[1]);
}
static void __resetpair(struct __test_metadata *_metadata,
FIXTURE_DATA(msg_oob) *self,
const FIXTURE_VARIANT(msg_oob) *variant,
bool reset)
{
int i;
for (i = 0; i < 2; i++)
close(self->fd[i * 2 + 1]);
__recvpair(_metadata, self, "", reset ? -ECONNRESET : 0, 1,
variant->peek ? MSG_PEEK : 0, true);
}
#define sendpair(buf, len, flags) \
__sendpair(_metadata, self, buf, len, flags)
#define recvpair(expected_buf, expected_len, buf_len, flags) \
do { \
if (variant->peek) \
__recvpair(_metadata, self, \
expected_buf, expected_len, \
buf_len, (flags) | MSG_PEEK, false); \
__recvpair(_metadata, self, \
expected_buf, expected_len, \
buf_len, flags, false); \
} while (0)
#define epollpair(oob_remaining) \
__epollpair(_metadata, self, oob_remaining)
#define siocatmarkpair(oob_head) \
__siocatmarkpair(_metadata, self, oob_head)
#define setinlinepair() \
__setinlinepair(_metadata, self)
#define resetpair(reset) \
__resetpair(_metadata, self, variant, reset)
#define tcp_incompliant \
for (self->tcp_compliant = false; \
self->tcp_compliant == false; \
self->tcp_compliant = true)
TEST_F(msg_oob, non_oob)
{
sendpair("x", 1, 0);
epollpair(false);
siocatmarkpair(false);
recvpair("", -EINVAL, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
resetpair(true);
}
TEST_F(msg_oob, non_oob_no_reset)
{
sendpair("x", 1, 0);
epollpair(false);
siocatmarkpair(false);
recvpair("x", 1, 1, 0);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, oob)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("x", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
tcp_incompliant {
resetpair(false); /* TCP sets -ECONNRESET for ex-OOB. */
}
}
TEST_F(msg_oob, oob_reset)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
resetpair(true);
}
TEST_F(msg_oob, oob_drop)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("", -EAGAIN, 1, 0); /* Drop OOB. */
epollpair(false);
siocatmarkpair(false);
recvpair("", -EINVAL, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, oob_ahead)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("o", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
recvpair("hell", 4, 4, 0);
epollpair(false);
siocatmarkpair(true);
tcp_incompliant {
resetpair(false); /* TCP sets -ECONNRESET for ex-OOB. */
}
}
TEST_F(msg_oob, oob_break)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("hell", 4, 5, 0); /* Break at OOB even with enough buffer. */
epollpair(true);
siocatmarkpair(true);
recvpair("o", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
recvpair("", -EAGAIN, 1, 0);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, oob_ahead_break)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
sendpair("world", 5, 0);
epollpair(true);
siocatmarkpair(false);
recvpair("o", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
recvpair("hell", 4, 9, 0); /* Break at OOB even after it's recv()ed. */
epollpair(false);
siocatmarkpair(true);
recvpair("world", 5, 5, 0);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, oob_break_drop)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
sendpair("world", 5, 0);
epollpair(true);
siocatmarkpair(false);
recvpair("hell", 4, 10, 0); /* Break at OOB even with enough buffer. */
epollpair(true);
siocatmarkpair(true);
recvpair("world", 5, 10, 0); /* Drop OOB and recv() the next skb. */
epollpair(false);
siocatmarkpair(false);
recvpair("", -EINVAL, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, ex_oob_break)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
sendpair("wor", 3, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
sendpair("ld", 2, 0);
epollpair(true);
siocatmarkpair(false);
recvpair("hellowo", 7, 10, 0); /* Break at OOB but not at ex-OOB. */
epollpair(true);
siocatmarkpair(true);
recvpair("r", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
recvpair("ld", 2, 2, 0);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, ex_oob_drop)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
sendpair("y", 1, MSG_OOB); /* TCP drops "x" at this moment. */
epollpair(true);
tcp_incompliant {
siocatmarkpair(false);
recvpair("x", 1, 1, 0); /* TCP drops "y" by passing through it. */
epollpair(true);
siocatmarkpair(true);
recvpair("y", 1, 1, MSG_OOB); /* TCP returns -EINVAL. */
epollpair(false);
siocatmarkpair(true);
}
resetpair(false);
}
TEST_F(msg_oob, ex_oob_drop_2)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
sendpair("y", 1, MSG_OOB); /* TCP drops "x" at this moment. */
epollpair(true);
tcp_incompliant {
siocatmarkpair(false);
}
recvpair("y", 1, 1, MSG_OOB);
epollpair(false);
tcp_incompliant {
siocatmarkpair(false);
recvpair("x", 1, 1, 0); /* TCP returns -EAGAIN. */
epollpair(false);
siocatmarkpair(true);
}
resetpair(false);
}
TEST_F(msg_oob, ex_oob_oob)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("x", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
sendpair("y", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("", -EAGAIN, 1, 0);
epollpair(false);
siocatmarkpair(false);
recvpair("", -EINVAL, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, ex_oob_ex_oob)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("x", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
sendpair("y", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("y", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
tcp_incompliant {
resetpair(false); /* TCP sets -ECONNRESET for ex-OOB. */
}
}
TEST_F(msg_oob, ex_oob_ex_oob_oob)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("x", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
sendpair("y", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("y", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
sendpair("z", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
}
TEST_F(msg_oob, ex_oob_ahead_break)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
sendpair("wor", 3, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("r", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
sendpair("ld", 2, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
tcp_incompliant {
recvpair("hellowol", 8, 10, 0); /* TCP recv()s "helloworl", why "r" ?? */
}
epollpair(true);
siocatmarkpair(true);
recvpair("d", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(true);
tcp_incompliant {
resetpair(false); /* TCP sets -ECONNRESET for ex-OOB. */
}
}
TEST_F(msg_oob, ex_oob_siocatmark)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("o", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
sendpair("world", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("hell", 4, 4, 0); /* Intentionally stop at ex-OOB. */
epollpair(true);
siocatmarkpair(false);
resetpair(true);
}
TEST_F(msg_oob, inline_oob)
{
setinlinepair();
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("", -EINVAL, 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
recvpair("x", 1, 1, 0);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, inline_oob_break)
{
setinlinepair();
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("", -EINVAL, 1, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("hell", 4, 5, 0); /* Break at OOB but not at ex-OOB. */
epollpair(true);
siocatmarkpair(true);
recvpair("o", 1, 1, 0);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, inline_oob_ahead_break)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
sendpair("world", 5, 0);
epollpair(true);
siocatmarkpair(false);
recvpair("o", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
setinlinepair();
recvpair("hell", 4, 9, 0); /* Break at OOB even with enough buffer. */
epollpair(false);
siocatmarkpair(true);
tcp_incompliant {
recvpair("world", 5, 6, 0); /* TCP recv()s "oworld", ... "o" ??? */
}
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, inline_ex_oob_break)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
sendpair("wor", 3, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
sendpair("ld", 2, 0);
epollpair(true);
siocatmarkpair(false);
setinlinepair();
recvpair("hellowo", 7, 10, 0); /* Break at OOB but not at ex-OOB. */
epollpair(true);
siocatmarkpair(true);
recvpair("rld", 3, 3, 0);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, inline_ex_oob_no_drop)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
setinlinepair();
sendpair("y", 1, MSG_OOB); /* TCP does NOT drops "x" at this moment. */
epollpair(true);
siocatmarkpair(false);
recvpair("x", 1, 1, 0);
epollpair(true);
siocatmarkpair(true);
recvpair("y", 1, 1, 0);
epollpair(false);
siocatmarkpair(false);
resetpair(false);
}
TEST_F(msg_oob, inline_ex_oob_drop)
{
sendpair("x", 1, MSG_OOB);
epollpair(true);
siocatmarkpair(true);
sendpair("y", 1, MSG_OOB); /* TCP drops "x" at this moment. */
epollpair(true);
setinlinepair();
tcp_incompliant {
siocatmarkpair(false);
recvpair("x", 1, 1, 0); /* TCP recv()s "y". */
epollpair(true);
siocatmarkpair(true);
recvpair("y", 1, 1, 0); /* TCP returns -EAGAIN. */
epollpair(false);
siocatmarkpair(false);
}
resetpair(false);
}
TEST_F(msg_oob, inline_ex_oob_siocatmark)
{
sendpair("hello", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("o", 1, 1, MSG_OOB);
epollpair(false);
siocatmarkpair(false);
setinlinepair();
sendpair("world", 5, MSG_OOB);
epollpair(true);
siocatmarkpair(false);
recvpair("hell", 4, 4, 0); /* Intentionally stop at ex-OOB. */
epollpair(true);
siocatmarkpair(false);
resetpair(true);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2025 Google LLC */
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "kselftest_harness.h"
#define NR_CHUNKS 100
#define MSG_LEN 256
#define NR_PARTIAL_READS 3
FIXTURE(scm_inq)
{
int fd[2];
};
FIXTURE_VARIANT(scm_inq)
{
int type;
};
FIXTURE_VARIANT_ADD(scm_inq, stream)
{
.type = SOCK_STREAM,
};
FIXTURE_VARIANT_ADD(scm_inq, dgram)
{
.type = SOCK_DGRAM,
};
FIXTURE_VARIANT_ADD(scm_inq, seqpacket)
{
.type = SOCK_SEQPACKET,
};
FIXTURE_SETUP(scm_inq)
{
int err;
err = socketpair(AF_UNIX, variant->type | SOCK_NONBLOCK, 0, self->fd);
ASSERT_EQ(0, err);
}
FIXTURE_TEARDOWN(scm_inq)
{
close(self->fd[0]);
close(self->fd[1]);
}
static void send_chunks(struct __test_metadata *_metadata,
FIXTURE_DATA(scm_inq) *self)
{
char buf[MSG_LEN] = {};
int i, ret;
for (i = 0; i < NR_CHUNKS; i++) {
ret = send(self->fd[0], buf, sizeof(buf), 0);
ASSERT_EQ(sizeof(buf), ret);
}
}
static void recv_chunks(struct __test_metadata *_metadata,
FIXTURE_DATA(scm_inq) *self)
{
char cmsg_buf[CMSG_SPACE(sizeof(int))];
struct msghdr msg = {};
struct iovec iov = {};
struct cmsghdr *cmsg;
char buf[MSG_LEN];
int i, ret;
int inq;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
for (i = 0; i < NR_CHUNKS; i++) {
memset(buf, 0, sizeof(buf));
memset(cmsg_buf, 0, sizeof(cmsg_buf));
ret = recvmsg(self->fd[1], &msg, 0);
ASSERT_EQ(MSG_LEN, ret);
cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(NULL, cmsg);
ASSERT_EQ(CMSG_LEN(sizeof(int)), cmsg->cmsg_len);
ASSERT_EQ(SOL_SOCKET, cmsg->cmsg_level);
ASSERT_EQ(SCM_INQ, cmsg->cmsg_type);
ret = ioctl(self->fd[1], SIOCINQ, &inq);
ASSERT_EQ(0, ret);
ASSERT_EQ(*(int *)CMSG_DATA(cmsg), inq);
}
}
TEST_F(scm_inq, basic)
{
int err, inq;
err = setsockopt(self->fd[1], SOL_SOCKET, SO_INQ, &(int){1}, sizeof(int));
if (variant->type != SOCK_STREAM) {
ASSERT_EQ(-ENOPROTOOPT, -errno);
return;
}
ASSERT_EQ(0, err);
err = ioctl(self->fd[1], SIOCINQ, &inq);
ASSERT_EQ(0, err);
ASSERT_EQ(0, inq);
send_chunks(_metadata, self);
recv_chunks(_metadata, self);
}
TEST_F(scm_inq, partial_read)
{
char buf[MSG_LEN * NR_PARTIAL_READS] = {};
char cmsg_buf[CMSG_SPACE(sizeof(int))];
struct msghdr msg = {};
struct iovec iov = {};
struct cmsghdr *cmsg;
int err, inq, ret, i;
int remain;
err = setsockopt(self->fd[1], SOL_SOCKET, SO_INQ, &(int){1}, sizeof(int));
if (variant->type != SOCK_STREAM) {
ASSERT_EQ(-ENOPROTOOPT, -errno);
return;
}
ASSERT_EQ(0, err);
ret = send(self->fd[0], buf, sizeof(buf), 0);
ASSERT_EQ(sizeof(buf), ret);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
iov.iov_base = buf;
iov.iov_len = MSG_LEN;
for (i = 0; i < NR_PARTIAL_READS; i++) {
remain = MSG_LEN * (NR_PARTIAL_READS - 1 - i);
memset(buf, 0, MSG_LEN);
memset(cmsg_buf, 0, sizeof(cmsg_buf));
ret = recvmsg(self->fd[1], &msg, 0);
ASSERT_EQ(MSG_LEN, ret);
cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(NULL, cmsg);
ASSERT_EQ(CMSG_LEN(sizeof(int)), cmsg->cmsg_len);
ASSERT_EQ(SOL_SOCKET, cmsg->cmsg_level);
ASSERT_EQ(SCM_INQ, cmsg->cmsg_type);
ASSERT_EQ(remain, *(int *)CMSG_DATA(cmsg));
ret = ioctl(self->fd[1], SIOCINQ, &inq);
ASSERT_EQ(0, ret);
ASSERT_EQ(remain, inq);
}
}
TEST_HARNESS_MAIN
@@ -0,0 +1,556 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
#define _GNU_SOURCE
#include <error.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <linux/socket.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/un.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "../../pidfd/pidfd.h"
#include "kselftest_harness.h"
#define clean_errno() (errno == 0 ? "None" : strerror(errno))
#define log_err(MSG, ...) \
fprintf(stderr, "(%s:%d: errno: %s) " MSG "\n", __FILE__, __LINE__, \
clean_errno(), ##__VA_ARGS__)
#ifndef SCM_PIDFD
#define SCM_PIDFD 0x04
#endif
#define CHILD_EXIT_CODE_OK 123
static void child_die()
{
exit(1);
}
static int safe_int(const char *numstr, int *converted)
{
char *err = NULL;
long sli;
errno = 0;
sli = strtol(numstr, &err, 0);
if (errno == ERANGE && (sli == LONG_MAX || sli == LONG_MIN))
return -ERANGE;
if (errno != 0 && sli == 0)
return -EINVAL;
if (err == numstr || *err != '\0')
return -EINVAL;
if (sli > INT_MAX || sli < INT_MIN)
return -ERANGE;
*converted = (int)sli;
return 0;
}
static int char_left_gc(const char *buffer, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
if (buffer[i] == ' ' || buffer[i] == '\t')
continue;
return i;
}
return 0;
}
static int char_right_gc(const char *buffer, size_t len)
{
int i;
for (i = len - 1; i >= 0; i--) {
if (buffer[i] == ' ' || buffer[i] == '\t' ||
buffer[i] == '\n' || buffer[i] == '\0')
continue;
return i + 1;
}
return 0;
}
static char *trim_whitespace_in_place(char *buffer)
{
buffer += char_left_gc(buffer, strlen(buffer));
buffer[char_right_gc(buffer, strlen(buffer))] = '\0';
return buffer;
}
/* borrowed (with all helpers) from pidfd/pidfd_open_test.c */
static pid_t get_pid_from_fdinfo_file(int pidfd, const char *key, size_t keylen)
{
int ret;
char path[512];
FILE *f;
size_t n = 0;
pid_t result = -1;
char *line = NULL;
snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", pidfd);
f = fopen(path, "re");
if (!f)
return -1;
while (getline(&line, &n, f) != -1) {
char *numstr;
if (strncmp(line, key, keylen))
continue;
numstr = trim_whitespace_in_place(line + 4);
ret = safe_int(numstr, &result);
if (ret < 0)
goto out;
break;
}
out:
free(line);
fclose(f);
return result;
}
struct cmsg_data {
struct ucred *ucred;
int *pidfd;
};
static int parse_cmsg(struct msghdr *msg, struct cmsg_data *res)
{
struct cmsghdr *cmsg;
if (msg->msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
log_err("recvmsg: truncated");
return 1;
}
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_PIDFD) {
if (cmsg->cmsg_len < sizeof(*res->pidfd)) {
log_err("CMSG parse: SCM_PIDFD wrong len");
return 1;
}
res->pidfd = (void *)CMSG_DATA(cmsg);
}
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_CREDENTIALS) {
if (cmsg->cmsg_len < sizeof(*res->ucred)) {
log_err("CMSG parse: SCM_CREDENTIALS wrong len");
return 1;
}
res->ucred = (void *)CMSG_DATA(cmsg);
}
}
if (!res->pidfd) {
log_err("CMSG parse: SCM_PIDFD not found");
return 1;
}
if (!res->ucred) {
log_err("CMSG parse: SCM_CREDENTIALS not found");
return 1;
}
return 0;
}
static int cmsg_check(int fd)
{
struct msghdr msg = { 0 };
struct cmsg_data res;
struct iovec iov;
int data = 0;
char control[CMSG_SPACE(sizeof(struct ucred)) +
CMSG_SPACE(sizeof(int))] = { 0 };
pid_t parent_pid;
int err;
iov.iov_base = &data;
iov.iov_len = sizeof(data);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
err = recvmsg(fd, &msg, 0);
if (err < 0) {
log_err("recvmsg");
return 1;
}
if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
log_err("recvmsg: truncated");
return 1;
}
/* send(pfd, "x", sizeof(char), 0) */
if (data != 'x') {
log_err("recvmsg: data corruption");
return 1;
}
if (parse_cmsg(&msg, &res)) {
log_err("CMSG parse: parse_cmsg() failed");
return 1;
}
/* pidfd from SCM_PIDFD should point to the parent process PID */
parent_pid =
get_pid_from_fdinfo_file(*res.pidfd, "Pid:", sizeof("Pid:") - 1);
if (parent_pid != getppid()) {
log_err("wrong SCM_PIDFD %d != %d", parent_pid, getppid());
close(*res.pidfd);
return 1;
}
close(*res.pidfd);
return 0;
}
static int cmsg_check_dead(int fd, int expected_pid)
{
int err;
struct msghdr msg = { 0 };
struct cmsg_data res;
struct iovec iov;
int data = 0;
char control[CMSG_SPACE(sizeof(struct ucred)) +
CMSG_SPACE(sizeof(int))] = { 0 };
struct pidfd_info info = {
.mask = PIDFD_INFO_EXIT,
};
iov.iov_base = &data;
iov.iov_len = sizeof(data);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
err = recvmsg(fd, &msg, 0);
if (err < 0) {
log_err("recvmsg");
return 1;
}
if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
log_err("recvmsg: truncated");
return 1;
}
/* send(cfd, "y", sizeof(char), 0) */
if (data != 'y') {
log_err("recvmsg: data corruption");
return 1;
}
if (parse_cmsg(&msg, &res)) {
log_err("CMSG parse: parse_cmsg() failed");
return 1;
}
/*
* pidfd from SCM_PIDFD should point to the client_pid.
* Let's read exit information and check if it's what
* we expect to see.
*/
if (ioctl(*res.pidfd, PIDFD_GET_INFO, &info)) {
log_err("%s: ioctl(PIDFD_GET_INFO) failed", __func__);
close(*res.pidfd);
return 1;
}
if (!(info.mask & PIDFD_INFO_EXIT)) {
log_err("%s: No exit information from ioctl(PIDFD_GET_INFO)", __func__);
close(*res.pidfd);
return 1;
}
err = WIFEXITED(info.exit_code) ? WEXITSTATUS(info.exit_code) : 1;
if (err != CHILD_EXIT_CODE_OK) {
log_err("%s: wrong exit_code %d != %d", __func__, err, CHILD_EXIT_CODE_OK);
close(*res.pidfd);
return 1;
}
close(*res.pidfd);
return 0;
}
struct sock_addr {
char sock_name[32];
struct sockaddr_un listen_addr;
socklen_t addrlen;
};
FIXTURE(scm_pidfd)
{
int server;
pid_t client_pid;
int startup_pipe[2];
struct sock_addr server_addr;
struct sock_addr *client_addr;
};
FIXTURE_VARIANT(scm_pidfd)
{
int type;
bool abstract;
};
FIXTURE_VARIANT_ADD(scm_pidfd, stream_pathname)
{
.type = SOCK_STREAM,
.abstract = 0,
};
FIXTURE_VARIANT_ADD(scm_pidfd, stream_abstract)
{
.type = SOCK_STREAM,
.abstract = 1,
};
FIXTURE_VARIANT_ADD(scm_pidfd, dgram_pathname)
{
.type = SOCK_DGRAM,
.abstract = 0,
};
FIXTURE_VARIANT_ADD(scm_pidfd, dgram_abstract)
{
.type = SOCK_DGRAM,
.abstract = 1,
};
FIXTURE_SETUP(scm_pidfd)
{
self->client_addr = mmap(NULL, sizeof(*self->client_addr), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(MAP_FAILED, self->client_addr);
}
FIXTURE_TEARDOWN(scm_pidfd)
{
close(self->server);
kill(self->client_pid, SIGKILL);
waitpid(self->client_pid, NULL, 0);
if (!variant->abstract) {
unlink(self->server_addr.sock_name);
unlink(self->client_addr->sock_name);
}
}
static void fill_sockaddr(struct sock_addr *addr, bool abstract)
{
char *sun_path_buf = (char *)&addr->listen_addr.sun_path;
addr->listen_addr.sun_family = AF_UNIX;
addr->addrlen = offsetof(struct sockaddr_un, sun_path);
snprintf(addr->sock_name, sizeof(addr->sock_name), "scm_pidfd_%d", getpid());
addr->addrlen += strlen(addr->sock_name);
if (abstract) {
*sun_path_buf = '\0';
addr->addrlen++;
sun_path_buf++;
} else {
unlink(addr->sock_name);
}
memcpy(sun_path_buf, addr->sock_name, strlen(addr->sock_name));
}
static int sk_enable_cred_pass(int sk)
{
int on = 0;
on = 1;
if (setsockopt(sk, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) {
log_err("Failed to set SO_PASSCRED");
return 1;
}
if (setsockopt(sk, SOL_SOCKET, SO_PASSPIDFD, &on, sizeof(on))) {
log_err("Failed to set SO_PASSPIDFD");
return 1;
}
return 0;
}
static void client(FIXTURE_DATA(scm_pidfd) *self,
const FIXTURE_VARIANT(scm_pidfd) *variant)
{
int cfd;
socklen_t len;
struct ucred peer_cred;
int peer_pidfd;
pid_t peer_pid;
cfd = socket(AF_UNIX, variant->type, 0);
if (cfd < 0) {
log_err("socket");
child_die();
}
if (variant->type == SOCK_DGRAM) {
fill_sockaddr(self->client_addr, variant->abstract);
if (bind(cfd, (struct sockaddr *)&self->client_addr->listen_addr, self->client_addr->addrlen)) {
log_err("bind");
child_die();
}
}
if (connect(cfd, (struct sockaddr *)&self->server_addr.listen_addr,
self->server_addr.addrlen) != 0) {
log_err("connect");
child_die();
}
if (sk_enable_cred_pass(cfd)) {
log_err("sk_enable_cred_pass() failed");
child_die();
}
close(self->startup_pipe[1]);
if (cmsg_check(cfd)) {
log_err("cmsg_check failed");
child_die();
}
/* send something to the parent so it can receive SCM_PIDFD too and validate it */
if (send(cfd, "y", sizeof(char), 0) == -1) {
log_err("Failed to send(cfd, \"y\", sizeof(char), 0)");
child_die();
}
/* skip further for SOCK_DGRAM as it's not applicable */
if (variant->type == SOCK_DGRAM)
return;
len = sizeof(peer_cred);
if (getsockopt(cfd, SOL_SOCKET, SO_PEERCRED, &peer_cred, &len)) {
log_err("Failed to get SO_PEERCRED");
child_die();
}
len = sizeof(peer_pidfd);
if (getsockopt(cfd, SOL_SOCKET, SO_PEERPIDFD, &peer_pidfd, &len)) {
log_err("Failed to get SO_PEERPIDFD");
child_die();
}
/* pid from SO_PEERCRED should point to the parent process PID */
if (peer_cred.pid != getppid()) {
log_err("peer_cred.pid != getppid(): %d != %d", peer_cred.pid, getppid());
child_die();
}
peer_pid = get_pid_from_fdinfo_file(peer_pidfd,
"Pid:", sizeof("Pid:") - 1);
if (peer_pid != peer_cred.pid) {
log_err("peer_pid != peer_cred.pid: %d != %d", peer_pid, peer_cred.pid);
child_die();
}
}
TEST_F(scm_pidfd, test)
{
int err;
int pfd;
int child_status = 0;
self->server = socket(AF_UNIX, variant->type, 0);
ASSERT_NE(-1, self->server);
fill_sockaddr(&self->server_addr, variant->abstract);
err = bind(self->server, (struct sockaddr *)&self->server_addr.listen_addr, self->server_addr.addrlen);
ASSERT_EQ(0, err);
if (variant->type == SOCK_STREAM) {
err = listen(self->server, 1);
ASSERT_EQ(0, err);
}
err = pipe(self->startup_pipe);
ASSERT_NE(-1, err);
self->client_pid = fork();
ASSERT_NE(-1, self->client_pid);
if (self->client_pid == 0) {
close(self->server);
close(self->startup_pipe[0]);
client(self, variant);
/*
* It's a bit unusual, but in case of success we return non-zero
* exit code (CHILD_EXIT_CODE_OK) and then we expect to read it
* from ioctl(PIDFD_GET_INFO) in cmsg_check_dead().
*/
exit(CHILD_EXIT_CODE_OK);
}
close(self->startup_pipe[1]);
if (variant->type == SOCK_STREAM) {
pfd = accept(self->server, NULL, NULL);
ASSERT_NE(-1, pfd);
} else {
pfd = self->server;
}
/* wait until the child arrives at checkpoint */
read(self->startup_pipe[0], &err, sizeof(int));
close(self->startup_pipe[0]);
if (variant->type == SOCK_DGRAM) {
err = sendto(pfd, "x", sizeof(char), 0, (struct sockaddr *)&self->client_addr->listen_addr, self->client_addr->addrlen);
ASSERT_NE(-1, err);
} else {
err = send(pfd, "x", sizeof(char), 0);
ASSERT_NE(-1, err);
}
waitpid(self->client_pid, &child_status, 0);
/* see comment before exit(CHILD_EXIT_CODE_OK) */
ASSERT_EQ(CHILD_EXIT_CODE_OK, WIFEXITED(child_status) ? WEXITSTATUS(child_status) : 1);
err = sk_enable_cred_pass(pfd);
ASSERT_EQ(0, err);
err = cmsg_check_dead(pfd, self->client_pid);
ASSERT_EQ(0, err);
close(pfd);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright Amazon.com Inc. or its affiliates. */
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "kselftest_harness.h"
FIXTURE(scm_rights)
{
int fd[32];
};
FIXTURE_VARIANT(scm_rights)
{
char name[32];
int type;
int flags;
bool test_listener;
bool disabled;
};
FIXTURE_VARIANT_ADD(scm_rights, dgram)
{
.name = "UNIX ",
.type = SOCK_DGRAM,
.flags = 0,
.test_listener = false,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, dgram_disabled)
{
.name = "UNIX ",
.type = SOCK_DGRAM,
.flags = 0,
.test_listener = false,
.disabled = true,
};
FIXTURE_VARIANT_ADD(scm_rights, stream)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = 0,
.test_listener = false,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_disabled)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = 0,
.test_listener = false,
.disabled = true,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_oob)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = MSG_OOB,
.test_listener = false,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_oob_disabled)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = MSG_OOB,
.test_listener = false,
.disabled = true,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_listener)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = 0,
.test_listener = true,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_listener_disabled)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = 0,
.test_listener = true,
.disabled = true,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_listener_oob)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = MSG_OOB,
.test_listener = true,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_listener_oob_disabled)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = MSG_OOB,
.test_listener = true,
.disabled = true,
};
static int count_sockets(struct __test_metadata *_metadata,
const FIXTURE_VARIANT(scm_rights) *variant)
{
int sockets = -1, len, ret;
char *line = NULL;
size_t unused;
FILE *f;
f = fopen("/proc/net/protocols", "r");
ASSERT_NE(NULL, f);
len = strlen(variant->name);
while (getline(&line, &unused, f) != -1) {
int unused2;
if (strncmp(line, variant->name, len))
continue;
ret = sscanf(line + len, "%d %d", &unused2, &sockets);
ASSERT_EQ(2, ret);
break;
}
free(line);
ret = fclose(f);
ASSERT_EQ(0, ret);
return sockets;
}
FIXTURE_SETUP(scm_rights)
{
int ret;
ret = unshare(CLONE_NEWNET);
ASSERT_EQ(0, ret);
if (variant->disabled)
return;
ret = count_sockets(_metadata, variant);
ASSERT_EQ(0, ret);
}
FIXTURE_TEARDOWN(scm_rights)
{
int ret;
if (variant->disabled)
return;
sleep(1);
ret = count_sockets(_metadata, variant);
ASSERT_EQ(0, ret);
}
static void create_listeners(struct __test_metadata *_metadata,
FIXTURE_DATA(scm_rights) *self,
const FIXTURE_VARIANT(scm_rights) *variant,
int n)
{
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
socklen_t addrlen;
int i, ret;
for (i = 0; i < n * 2; i += 2) {
self->fd[i] = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, self->fd[i]);
addrlen = sizeof(addr.sun_family);
ret = bind(self->fd[i], (struct sockaddr *)&addr, addrlen);
ASSERT_EQ(0, ret);
ret = listen(self->fd[i], -1);
ASSERT_EQ(0, ret);
if (variant->disabled) {
ret = setsockopt(self->fd[i], SOL_SOCKET, SO_PASSRIGHTS,
&(int){0}, sizeof(int));
ASSERT_EQ(0, ret);
}
addrlen = sizeof(addr);
ret = getsockname(self->fd[i], (struct sockaddr *)&addr, &addrlen);
ASSERT_EQ(0, ret);
self->fd[i + 1] = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, self->fd[i + 1]);
ret = connect(self->fd[i + 1], (struct sockaddr *)&addr, addrlen);
ASSERT_EQ(0, ret);
}
}
static void create_socketpairs(struct __test_metadata *_metadata,
FIXTURE_DATA(scm_rights) *self,
const FIXTURE_VARIANT(scm_rights) *variant,
int n)
{
int i, ret;
ASSERT_GE(sizeof(self->fd) / sizeof(int), n);
for (i = 0; i < n * 2; i += 2) {
ret = socketpair(AF_UNIX, variant->type, 0, self->fd + i);
ASSERT_EQ(0, ret);
if (variant->disabled) {
ret = setsockopt(self->fd[i], SOL_SOCKET, SO_PASSRIGHTS,
&(int){0}, sizeof(int));
ASSERT_EQ(0, ret);
}
}
}
static void __create_sockets(struct __test_metadata *_metadata,
FIXTURE_DATA(scm_rights) *self,
const FIXTURE_VARIANT(scm_rights) *variant,
int n)
{
ASSERT_LE(n * 2, sizeof(self->fd) / sizeof(self->fd[0]));
if (variant->test_listener)
create_listeners(_metadata, self, variant, n);
else
create_socketpairs(_metadata, self, variant, n);
}
static void __close_sockets(struct __test_metadata *_metadata,
FIXTURE_DATA(scm_rights) *self,
int n)
{
int i, ret;
ASSERT_GE(sizeof(self->fd) / sizeof(int), n);
for (i = 0; i < n * 2; i++) {
ret = close(self->fd[i]);
ASSERT_EQ(0, ret);
}
}
void __send_fd(struct __test_metadata *_metadata,
const FIXTURE_DATA(scm_rights) *self,
const FIXTURE_VARIANT(scm_rights) *variant,
int inflight, int receiver)
{
#define MSG "x"
#define MSGLEN 1
int fds[2] = {
self->fd[inflight * 2],
self->fd[inflight * 2],
};
char cmsg_buf[CMSG_SPACE(sizeof(fds))];
struct iovec iov = {
.iov_base = MSG,
.iov_len = MSGLEN,
};
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsg_buf,
.msg_controllen = sizeof(cmsg_buf),
};
struct cmsghdr *cmsg;
int ret;
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
ret = sendmsg(self->fd[receiver * 2 + 1], &msg, variant->flags);
if (variant->disabled) {
ASSERT_EQ(-1, ret);
ASSERT_EQ(-EPERM, -errno);
} else {
ASSERT_EQ(MSGLEN, ret);
}
}
#define create_sockets(n) \
__create_sockets(_metadata, self, variant, n)
#define close_sockets(n) \
__close_sockets(_metadata, self, n)
#define send_fd(inflight, receiver) \
__send_fd(_metadata, self, variant, inflight, receiver)
TEST_F(scm_rights, self_ref)
{
create_sockets(2);
send_fd(0, 0);
send_fd(1, 1);
close_sockets(2);
}
TEST_F(scm_rights, triangle)
{
create_sockets(6);
send_fd(0, 1);
send_fd(1, 2);
send_fd(2, 0);
send_fd(3, 4);
send_fd(4, 5);
send_fd(5, 3);
close_sockets(6);
}
TEST_F(scm_rights, cross_edge)
{
create_sockets(8);
send_fd(0, 1);
send_fd(1, 2);
send_fd(2, 0);
send_fd(1, 3);
send_fd(3, 2);
send_fd(4, 5);
send_fd(5, 6);
send_fd(6, 4);
send_fd(5, 7);
send_fd(7, 6);
close_sockets(8);
}
TEST_F(scm_rights, backtrack_from_scc)
{
create_sockets(10);
send_fd(0, 1);
send_fd(0, 4);
send_fd(1, 2);
send_fd(2, 3);
send_fd(3, 1);
send_fd(5, 6);
send_fd(5, 9);
send_fd(6, 7);
send_fd(7, 8);
send_fd(8, 6);
close_sockets(10);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2025 Google LLC */
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include "../../kselftest_harness.h"
FIXTURE(so_peek_off)
{
int fd[2]; /* 0: sender, 1: receiver */
};
FIXTURE_VARIANT(so_peek_off)
{
int type;
};
FIXTURE_VARIANT_ADD(so_peek_off, stream)
{
.type = SOCK_STREAM,
};
FIXTURE_VARIANT_ADD(so_peek_off, dgram)
{
.type = SOCK_DGRAM,
};
FIXTURE_VARIANT_ADD(so_peek_off, seqpacket)
{
.type = SOCK_SEQPACKET,
};
FIXTURE_SETUP(so_peek_off)
{
struct timeval timeout = {
.tv_sec = 5,
.tv_usec = 0,
};
int ret;
ret = socketpair(AF_UNIX, variant->type, 0, self->fd);
ASSERT_EQ(0, ret);
ret = setsockopt(self->fd[1], SOL_SOCKET, SO_RCVTIMEO_NEW,
&timeout, sizeof(timeout));
ASSERT_EQ(0, ret);
ret = setsockopt(self->fd[1], SOL_SOCKET, SO_PEEK_OFF,
&(int){0}, sizeof(int));
ASSERT_EQ(0, ret);
}
FIXTURE_TEARDOWN(so_peek_off)
{
close_range(self->fd[0], self->fd[1], 0);
}
#define sendeq(fd, str, flags) \
do { \
int bytes, len = strlen(str); \
\
bytes = send(fd, str, len, flags); \
ASSERT_EQ(len, bytes); \
} while (0)
#define recveq(fd, str, buflen, flags) \
do { \
char buf[(buflen) + 1] = {}; \
int bytes; \
\
bytes = recv(fd, buf, buflen, flags); \
ASSERT_NE(-1, bytes); \
ASSERT_STREQ(str, buf); \
} while (0)
#define peekoffeq(fd, expected) \
do { \
socklen_t optlen = sizeof(int); \
int off = -1; \
int ret; \
\
ret = getsockopt(fd, SOL_SOCKET, SO_PEEK_OFF, \
&off, &optlen); \
ASSERT_EQ(0, ret); \
ASSERT_EQ((socklen_t)sizeof(off), optlen); \
ASSERT_EQ(expected, off); \
} while (0)
#define async \
for (pid_t pid = (pid = fork(), \
pid < 0 ? \
__TH_LOG("Failed to start async {}"), \
_metadata->exit_code = KSFT_FAIL, \
__bail(1, _metadata), \
0xdead : \
pid); \
!pid; exit(0))
TEST_F(so_peek_off, single_chunk)
{
sendeq(self->fd[0], "aaaabbbb", 0);
recveq(self->fd[1], "aaaa", 4, MSG_PEEK);
peekoffeq(self->fd[1], 4);
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
peekoffeq(self->fd[1], 8);
recveq(self->fd[1], "aaaabbbb", 8, 0);
peekoffeq(self->fd[1], 0);
}
TEST_F(so_peek_off, two_chunks)
{
sendeq(self->fd[0], "aaaa", 0);
sendeq(self->fd[0], "bbbb", 0);
recveq(self->fd[1], "aaaa", 4, MSG_PEEK);
peekoffeq(self->fd[1], 4);
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
peekoffeq(self->fd[1], 8);
recveq(self->fd[1], "aaaa", 4, 0);
recveq(self->fd[1], "bbbb", 4, 0);
peekoffeq(self->fd[1], 0);
}
TEST_F(so_peek_off, two_chunks_blocking)
{
async {
usleep(1000);
sendeq(self->fd[0], "aaaa", 0);
}
recveq(self->fd[1], "aaaa", 4, MSG_PEEK);
peekoffeq(self->fd[1], 4);
async {
usleep(1000);
sendeq(self->fd[0], "bbbb", 0);
}
/* goto again; -> goto redo; in unix_stream_read_generic(). */
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
peekoffeq(self->fd[1], 8);
recveq(self->fd[1], "aaaa", 4, 0);
recveq(self->fd[1], "bbbb", 4, 0);
peekoffeq(self->fd[1], 0);
}
TEST_F(so_peek_off, two_chunks_overlap)
{
sendeq(self->fd[0], "aaaa", 0);
recveq(self->fd[1], "aa", 2, MSG_PEEK);
peekoffeq(self->fd[1], 2);
sendeq(self->fd[0], "bbbb", 0);
if (variant->type == SOCK_STREAM) {
/* SOCK_STREAM tries to fill the buffer. */
recveq(self->fd[1], "aabb", 4, MSG_PEEK);
peekoffeq(self->fd[1], 6);
recveq(self->fd[1], "bb", 100, MSG_PEEK);
peekoffeq(self->fd[1], 8);
} else {
/* SOCK_DGRAM and SOCK_SEQPACKET returns at the skb boundary. */
recveq(self->fd[1], "aa", 100, MSG_PEEK);
peekoffeq(self->fd[1], 4);
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
peekoffeq(self->fd[1], 8);
}
recveq(self->fd[1], "aaaa", 4, 0);
recveq(self->fd[1], "bbbb", 4, 0);
peekoffeq(self->fd[1], 0);
}
TEST_F(so_peek_off, two_chunks_overlap_blocking)
{
async {
usleep(1000);
sendeq(self->fd[0], "aaaa", 0);
}
recveq(self->fd[1], "aa", 2, MSG_PEEK);
peekoffeq(self->fd[1], 2);
async {
usleep(1000);
sendeq(self->fd[0], "bbbb", 0);
}
/* Even SOCK_STREAM does not wait if at least one byte is read. */
recveq(self->fd[1], "aa", 100, MSG_PEEK);
peekoffeq(self->fd[1], 4);
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
peekoffeq(self->fd[1], 8);
recveq(self->fd[1], "aaaa", 4, 0);
recveq(self->fd[1], "bbbb", 4, 0);
peekoffeq(self->fd[1], 0);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sched.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "kselftest_harness.h"
FIXTURE(unix_connect)
{
int server, client;
int family;
};
FIXTURE_VARIANT(unix_connect)
{
int type;
char sun_path[8];
int len;
int flags;
int err;
};
FIXTURE_VARIANT_ADD(unix_connect, stream_pathname)
{
.type = SOCK_STREAM,
.sun_path = "test",
.len = 4 + 1,
.flags = 0,
.err = 0,
};
FIXTURE_VARIANT_ADD(unix_connect, stream_abstract)
{
.type = SOCK_STREAM,
.sun_path = "\0test",
.len = 5,
.flags = 0,
.err = 0,
};
FIXTURE_VARIANT_ADD(unix_connect, stream_pathname_netns)
{
.type = SOCK_STREAM,
.sun_path = "test",
.len = 4 + 1,
.flags = CLONE_NEWNET,
.err = 0,
};
FIXTURE_VARIANT_ADD(unix_connect, stream_abstract_netns)
{
.type = SOCK_STREAM,
.sun_path = "\0test",
.len = 5,
.flags = CLONE_NEWNET,
.err = ECONNREFUSED,
};
FIXTURE_VARIANT_ADD(unix_connect, dgram_pathname)
{
.type = SOCK_DGRAM,
.sun_path = "test",
.len = 4 + 1,
.flags = 0,
.err = 0,
};
FIXTURE_VARIANT_ADD(unix_connect, dgram_abstract)
{
.type = SOCK_DGRAM,
.sun_path = "\0test",
.len = 5,
.flags = 0,
.err = 0,
};
FIXTURE_VARIANT_ADD(unix_connect, dgram_pathname_netns)
{
.type = SOCK_DGRAM,
.sun_path = "test",
.len = 4 + 1,
.flags = CLONE_NEWNET,
.err = 0,
};
FIXTURE_VARIANT_ADD(unix_connect, dgram_abstract_netns)
{
.type = SOCK_DGRAM,
.sun_path = "\0test",
.len = 5,
.flags = CLONE_NEWNET,
.err = ECONNREFUSED,
};
FIXTURE_SETUP(unix_connect)
{
self->family = AF_UNIX;
}
FIXTURE_TEARDOWN(unix_connect)
{
close(self->server);
close(self->client);
if (variant->sun_path[0])
remove("test");
}
TEST_F(unix_connect, test)
{
socklen_t addrlen;
struct sockaddr_un addr = {
.sun_family = self->family,
};
int err;
self->server = socket(self->family, variant->type, 0);
ASSERT_NE(-1, self->server);
addrlen = offsetof(struct sockaddr_un, sun_path) + variant->len;
memcpy(&addr.sun_path, variant->sun_path, variant->len);
err = bind(self->server, (struct sockaddr *)&addr, addrlen);
ASSERT_EQ(0, err);
if (variant->type == SOCK_STREAM) {
err = listen(self->server, 32);
ASSERT_EQ(0, err);
}
err = unshare(variant->flags);
ASSERT_EQ(0, err);
self->client = socket(self->family, variant->type, 0);
ASSERT_LT(0, self->client);
err = connect(self->client, (struct sockaddr *)&addr, addrlen);
ASSERT_EQ(variant->err, err == -1 ? errno : 0);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Selftest for AF_UNIX socket close and ECONNRESET behaviour.
*
* This test verifies:
* 1. SOCK_STREAM returns EOF when the peer closes normally.
* 2. SOCK_STREAM returns ECONNRESET if peer closes with unread data.
* 3. SOCK_SEQPACKET returns EOF when the peer closes normally.
* 4. SOCK_SEQPACKET returns ECONNRESET if the peer closes with unread data.
* 5. SOCK_DGRAM does not return ECONNRESET when the peer closes.
*
* These tests document the intended Linux behaviour.
*
*/
#define _GNU_SOURCE
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "../../kselftest_harness.h"
#define SOCK_PATH "/tmp/af_unix_connreset.sock"
static void remove_socket_file(void)
{
unlink(SOCK_PATH);
}
FIXTURE(unix_sock)
{
int server;
int client;
int child;
};
FIXTURE_VARIANT(unix_sock)
{
int socket_type;
const char *name;
};
FIXTURE_VARIANT_ADD(unix_sock, stream) {
.socket_type = SOCK_STREAM,
.name = "SOCK_STREAM",
};
FIXTURE_VARIANT_ADD(unix_sock, dgram) {
.socket_type = SOCK_DGRAM,
.name = "SOCK_DGRAM",
};
FIXTURE_VARIANT_ADD(unix_sock, seqpacket) {
.socket_type = SOCK_SEQPACKET,
.name = "SOCK_SEQPACKET",
};
FIXTURE_SETUP(unix_sock)
{
struct sockaddr_un addr = {};
int err;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, SOCK_PATH);
remove_socket_file();
self->server = socket(AF_UNIX, variant->socket_type, 0);
ASSERT_LT(-1, self->server);
err = bind(self->server, (struct sockaddr *)&addr, sizeof(addr));
ASSERT_EQ(0, err);
if (variant->socket_type == SOCK_STREAM ||
variant->socket_type == SOCK_SEQPACKET) {
err = listen(self->server, 1);
ASSERT_EQ(0, err);
}
self->client = socket(AF_UNIX, variant->socket_type | SOCK_NONBLOCK, 0);
ASSERT_LT(-1, self->client);
err = connect(self->client, (struct sockaddr *)&addr, sizeof(addr));
ASSERT_EQ(0, err);
}
FIXTURE_TEARDOWN(unix_sock)
{
if (variant->socket_type == SOCK_STREAM ||
variant->socket_type == SOCK_SEQPACKET)
close(self->child);
close(self->client);
close(self->server);
remove_socket_file();
}
/* Test 1: peer closes normally */
TEST_F(unix_sock, eof)
{
char buf[16] = {};
ssize_t n;
if (variant->socket_type == SOCK_STREAM ||
variant->socket_type == SOCK_SEQPACKET) {
self->child = accept(self->server, NULL, NULL);
ASSERT_LT(-1, self->child);
close(self->child);
} else {
close(self->server);
}
n = recv(self->client, buf, sizeof(buf), 0);
if (variant->socket_type == SOCK_STREAM ||
variant->socket_type == SOCK_SEQPACKET) {
ASSERT_EQ(0, n);
} else {
ASSERT_EQ(-1, n);
ASSERT_EQ(EAGAIN, errno);
}
}
/* Test 2: peer closes with unread data */
TEST_F(unix_sock, reset_unread_behavior)
{
char buf[16] = {};
ssize_t n;
/* Send data that will remain unread */
send(self->client, "hello", 5, 0);
if (variant->socket_type == SOCK_DGRAM) {
/* No real connection, just close the server */
close(self->server);
} else {
self->child = accept(self->server, NULL, NULL);
ASSERT_LT(-1, self->child);
/* Peer closes before client reads */
close(self->child);
}
n = recv(self->client, buf, sizeof(buf), 0);
ASSERT_EQ(-1, n);
if (variant->socket_type == SOCK_STREAM ||
variant->socket_type == SOCK_SEQPACKET) {
ASSERT_EQ(ECONNRESET, errno);
} else {
ASSERT_EQ(EAGAIN, errno);
}
}
/* Test 3: closing unaccepted (embryo) server socket should reset client. */
TEST_F(unix_sock, reset_closed_embryo)
{
char buf[16] = {};
ssize_t n;
if (variant->socket_type == SOCK_DGRAM) {
snprintf(_metadata->results->reason,
sizeof(_metadata->results->reason),
"Test only applies to SOCK_STREAM and SOCK_SEQPACKET");
exit(KSFT_XFAIL);
}
/* Close server without accept()ing */
close(self->server);
n = recv(self->client, buf, sizeof(buf), 0);
ASSERT_EQ(-1, n);
ASSERT_EQ(ECONNRESET, errno);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Tests for the state checks in AF_UNIX listen().
*
* The central case is a regression test: listen() on a bound socket that
* is already connected (i.e. not in TCP_CLOSE or TCP_LISTEN state) must
* fail with EINVAL. A prior change accidentally let it return success
* without doing anything, because a helper called in between reset the
* error code to 0. The neighbouring checks (unbound, already listening)
* are tested too so they cannot silently regress the same way.
*
* Every case runs for both listenable socket types (SOCK_STREAM and
* SOCK_SEQPACKET) and both pathname and abstract addresses.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "kselftest_harness.h"
#define SK_NAME "unix_listen_sk"
#define SRV_NAME "unix_listen_srv"
FIXTURE(unix_listen)
{
int sk; /* socket under test */
int server; /* a listening peer, when a test needs one */
struct sockaddr_un addr, srv_addr;
socklen_t addrlen, srv_addrlen;
};
FIXTURE_VARIANT(unix_listen)
{
int type;
int abstract;
};
FIXTURE_VARIANT_ADD(unix_listen, stream_pathname)
{
.type = SOCK_STREAM,
.abstract = 0,
};
FIXTURE_VARIANT_ADD(unix_listen, stream_abstract)
{
.type = SOCK_STREAM,
.abstract = 1,
};
FIXTURE_VARIANT_ADD(unix_listen, seqpacket_pathname)
{
.type = SOCK_SEQPACKET,
.abstract = 0,
};
FIXTURE_VARIANT_ADD(unix_listen, seqpacket_abstract)
{
.type = SOCK_SEQPACKET,
.abstract = 1,
};
/* Fill @addr with a pathname or abstract address named @name. */
static socklen_t unix_set_addr(struct sockaddr_un *addr, const char *name,
int abstract)
{
size_t len = strlen(name);
memset(addr, 0, sizeof(*addr));
addr->sun_family = AF_UNIX;
/* An abstract address leads with a NUL and has no filesystem entry. */
memcpy(addr->sun_path + (abstract ? 1 : 0), name, len);
return offsetof(struct sockaddr_un, sun_path) + len + 1;
}
FIXTURE_SETUP(unix_listen)
{
self->sk = -1;
self->server = -1;
self->addrlen = unix_set_addr(&self->addr, SK_NAME, variant->abstract);
self->srv_addrlen = unix_set_addr(&self->srv_addr, SRV_NAME,
variant->abstract);
}
FIXTURE_TEARDOWN(unix_listen)
{
if (self->sk >= 0)
close(self->sk);
if (self->server >= 0)
close(self->server);
/* Pathname sockets leave a filesystem entry behind; abstract ones do not. */
if (!variant->abstract) {
remove(SK_NAME);
remove(SRV_NAME);
}
}
/* A bound socket in TCP_CLOSE is the normal, allowed case. */
TEST_F(unix_listen, bound_is_ok)
{
int err;
self->sk = socket(AF_UNIX, variant->type, 0);
ASSERT_LE(0, self->sk);
err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
ASSERT_EQ(0, err);
err = listen(self->sk, 8);
EXPECT_EQ(0, err);
}
/* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */
TEST_F(unix_listen, relisten_is_ok)
{
int err;
self->sk = socket(AF_UNIX, variant->type, 0);
ASSERT_LE(0, self->sk);
err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
ASSERT_EQ(0, err);
err = listen(self->sk, 8);
ASSERT_EQ(0, err);
err = listen(self->sk, 16);
EXPECT_EQ(0, err);
}
/* listen() on an unbound socket fails: there is nothing to listen on. */
TEST_F(unix_listen, unbound_is_einval)
{
int err;
self->sk = socket(AF_UNIX, variant->type, 0);
ASSERT_LE(0, self->sk);
err = listen(self->sk, 8);
EXPECT_EQ(-1, err);
EXPECT_EQ(EINVAL, errno);
}
/*
* The regression: a bound socket that has already been connected is not in
* TCP_CLOSE or TCP_LISTEN, so listen() must reject it with EINVAL rather
* than quietly succeeding.
*/
TEST_F(unix_listen, connected_is_einval)
{
int err;
self->server = socket(AF_UNIX, variant->type, 0);
ASSERT_LE(0, self->server);
err = bind(self->server, (struct sockaddr *)&self->srv_addr,
self->srv_addrlen);
ASSERT_EQ(0, err);
err = listen(self->server, 8);
ASSERT_EQ(0, err);
self->sk = socket(AF_UNIX, variant->type, 0);
ASSERT_LE(0, self->sk);
/* Bind first so the unbound check does not mask the state check. */
err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
ASSERT_EQ(0, err);
err = connect(self->sk, (struct sockaddr *)&self->srv_addr,
self->srv_addrlen);
ASSERT_EQ(0, err);
err = listen(self->sk, 8);
EXPECT_EQ(-1, err);
EXPECT_EQ(EINVAL, errno);
}
TEST_HARNESS_MAIN