Test user.* xattr operations on sockets from different address families: AF_INET, AF_INET6, AF_NETLINK, and AF_PACKET. All socket types use sockfs for their inodes, so user.* xattrs should work regardless of address family. Each fixture creates a socket (no bind needed) and verifies the full fsetxattr/fgetxattr/flistxattr/fremovexattr cycle. AF_INET6 skips if not supported; AF_PACKET skips if CAP_NET_RAW is unavailable. Also tests abstract namespace AF_UNIX sockets, which live in sockfs (not on a filesystem) and should support user.* xattrs. Signed-off-by: Christian Brauner --- .../testing/selftests/filesystems/xattr/.gitignore | 1 + tools/testing/selftests/filesystems/xattr/Makefile | 2 +- .../filesystems/xattr/xattr_socket_types_test.c | 177 +++++++++++++++++++++ 3 files changed, 179 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/filesystems/xattr/.gitignore b/tools/testing/selftests/filesystems/xattr/.gitignore index 00a59c89efab..092d14094c0f 100644 --- a/tools/testing/selftests/filesystems/xattr/.gitignore +++ b/tools/testing/selftests/filesystems/xattr/.gitignore @@ -1,2 +1,3 @@ xattr_socket_test xattr_sockfs_test +xattr_socket_types_test diff --git a/tools/testing/selftests/filesystems/xattr/Makefile b/tools/testing/selftests/filesystems/xattr/Makefile index 2cd722dba47b..95364ffb10e9 100644 --- a/tools/testing/selftests/filesystems/xattr/Makefile +++ b/tools/testing/selftests/filesystems/xattr/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS += $(KHDR_INCLUDES) -TEST_GEN_PROGS := xattr_socket_test xattr_sockfs_test +TEST_GEN_PROGS := xattr_socket_test xattr_sockfs_test xattr_socket_types_test include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/xattr/xattr_socket_types_test.c b/tools/testing/selftests/filesystems/xattr/xattr_socket_types_test.c new file mode 100644 index 000000000000..bfabe91b2ed1 --- /dev/null +++ b/tools/testing/selftests/filesystems/xattr/xattr_socket_types_test.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2026 Christian Brauner +/* + * Test user.* xattrs on various socket families. + * + * All socket types use sockfs for their inodes, so user.* xattrs should + * work on any socket regardless of address family. This tests AF_INET, + * AF_INET6, AF_NETLINK, AF_PACKET, and abstract namespace AF_UNIX sockets. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define TEST_XATTR_NAME "user.testattr" +#define TEST_XATTR_VALUE "testvalue" + +FIXTURE(xattr_socket_types) +{ + int sockfd; +}; + +FIXTURE_VARIANT(xattr_socket_types) +{ + int family; + int type; + int protocol; +}; + +FIXTURE_VARIANT_ADD(xattr_socket_types, inet) { + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = 0, +}; + +FIXTURE_VARIANT_ADD(xattr_socket_types, inet6) { + .family = AF_INET6, + .type = SOCK_STREAM, + .protocol = 0, +}; + +FIXTURE_VARIANT_ADD(xattr_socket_types, netlink) { + .family = AF_NETLINK, + .type = SOCK_RAW, + .protocol = NETLINK_USERSOCK, +}; + +FIXTURE_VARIANT_ADD(xattr_socket_types, packet) { + .family = AF_PACKET, + .type = SOCK_DGRAM, + .protocol = 0, +}; + +FIXTURE_SETUP(xattr_socket_types) +{ + self->sockfd = socket(variant->family, variant->type, + variant->protocol); + if (self->sockfd < 0 && + (errno == EAFNOSUPPORT || errno == EPERM || errno == EACCES)) + SKIP(return, "socket(%d, %d, %d) not available: %s", + variant->family, variant->type, variant->protocol, + strerror(errno)); + ASSERT_GE(self->sockfd, 0) { + TH_LOG("Failed to create socket(%d, %d, %d): %s", + variant->family, variant->type, variant->protocol, + strerror(errno)); + } +} + +FIXTURE_TEARDOWN(xattr_socket_types) +{ + if (self->sockfd >= 0) + close(self->sockfd); +} + +TEST_F(xattr_socket_types, set_get_list_remove) +{ + char buf[256], list[4096], *ptr; + ssize_t ret; + bool found; + + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("fsetxattr failed: %s", strerror(errno)); + } + + memset(buf, 0, sizeof(buf)); + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE); + + memset(list, 0, sizeof(list)); + ret = flistxattr(self->sockfd, list, sizeof(list)); + ASSERT_GT(ret, 0); + found = false; + for (ptr = list; ptr < list + ret; ptr += strlen(ptr) + 1) { + if (strcmp(ptr, TEST_XATTR_NAME) == 0) + found = true; + } + ASSERT_TRUE(found); + + ret = fremovexattr(self->sockfd, TEST_XATTR_NAME); + ASSERT_EQ(ret, 0); + + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); +} + +/* + * Test abstract namespace AF_UNIX socket. + * Abstract sockets don't have a filesystem path; their inodes live in + * sockfs so user.* xattrs should work via fsetxattr/fgetxattr. + */ +FIXTURE(xattr_abstract) +{ + int sockfd; +}; + +FIXTURE_SETUP(xattr_abstract) +{ + struct sockaddr_un addr; + char name[64]; + int ret, len; + + self->sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_GE(self->sockfd, 0); + + len = snprintf(name, sizeof(name), "xattr_test_abstract_%d", getpid()); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_path[0] = '\0'; + memcpy(&addr.sun_path[1], name, len); + + ret = bind(self->sockfd, (struct sockaddr *)&addr, + offsetof(struct sockaddr_un, sun_path) + 1 + len); + ASSERT_EQ(ret, 0); +} + +FIXTURE_TEARDOWN(xattr_abstract) +{ + if (self->sockfd >= 0) + close(self->sockfd); +} + +TEST_F(xattr_abstract, set_get) +{ + char buf[256]; + ssize_t ret; + + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("fsetxattr on abstract socket failed: %s", + strerror(errno)); + } + + memset(buf, 0, sizeof(buf)); + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE); +} + +TEST_HARNESS_MAIN -- 2.47.3