|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +// Copyright (c) 2026 Christian Brauner <brauner@kernel.org> |
| 3 | +/* |
| 4 | + * Test extended attributes on sockfs sockets. |
| 5 | + * |
| 6 | + * Sockets created via socket() have their inodes in sockfs, which supports |
| 7 | + * user.* xattrs with per-inode limits: up to 128 xattrs and 128KB total |
| 8 | + * value size. These tests verify xattr operations via fsetxattr/fgetxattr/ |
| 9 | + * flistxattr/fremovexattr on the socket fd, as well as limit enforcement. |
| 10 | + */ |
| 11 | + |
| 12 | +#define _GNU_SOURCE |
| 13 | +#include <errno.h> |
| 14 | +#include <stdio.h> |
| 15 | +#include <stdlib.h> |
| 16 | +#include <string.h> |
| 17 | +#include <sys/socket.h> |
| 18 | +#include <sys/types.h> |
| 19 | +#include <sys/xattr.h> |
| 20 | +#include <unistd.h> |
| 21 | + |
| 22 | +#include "../../kselftest_harness.h" |
| 23 | + |
| 24 | +#define TEST_XATTR_NAME "user.testattr" |
| 25 | +#define TEST_XATTR_VALUE "testvalue" |
| 26 | +#define TEST_XATTR_VALUE2 "newvalue" |
| 27 | + |
| 28 | +/* Per-inode limits for user.* xattrs on sockfs (from include/linux/xattr.h) */ |
| 29 | +#define SIMPLE_XATTR_MAX_NR 128 |
| 30 | +#define SIMPLE_XATTR_MAX_SIZE (128 << 10) /* 128 KB */ |
| 31 | + |
| 32 | +#ifndef XATTR_SIZE_MAX |
| 33 | +#define XATTR_SIZE_MAX 65536 |
| 34 | +#endif |
| 35 | + |
| 36 | +/* |
| 37 | + * Fixture for sockfs socket xattr tests. |
| 38 | + * Creates an AF_UNIX socket (lives in sockfs, not bound to any path). |
| 39 | + */ |
| 40 | +FIXTURE(xattr_sockfs) |
| 41 | +{ |
| 42 | + int sockfd; |
| 43 | +}; |
| 44 | + |
| 45 | +FIXTURE_SETUP(xattr_sockfs) |
| 46 | +{ |
| 47 | + self->sockfd = socket(AF_UNIX, SOCK_STREAM, 0); |
| 48 | + ASSERT_GE(self->sockfd, 0) { |
| 49 | + TH_LOG("Failed to create socket: %s", strerror(errno)); |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +FIXTURE_TEARDOWN(xattr_sockfs) |
| 54 | +{ |
| 55 | + if (self->sockfd >= 0) |
| 56 | + close(self->sockfd); |
| 57 | +} |
| 58 | + |
| 59 | +TEST_F(xattr_sockfs, set_get_user_xattr) |
| 60 | +{ |
| 61 | + char buf[256]; |
| 62 | + ssize_t ret; |
| 63 | + |
| 64 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 65 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); |
| 66 | + ASSERT_EQ(ret, 0) { |
| 67 | + TH_LOG("fsetxattr failed: %s", strerror(errno)); |
| 68 | + } |
| 69 | + |
| 70 | + memset(buf, 0, sizeof(buf)); |
| 71 | + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); |
| 72 | + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)) { |
| 73 | + TH_LOG("fgetxattr returned %zd: %s", ret, strerror(errno)); |
| 74 | + } |
| 75 | + ASSERT_STREQ(buf, TEST_XATTR_VALUE); |
| 76 | +} |
| 77 | + |
| 78 | +/* |
| 79 | + * Test listing xattrs on a sockfs socket. |
| 80 | + * Should include user.* xattrs and system.sockprotoname. |
| 81 | + */ |
| 82 | +TEST_F(xattr_sockfs, list_user_xattr) |
| 83 | +{ |
| 84 | + char list[4096]; |
| 85 | + ssize_t ret; |
| 86 | + char *ptr; |
| 87 | + bool found_user = false; |
| 88 | + bool found_proto = false; |
| 89 | + |
| 90 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 91 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); |
| 92 | + ASSERT_EQ(ret, 0) { |
| 93 | + TH_LOG("fsetxattr failed: %s", strerror(errno)); |
| 94 | + } |
| 95 | + |
| 96 | + memset(list, 0, sizeof(list)); |
| 97 | + ret = flistxattr(self->sockfd, list, sizeof(list)); |
| 98 | + ASSERT_GT(ret, 0) { |
| 99 | + TH_LOG("flistxattr failed: %s", strerror(errno)); |
| 100 | + } |
| 101 | + |
| 102 | + for (ptr = list; ptr < list + ret; ptr += strlen(ptr) + 1) { |
| 103 | + if (strcmp(ptr, TEST_XATTR_NAME) == 0) |
| 104 | + found_user = true; |
| 105 | + if (strcmp(ptr, "system.sockprotoname") == 0) |
| 106 | + found_proto = true; |
| 107 | + } |
| 108 | + ASSERT_TRUE(found_user) { |
| 109 | + TH_LOG("user xattr not found in list"); |
| 110 | + } |
| 111 | + ASSERT_TRUE(found_proto) { |
| 112 | + TH_LOG("system.sockprotoname not found in list"); |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +TEST_F(xattr_sockfs, remove_user_xattr) |
| 117 | +{ |
| 118 | + char buf[256]; |
| 119 | + ssize_t ret; |
| 120 | + |
| 121 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 122 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); |
| 123 | + ASSERT_EQ(ret, 0); |
| 124 | + |
| 125 | + ret = fremovexattr(self->sockfd, TEST_XATTR_NAME); |
| 126 | + ASSERT_EQ(ret, 0) { |
| 127 | + TH_LOG("fremovexattr failed: %s", strerror(errno)); |
| 128 | + } |
| 129 | + |
| 130 | + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); |
| 131 | + ASSERT_EQ(ret, -1); |
| 132 | + ASSERT_EQ(errno, ENODATA); |
| 133 | +} |
| 134 | + |
| 135 | +TEST_F(xattr_sockfs, update_user_xattr) |
| 136 | +{ |
| 137 | + char buf[256]; |
| 138 | + ssize_t ret; |
| 139 | + |
| 140 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 141 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); |
| 142 | + ASSERT_EQ(ret, 0); |
| 143 | + |
| 144 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 145 | + TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), 0); |
| 146 | + ASSERT_EQ(ret, 0); |
| 147 | + |
| 148 | + memset(buf, 0, sizeof(buf)); |
| 149 | + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); |
| 150 | + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE2)); |
| 151 | + ASSERT_STREQ(buf, TEST_XATTR_VALUE2); |
| 152 | +} |
| 153 | + |
| 154 | +TEST_F(xattr_sockfs, xattr_create_flag) |
| 155 | +{ |
| 156 | + int ret; |
| 157 | + |
| 158 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 159 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); |
| 160 | + ASSERT_EQ(ret, 0); |
| 161 | + |
| 162 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 163 | + TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), |
| 164 | + XATTR_CREATE); |
| 165 | + ASSERT_EQ(ret, -1); |
| 166 | + ASSERT_EQ(errno, EEXIST); |
| 167 | +} |
| 168 | + |
| 169 | +TEST_F(xattr_sockfs, xattr_replace_flag) |
| 170 | +{ |
| 171 | + int ret; |
| 172 | + |
| 173 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 174 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), |
| 175 | + XATTR_REPLACE); |
| 176 | + ASSERT_EQ(ret, -1); |
| 177 | + ASSERT_EQ(errno, ENODATA); |
| 178 | +} |
| 179 | + |
| 180 | +TEST_F(xattr_sockfs, get_nonexistent) |
| 181 | +{ |
| 182 | + char buf[256]; |
| 183 | + ssize_t ret; |
| 184 | + |
| 185 | + ret = fgetxattr(self->sockfd, "user.nonexistent", buf, sizeof(buf)); |
| 186 | + ASSERT_EQ(ret, -1); |
| 187 | + ASSERT_EQ(errno, ENODATA); |
| 188 | +} |
| 189 | + |
| 190 | +TEST_F(xattr_sockfs, empty_value) |
| 191 | +{ |
| 192 | + ssize_t ret; |
| 193 | + |
| 194 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, "", 0, 0); |
| 195 | + ASSERT_EQ(ret, 0); |
| 196 | + |
| 197 | + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, NULL, 0); |
| 198 | + ASSERT_EQ(ret, 0); |
| 199 | +} |
| 200 | + |
| 201 | +TEST_F(xattr_sockfs, get_size) |
| 202 | +{ |
| 203 | + ssize_t ret; |
| 204 | + |
| 205 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 206 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); |
| 207 | + ASSERT_EQ(ret, 0); |
| 208 | + |
| 209 | + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, NULL, 0); |
| 210 | + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); |
| 211 | +} |
| 212 | + |
| 213 | +TEST_F(xattr_sockfs, buffer_too_small) |
| 214 | +{ |
| 215 | + char buf[2]; |
| 216 | + ssize_t ret; |
| 217 | + |
| 218 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 219 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); |
| 220 | + ASSERT_EQ(ret, 0); |
| 221 | + |
| 222 | + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); |
| 223 | + ASSERT_EQ(ret, -1); |
| 224 | + ASSERT_EQ(errno, ERANGE); |
| 225 | +} |
| 226 | + |
| 227 | +/* |
| 228 | + * Test maximum number of user.* xattrs per socket. |
| 229 | + * The kernel enforces SIMPLE_XATTR_MAX_NR (128), so the 129th should |
| 230 | + * fail with ENOSPC. |
| 231 | + */ |
| 232 | +TEST_F(xattr_sockfs, max_nr_xattrs) |
| 233 | +{ |
| 234 | + char name[32]; |
| 235 | + int i, ret; |
| 236 | + |
| 237 | + for (i = 0; i < SIMPLE_XATTR_MAX_NR; i++) { |
| 238 | + snprintf(name, sizeof(name), "user.test%03d", i); |
| 239 | + ret = fsetxattr(self->sockfd, name, "v", 1, 0); |
| 240 | + ASSERT_EQ(ret, 0) { |
| 241 | + TH_LOG("fsetxattr %s failed at i=%d: %s", |
| 242 | + name, i, strerror(errno)); |
| 243 | + } |
| 244 | + } |
| 245 | + |
| 246 | + ret = fsetxattr(self->sockfd, "user.overflow", "v", 1, 0); |
| 247 | + ASSERT_EQ(ret, -1); |
| 248 | + ASSERT_EQ(errno, ENOSPC) { |
| 249 | + TH_LOG("Expected ENOSPC for xattr %d, got %s", |
| 250 | + SIMPLE_XATTR_MAX_NR + 1, strerror(errno)); |
| 251 | + } |
| 252 | +} |
| 253 | + |
| 254 | +/* |
| 255 | + * Test maximum total value size for user.* xattrs. |
| 256 | + * The kernel enforces SIMPLE_XATTR_MAX_SIZE (128KB). Individual xattr |
| 257 | + * values are limited to XATTR_SIZE_MAX (64KB) by the VFS, so we need |
| 258 | + * at least two xattrs to hit the total limit. |
| 259 | + */ |
| 260 | +TEST_F(xattr_sockfs, max_xattr_size) |
| 261 | +{ |
| 262 | + char *value; |
| 263 | + int ret; |
| 264 | + |
| 265 | + value = malloc(XATTR_SIZE_MAX); |
| 266 | + ASSERT_NE(value, NULL); |
| 267 | + memset(value, 'A', XATTR_SIZE_MAX); |
| 268 | + |
| 269 | + /* First 64KB xattr - total = 64KB */ |
| 270 | + ret = fsetxattr(self->sockfd, "user.big1", value, XATTR_SIZE_MAX, 0); |
| 271 | + ASSERT_EQ(ret, 0) { |
| 272 | + TH_LOG("first large xattr failed: %s", strerror(errno)); |
| 273 | + } |
| 274 | + |
| 275 | + /* Second 64KB xattr - total = 128KB (exactly at limit) */ |
| 276 | + ret = fsetxattr(self->sockfd, "user.big2", value, XATTR_SIZE_MAX, 0); |
| 277 | + free(value); |
| 278 | + ASSERT_EQ(ret, 0) { |
| 279 | + TH_LOG("second large xattr failed: %s", strerror(errno)); |
| 280 | + } |
| 281 | + |
| 282 | + /* Third xattr with 1 byte - total > 128KB, should fail */ |
| 283 | + ret = fsetxattr(self->sockfd, "user.big3", "v", 1, 0); |
| 284 | + ASSERT_EQ(ret, -1); |
| 285 | + ASSERT_EQ(errno, ENOSPC) { |
| 286 | + TH_LOG("Expected ENOSPC when exceeding size limit, got %s", |
| 287 | + strerror(errno)); |
| 288 | + } |
| 289 | +} |
| 290 | + |
| 291 | +/* |
| 292 | + * Test that removing an xattr frees limit space, allowing re-addition. |
| 293 | + */ |
| 294 | +TEST_F(xattr_sockfs, limit_remove_readd) |
| 295 | +{ |
| 296 | + char name[32]; |
| 297 | + int i, ret; |
| 298 | + |
| 299 | + /* Fill up to the maximum count */ |
| 300 | + for (i = 0; i < SIMPLE_XATTR_MAX_NR; i++) { |
| 301 | + snprintf(name, sizeof(name), "user.test%03d", i); |
| 302 | + ret = fsetxattr(self->sockfd, name, "v", 1, 0); |
| 303 | + ASSERT_EQ(ret, 0); |
| 304 | + } |
| 305 | + |
| 306 | + /* Verify we're at the limit */ |
| 307 | + ret = fsetxattr(self->sockfd, "user.overflow", "v", 1, 0); |
| 308 | + ASSERT_EQ(ret, -1); |
| 309 | + ASSERT_EQ(errno, ENOSPC); |
| 310 | + |
| 311 | + /* Remove one xattr */ |
| 312 | + ret = fremovexattr(self->sockfd, "user.test000"); |
| 313 | + ASSERT_EQ(ret, 0); |
| 314 | + |
| 315 | + /* Now we should be able to add one more */ |
| 316 | + ret = fsetxattr(self->sockfd, "user.newattr", "v", 1, 0); |
| 317 | + ASSERT_EQ(ret, 0) { |
| 318 | + TH_LOG("re-add after remove failed: %s", strerror(errno)); |
| 319 | + } |
| 320 | +} |
| 321 | + |
| 322 | +/* |
| 323 | + * Test that two different sockets have independent xattr limits. |
| 324 | + */ |
| 325 | +TEST_F(xattr_sockfs, limits_per_inode) |
| 326 | +{ |
| 327 | + char buf[256]; |
| 328 | + int sock2; |
| 329 | + ssize_t ret; |
| 330 | + |
| 331 | + sock2 = socket(AF_UNIX, SOCK_STREAM, 0); |
| 332 | + ASSERT_GE(sock2, 0); |
| 333 | + |
| 334 | + /* Set xattr on first socket */ |
| 335 | + ret = fsetxattr(self->sockfd, TEST_XATTR_NAME, |
| 336 | + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); |
| 337 | + ASSERT_EQ(ret, 0); |
| 338 | + |
| 339 | + /* First socket's xattr should not be visible on second socket */ |
| 340 | + ret = fgetxattr(sock2, TEST_XATTR_NAME, NULL, 0); |
| 341 | + ASSERT_EQ(ret, -1); |
| 342 | + ASSERT_EQ(errno, ENODATA); |
| 343 | + |
| 344 | + /* Second socket should independently accept xattrs */ |
| 345 | + ret = fsetxattr(sock2, TEST_XATTR_NAME, |
| 346 | + TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), 0); |
| 347 | + ASSERT_EQ(ret, 0); |
| 348 | + |
| 349 | + /* Verify each socket has its own value */ |
| 350 | + memset(buf, 0, sizeof(buf)); |
| 351 | + ret = fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); |
| 352 | + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); |
| 353 | + ASSERT_STREQ(buf, TEST_XATTR_VALUE); |
| 354 | + |
| 355 | + memset(buf, 0, sizeof(buf)); |
| 356 | + ret = fgetxattr(sock2, TEST_XATTR_NAME, buf, sizeof(buf)); |
| 357 | + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE2)); |
| 358 | + ASSERT_STREQ(buf, TEST_XATTR_VALUE2); |
| 359 | + |
| 360 | + close(sock2); |
| 361 | +} |
| 362 | + |
| 363 | +TEST_HARNESS_MAIN |
0 commit comments