Path: blob/master/tools/testing/selftests/filesystems/xattr/xattr_socket_test.c
170987 views
// SPDX-License-Identifier: GPL-2.01// Copyright (c) 2026 Christian Brauner <[email protected]>2/*3* Test extended attributes on path-based Unix domain sockets.4*5* Path-based Unix domain sockets are bound to a filesystem path and their6* inodes live on the underlying filesystem (e.g. tmpfs). These tests verify7* that user.* and trusted.* xattr operations work correctly on them using8* path-based syscalls (setxattr, getxattr, etc.).9*10* Covers SOCK_STREAM, SOCK_DGRAM, and SOCK_SEQPACKET socket types.11*/1213#define _GNU_SOURCE14#include <errno.h>15#include <limits.h>16#include <stdio.h>17#include <stdlib.h>18#include <string.h>19#include <sys/socket.h>20#include <sys/stat.h>21#include <sys/types.h>22#include <sys/un.h>23#include <sys/xattr.h>24#include <unistd.h>2526#include "../../kselftest_harness.h"2728#define TEST_XATTR_NAME "user.testattr"29#define TEST_XATTR_VALUE "testvalue"30#define TEST_XATTR_VALUE2 "newvalue"3132/*33* Fixture for path-based Unix domain socket tests.34* Creates a SOCK_STREAM socket bound to a path in /tmp (typically tmpfs).35*/36FIXTURE(xattr_socket)37{38char socket_path[PATH_MAX];39int sockfd;40};4142FIXTURE_VARIANT(xattr_socket)43{44int sock_type;45const char *name;46};4748FIXTURE_VARIANT_ADD(xattr_socket, stream) {49.sock_type = SOCK_STREAM,50.name = "stream",51};5253FIXTURE_VARIANT_ADD(xattr_socket, dgram) {54.sock_type = SOCK_DGRAM,55.name = "dgram",56};5758FIXTURE_VARIANT_ADD(xattr_socket, seqpacket) {59.sock_type = SOCK_SEQPACKET,60.name = "seqpacket",61};6263FIXTURE_SETUP(xattr_socket)64{65struct sockaddr_un addr;66int ret;6768self->sockfd = -1;6970snprintf(self->socket_path, sizeof(self->socket_path),71"/tmp/xattr_socket_test_%s.%d", variant->name, getpid());72unlink(self->socket_path);7374self->sockfd = socket(AF_UNIX, variant->sock_type, 0);75ASSERT_GE(self->sockfd, 0) {76TH_LOG("Failed to create socket: %s", strerror(errno));77}7879memset(&addr, 0, sizeof(addr));80addr.sun_family = AF_UNIX;81strncpy(addr.sun_path, self->socket_path, sizeof(addr.sun_path) - 1);8283ret = bind(self->sockfd, (struct sockaddr *)&addr, sizeof(addr));84ASSERT_EQ(ret, 0) {85TH_LOG("Failed to bind socket to %s: %s",86self->socket_path, strerror(errno));87}88}8990FIXTURE_TEARDOWN(xattr_socket)91{92if (self->sockfd >= 0)93close(self->sockfd);94unlink(self->socket_path);95}9697TEST_F(xattr_socket, set_user_xattr)98{99int ret;100101ret = setxattr(self->socket_path, TEST_XATTR_NAME,102TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);103ASSERT_EQ(ret, 0) {104TH_LOG("setxattr failed: %s (errno=%d)", strerror(errno), errno);105}106}107108TEST_F(xattr_socket, get_user_xattr)109{110char buf[256];111ssize_t ret;112113ret = setxattr(self->socket_path, TEST_XATTR_NAME,114TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);115ASSERT_EQ(ret, 0) {116TH_LOG("setxattr failed: %s", strerror(errno));117}118119memset(buf, 0, sizeof(buf));120ret = getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf));121ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)) {122TH_LOG("getxattr returned %zd, expected %zu: %s",123ret, strlen(TEST_XATTR_VALUE), strerror(errno));124}125ASSERT_STREQ(buf, TEST_XATTR_VALUE);126}127128TEST_F(xattr_socket, list_user_xattr)129{130char list[1024];131ssize_t ret;132bool found = false;133char *ptr;134135ret = setxattr(self->socket_path, TEST_XATTR_NAME,136TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);137ASSERT_EQ(ret, 0) {138TH_LOG("setxattr failed: %s", strerror(errno));139}140141memset(list, 0, sizeof(list));142ret = listxattr(self->socket_path, list, sizeof(list));143ASSERT_GT(ret, 0) {144TH_LOG("listxattr failed: %s", strerror(errno));145}146147for (ptr = list; ptr < list + ret; ptr += strlen(ptr) + 1) {148if (strcmp(ptr, TEST_XATTR_NAME) == 0) {149found = true;150break;151}152}153ASSERT_TRUE(found) {154TH_LOG("xattr %s not found in list", TEST_XATTR_NAME);155}156}157158TEST_F(xattr_socket, remove_user_xattr)159{160char buf[256];161ssize_t ret;162163ret = setxattr(self->socket_path, TEST_XATTR_NAME,164TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);165ASSERT_EQ(ret, 0) {166TH_LOG("setxattr failed: %s", strerror(errno));167}168169ret = removexattr(self->socket_path, TEST_XATTR_NAME);170ASSERT_EQ(ret, 0) {171TH_LOG("removexattr failed: %s", strerror(errno));172}173174ret = getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf));175ASSERT_EQ(ret, -1);176ASSERT_EQ(errno, ENODATA) {177TH_LOG("Expected ENODATA, got %s", strerror(errno));178}179}180181/*182* Test that xattrs persist across socket close and reopen.183* The xattr is on the filesystem inode, not the socket fd.184*/185TEST_F(xattr_socket, xattr_persistence)186{187char buf[256];188ssize_t ret;189190ret = setxattr(self->socket_path, TEST_XATTR_NAME,191TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);192ASSERT_EQ(ret, 0) {193TH_LOG("setxattr failed: %s", strerror(errno));194}195196close(self->sockfd);197self->sockfd = -1;198199memset(buf, 0, sizeof(buf));200ret = getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf));201ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)) {202TH_LOG("getxattr after close failed: %s", strerror(errno));203}204ASSERT_STREQ(buf, TEST_XATTR_VALUE);205}206207TEST_F(xattr_socket, update_user_xattr)208{209char buf[256];210ssize_t ret;211212ret = setxattr(self->socket_path, TEST_XATTR_NAME,213TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);214ASSERT_EQ(ret, 0);215216ret = setxattr(self->socket_path, TEST_XATTR_NAME,217TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), 0);218ASSERT_EQ(ret, 0);219220memset(buf, 0, sizeof(buf));221ret = getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf));222ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE2));223ASSERT_STREQ(buf, TEST_XATTR_VALUE2);224}225226TEST_F(xattr_socket, xattr_create_flag)227{228int ret;229230ret = setxattr(self->socket_path, TEST_XATTR_NAME,231TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);232ASSERT_EQ(ret, 0);233234ret = setxattr(self->socket_path, TEST_XATTR_NAME,235TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), XATTR_CREATE);236ASSERT_EQ(ret, -1);237ASSERT_EQ(errno, EEXIST);238}239240TEST_F(xattr_socket, xattr_replace_flag)241{242int ret;243244ret = setxattr(self->socket_path, TEST_XATTR_NAME,245TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), XATTR_REPLACE);246ASSERT_EQ(ret, -1);247ASSERT_EQ(errno, ENODATA);248}249250TEST_F(xattr_socket, multiple_xattrs)251{252char buf[256];253ssize_t ret;254int i;255char name[64], value[64];256const int num_xattrs = 5;257258for (i = 0; i < num_xattrs; i++) {259snprintf(name, sizeof(name), "user.test%d", i);260snprintf(value, sizeof(value), "value%d", i);261ret = setxattr(self->socket_path, name, value, strlen(value), 0);262ASSERT_EQ(ret, 0) {263TH_LOG("setxattr %s failed: %s", name, strerror(errno));264}265}266267for (i = 0; i < num_xattrs; i++) {268snprintf(name, sizeof(name), "user.test%d", i);269snprintf(value, sizeof(value), "value%d", i);270memset(buf, 0, sizeof(buf));271ret = getxattr(self->socket_path, name, buf, sizeof(buf));272ASSERT_EQ(ret, (ssize_t)strlen(value));273ASSERT_STREQ(buf, value);274}275}276277TEST_F(xattr_socket, xattr_empty_value)278{279char buf[256];280ssize_t ret;281282ret = setxattr(self->socket_path, TEST_XATTR_NAME, "", 0, 0);283ASSERT_EQ(ret, 0);284285ret = getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf));286ASSERT_EQ(ret, 0);287}288289TEST_F(xattr_socket, xattr_get_size)290{291ssize_t ret;292293ret = setxattr(self->socket_path, TEST_XATTR_NAME,294TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);295ASSERT_EQ(ret, 0);296297ret = getxattr(self->socket_path, TEST_XATTR_NAME, NULL, 0);298ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE));299}300301TEST_F(xattr_socket, xattr_buffer_too_small)302{303char buf[2];304ssize_t ret;305306ret = setxattr(self->socket_path, TEST_XATTR_NAME,307TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);308ASSERT_EQ(ret, 0);309310ret = getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf));311ASSERT_EQ(ret, -1);312ASSERT_EQ(errno, ERANGE);313}314315TEST_F(xattr_socket, xattr_nonexistent)316{317char buf[256];318ssize_t ret;319320ret = getxattr(self->socket_path, "user.nonexistent", buf, sizeof(buf));321ASSERT_EQ(ret, -1);322ASSERT_EQ(errno, ENODATA);323}324325TEST_F(xattr_socket, remove_nonexistent_xattr)326{327int ret;328329ret = removexattr(self->socket_path, "user.nonexistent");330ASSERT_EQ(ret, -1);331ASSERT_EQ(errno, ENODATA);332}333334TEST_F(xattr_socket, large_xattr_value)335{336char large_value[4096];337char read_buf[4096];338ssize_t ret;339340memset(large_value, 'A', sizeof(large_value));341342ret = setxattr(self->socket_path, TEST_XATTR_NAME,343large_value, sizeof(large_value), 0);344ASSERT_EQ(ret, 0) {345TH_LOG("setxattr with large value failed: %s", strerror(errno));346}347348memset(read_buf, 0, sizeof(read_buf));349ret = getxattr(self->socket_path, TEST_XATTR_NAME,350read_buf, sizeof(read_buf));351ASSERT_EQ(ret, (ssize_t)sizeof(large_value));352ASSERT_EQ(memcmp(large_value, read_buf, sizeof(large_value)), 0);353}354355/*356* Test lsetxattr/lgetxattr (don't follow symlinks).357* Socket files aren't symlinks, so this should work the same.358*/359TEST_F(xattr_socket, lsetxattr_lgetxattr)360{361char buf[256];362ssize_t ret;363364ret = lsetxattr(self->socket_path, TEST_XATTR_NAME,365TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);366ASSERT_EQ(ret, 0) {367TH_LOG("lsetxattr failed: %s", strerror(errno));368}369370memset(buf, 0, sizeof(buf));371ret = lgetxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf));372ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE));373ASSERT_STREQ(buf, TEST_XATTR_VALUE);374}375376/*377* Fixture for trusted.* xattr tests.378* These require CAP_SYS_ADMIN.379*/380FIXTURE(xattr_socket_trusted)381{382char socket_path[PATH_MAX];383int sockfd;384};385386FIXTURE_VARIANT(xattr_socket_trusted)387{388int sock_type;389const char *name;390};391392FIXTURE_VARIANT_ADD(xattr_socket_trusted, stream) {393.sock_type = SOCK_STREAM,394.name = "stream",395};396397FIXTURE_VARIANT_ADD(xattr_socket_trusted, dgram) {398.sock_type = SOCK_DGRAM,399.name = "dgram",400};401402FIXTURE_VARIANT_ADD(xattr_socket_trusted, seqpacket) {403.sock_type = SOCK_SEQPACKET,404.name = "seqpacket",405};406407FIXTURE_SETUP(xattr_socket_trusted)408{409struct sockaddr_un addr;410int ret;411412self->sockfd = -1;413414snprintf(self->socket_path, sizeof(self->socket_path),415"/tmp/xattr_socket_trusted_%s.%d", variant->name, getpid());416unlink(self->socket_path);417418self->sockfd = socket(AF_UNIX, variant->sock_type, 0);419ASSERT_GE(self->sockfd, 0);420421memset(&addr, 0, sizeof(addr));422addr.sun_family = AF_UNIX;423strncpy(addr.sun_path, self->socket_path, sizeof(addr.sun_path) - 1);424425ret = bind(self->sockfd, (struct sockaddr *)&addr, sizeof(addr));426ASSERT_EQ(ret, 0);427}428429FIXTURE_TEARDOWN(xattr_socket_trusted)430{431if (self->sockfd >= 0)432close(self->sockfd);433unlink(self->socket_path);434}435436TEST_F(xattr_socket_trusted, set_trusted_xattr)437{438char buf[256];439ssize_t len;440int ret;441442ret = setxattr(self->socket_path, "trusted.testattr",443TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0);444if (ret == -1 && errno == EPERM)445SKIP(return, "Need CAP_SYS_ADMIN for trusted.* xattrs");446ASSERT_EQ(ret, 0) {447TH_LOG("setxattr trusted.testattr failed: %s", strerror(errno));448}449450memset(buf, 0, sizeof(buf));451len = getxattr(self->socket_path, "trusted.testattr",452buf, sizeof(buf));453ASSERT_EQ(len, (ssize_t)strlen(TEST_XATTR_VALUE));454ASSERT_STREQ(buf, TEST_XATTR_VALUE);455}456457TEST_F(xattr_socket_trusted, get_trusted_xattr_unprivileged)458{459char buf[256];460ssize_t ret;461462ret = getxattr(self->socket_path, "trusted.testattr", buf, sizeof(buf));463ASSERT_EQ(ret, -1);464ASSERT_TRUE(errno == ENODATA || errno == EPERM) {465TH_LOG("Expected ENODATA or EPERM, got %s", strerror(errno));466}467}468469TEST_HARNESS_MAIN470471472