1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Exercise the configfs userspace interface through the subsystems 4 * registered by samples/configfs. 5 * 6 * Copyright (c) 2026 Meta Platforms, Inc. and affiliates 7 * Copyright (c) 2026 Breno Leitao <leitao@debian.org> 8 */ 9 #define _GNU_SOURCE 10 11 #include <errno.h> 12 #include <fcntl.h> 13 #include <limits.h> 14 #include <pthread.h> 15 #include <sched.h> 16 #include <stdbool.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 #include <sys/mount.h> 21 #include <sys/stat.h> 22 #include <sys/syscall.h> 23 #include <sys/vfs.h> 24 #include <unistd.h> 25 26 #include "kselftest_harness.h" 27 28 /* Private to fs/configfs/mount.c. */ 29 #define CONFIGFS_MAGIC 0x62656570 30 31 #define SAMPLE_MODULE "configfs_sample" 32 33 #define CHILDLESS "01-childless" 34 #define SIMPLE "02-simple-children" 35 #define GROUPS "03-group-children" 36 #define SYMLINKS "04-symlink-children" 37 38 #define ITEM_A SIMPLE "/kselftest-a" 39 #define ITEM_B SIMPLE "/kselftest-b" 40 #define GROUP GROUPS "/kselftest-group" 41 #define GROUP_ITEM GROUP "/kselftest-a" 42 #define LINK_SRC SYMLINKS "/kselftest-src" 43 #define LINK LINK_SRC "/kselftest-link" 44 45 #define RACE_ITERATIONS 20000 46 47 static const char * const test_links[] = { 48 LINK, 49 }; 50 51 /* Deepest first, so one pass empties the tree. */ 52 static const char * const test_dirs[] = { 53 GROUP_ITEM, 54 GROUP, 55 LINK_SRC, 56 ITEM_A, 57 ITEM_B, 58 }; 59 60 static void drop_test_dirs(void) 61 { 62 size_t i; 63 64 /* Links first: they hold both their source and their target. */ 65 for (i = 0; i < ARRAY_SIZE(test_links); i++) 66 unlink(test_links[i]); 67 68 for (i = 0; i < ARRAY_SIZE(test_dirs); i++) 69 rmdir(test_dirs[i]); 70 } 71 72 static ssize_t read_attr(const char *path, char *buf, size_t len) 73 { 74 ssize_t ret; 75 int fd; 76 77 fd = open(path, O_RDONLY); 78 if (fd < 0) 79 return -1; 80 81 ret = read(fd, buf, len - 1); 82 close(fd); 83 if (ret < 0) 84 return -1; 85 86 buf[ret] = '\0'; 87 return ret; 88 } 89 90 static ssize_t write_attr(const char *path, const char *val) 91 { 92 ssize_t ret; 93 int fd, err; 94 95 fd = open(path, O_WRONLY); 96 if (fd < 0) 97 return -1; 98 99 ret = write(fd, val, strlen(val)); 100 err = errno; 101 close(fd); 102 errno = err; 103 104 return ret; 105 } 106 107 FIXTURE(configfs) { 108 char mnt[sizeof(P_tmpdir "/configfs_XXXXXX")]; 109 bool mounted; 110 }; 111 112 FIXTURE_SETUP(configfs) 113 { 114 char tmpl[] = P_tmpdir "/configfs_XXXXXX"; 115 116 if (geteuid()) 117 SKIP(return, "need root to load modules and mount configfs"); 118 119 ASSERT_EQ(system("modprobe -q " SAMPLE_MODULE), 0) 120 TH_LOG(SAMPLE_MODULE " missing, is CONFIG_SAMPLE_CONFIGFS=m?"); 121 122 ASSERT_EQ(unshare(CLONE_NEWNS), 0); 123 ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); 124 125 ASSERT_NE(mkdtemp(tmpl), NULL); 126 strcpy(self->mnt, tmpl); 127 128 ASSERT_EQ(mount("configfs", self->mnt, "configfs", 0, NULL), 0); 129 ASSERT_EQ(chdir(self->mnt), 0); 130 self->mounted = true; 131 132 /* configfs items outlive the mount, so a killed run leaves some. */ 133 drop_test_dirs(); 134 } 135 136 FIXTURE_TEARDOWN(configfs) 137 { 138 if (self->mounted) { 139 drop_test_dirs(); 140 EXPECT_EQ(chdir("/"), 0); 141 EXPECT_EQ(umount2(self->mnt, MNT_DETACH), 0); 142 } 143 144 if (self->mnt[0]) 145 EXPECT_EQ(rmdir(self->mnt), 0); 146 } 147 148 TEST_F(configfs, mount_and_subsystems) 149 { 150 const char * const subsys[] = { CHILDLESS, SIMPLE, GROUPS, SYMLINKS }; 151 struct statfs sfs; 152 struct stat st; 153 size_t i; 154 155 ASSERT_EQ(statfs(".", &sfs), 0); 156 EXPECT_EQ(sfs.f_type, CONFIGFS_MAGIC); 157 158 for (i = 0; i < ARRAY_SIZE(subsys); i++) { 159 ASSERT_EQ(stat(subsys[i], &st), 0) 160 TH_LOG("%s is missing", subsys[i]); 161 EXPECT_TRUE(S_ISDIR(st.st_mode)); 162 } 163 } 164 165 TEST_F(configfs, mkdir_at_root) 166 { 167 /* The root has no ->mkdir(); only subsystems register there. */ 168 ASSERT_EQ(mkdir("kselftest-root", 0755), -1); 169 EXPECT_EQ(errno, EPERM); 170 } 171 172 TEST_F(configfs, rmdir_subsystem) 173 { 174 ASSERT_EQ(rmdir(CHILDLESS), -1); 175 EXPECT_EQ(errno, EPERM); 176 } 177 178 TEST_F(configfs, mkdir_without_group_ops) 179 { 180 /* 01-childless has attributes but no ->make_item()/->make_group(). */ 181 ASSERT_EQ(mkdir(CHILDLESS "/kselftest-a", 0755), -1); 182 EXPECT_EQ(errno, EPERM); 183 } 184 185 TEST_F(configfs, attr_store_and_show) 186 { 187 char buf[64]; 188 189 ASSERT_GT(write_attr(CHILDLESS "/storeme", "42"), 0); 190 ASSERT_GT(read_attr(CHILDLESS "/storeme", buf, sizeof(buf)), 0); 191 EXPECT_STREQ(buf, "42\n"); 192 } 193 194 TEST_F(configfs, attr_store_rejects_garbage) 195 { 196 ASSERT_EQ(write_attr(CHILDLESS "/storeme", "not-a-number"), -1); 197 EXPECT_EQ(errno, EINVAL); 198 } 199 200 TEST_F(configfs, attr_show_runs_on_every_open) 201 { 202 char first[64], second[64]; 203 204 /* 01-childless/showme increments the value it just returned. */ 205 ASSERT_GT(read_attr(CHILDLESS "/showme", first, sizeof(first)), 0); 206 ASSERT_GT(read_attr(CHILDLESS "/showme", second, sizeof(second)), 0); 207 EXPECT_EQ(atoi(second), atoi(first) + 1); 208 } 209 210 TEST_F(configfs, attr_read_only) 211 { 212 ASSERT_EQ(open(CHILDLESS "/description", O_WRONLY), -1); 213 EXPECT_EQ(errno, EACCES); 214 } 215 216 TEST_F(configfs, attr_unlink) 217 { 218 /* ->unlink() only accepts the symlinks configfs itself created. */ 219 ASSERT_EQ(unlink(CHILDLESS "/storeme"), -1); 220 EXPECT_EQ(errno, EPERM); 221 } 222 223 TEST_F(configfs, attr_read_length) 224 { 225 char buf[8192]; 226 struct stat st; 227 ssize_t n; 228 int fd; 229 230 fd = open(CHILDLESS "/description", O_RDONLY); 231 ASSERT_GE(fd, 0); 232 233 /* Attributes report a page, whatever ->show() ends up producing. */ 234 ASSERT_EQ(fstat(fd, &st), 0); 235 EXPECT_EQ(st.st_size, sysconf(_SC_PAGESIZE)); 236 237 n = read(fd, buf, sizeof(buf)); 238 ASSERT_GT(n, 0); 239 EXPECT_LT(n, st.st_size); 240 EXPECT_EQ(read(fd, buf, sizeof(buf)), 0); 241 242 EXPECT_EQ(close(fd), 0); 243 } 244 245 TEST_F(configfs, attr_write_is_not_incremental) 246 { 247 char buf[64]; 248 int fd; 249 250 /* 251 * Every write hands the whole buffer to ->store() and the file 252 * position is ignored, so the second write replaces the first. 253 */ 254 fd = open(CHILDLESS "/storeme", O_WRONLY); 255 ASSERT_GE(fd, 0); 256 ASSERT_EQ(write(fd, "1", 1), 1); 257 ASSERT_EQ(write(fd, "2", 1), 1); 258 EXPECT_EQ(close(fd), 0); 259 260 ASSERT_GT(read_attr(CHILDLESS "/storeme", buf, sizeof(buf)), 0); 261 EXPECT_STREQ(buf, "2\n"); 262 } 263 264 TEST_F(configfs, item_create_and_drop) 265 { 266 struct stat st; 267 268 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 269 EXPECT_EQ(stat(ITEM_A "/storeme", &st), 0); 270 271 /* The item carries its own attributes, not the subsystem's. */ 272 ASSERT_EQ(stat(ITEM_A "/description", &st), -1); 273 EXPECT_EQ(errno, ENOENT); 274 275 ASSERT_EQ(rmdir(ITEM_A), 0); 276 ASSERT_EQ(stat(ITEM_A, &st), -1); 277 EXPECT_EQ(errno, ENOENT); 278 } 279 280 TEST_F(configfs, item_create_twice) 281 { 282 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 283 ASSERT_EQ(mkdir(ITEM_A, 0755), -1); 284 EXPECT_EQ(errno, EEXIST); 285 } 286 287 TEST_F(configfs, item_has_no_children) 288 { 289 /* ->make_item() produces an item, so it cannot nest. */ 290 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 291 ASSERT_EQ(mkdir(ITEM_A "/kselftest-b", 0755), -1); 292 EXPECT_EQ(errno, EPERM); 293 } 294 295 TEST_F(configfs, item_attrs_are_private) 296 { 297 char buf[64]; 298 299 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 300 ASSERT_EQ(mkdir(ITEM_B, 0755), 0); 301 302 ASSERT_GT(write_attr(ITEM_A "/storeme", "11"), 0); 303 ASSERT_GT(write_attr(ITEM_B "/storeme", "22"), 0); 304 305 ASSERT_GT(read_attr(ITEM_A "/storeme", buf, sizeof(buf)), 0); 306 EXPECT_STREQ(buf, "11\n"); 307 ASSERT_GT(read_attr(ITEM_B "/storeme", buf, sizeof(buf)), 0); 308 EXPECT_STREQ(buf, "22\n"); 309 } 310 311 TEST_F(configfs, group_create_and_drop) 312 { 313 struct stat st; 314 315 /* 03-group-children hands out groups that take items of their own. */ 316 ASSERT_EQ(mkdir(GROUP, 0755), 0); 317 EXPECT_EQ(stat(GROUP "/description", &st), 0); 318 319 ASSERT_EQ(mkdir(GROUP_ITEM, 0755), 0); 320 EXPECT_EQ(stat(GROUP_ITEM "/storeme", &st), 0); 321 322 ASSERT_EQ(rmdir(GROUP), -1); 323 EXPECT_EQ(errno, ENOTEMPTY); 324 325 ASSERT_EQ(rmdir(GROUP_ITEM), 0); 326 ASSERT_EQ(rmdir(GROUP), 0); 327 } 328 329 TEST_F(configfs, rename_item) 330 { 331 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 332 ASSERT_EQ(rename(ITEM_A, ITEM_B), -1); 333 EXPECT_EQ(errno, EPERM); 334 } 335 336 TEST_F(configfs, symlink_without_allow_link) 337 { 338 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 339 ASSERT_EQ(symlink(ITEM_A, SIMPLE "/kselftest-link"), -1); 340 EXPECT_EQ(errno, EPERM); 341 } 342 343 TEST_F(configfs, symlink_and_unlink) 344 { 345 char buf[PATH_MAX]; 346 struct stat st; 347 ssize_t n; 348 349 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 350 ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); 351 352 ASSERT_EQ(symlink(ITEM_A, LINK), 0); 353 354 /* configfs stores its own body, a path relative to the link. */ 355 n = readlink(LINK, buf, sizeof(buf) - 1); 356 ASSERT_GT(n, 0); 357 buf[n] = '\0'; 358 EXPECT_STREQ(buf, "../../" ITEM_A); 359 EXPECT_EQ(stat(LINK "/storeme", &st), 0); 360 361 /* ->allow_link() ran on the source, not on the target. */ 362 ASSERT_GT(read_attr(LINK_SRC "/nlinks", buf, sizeof(buf)), 0); 363 EXPECT_STREQ(buf, "1\n"); 364 365 ASSERT_EQ(unlink(LINK), 0); 366 ASSERT_GT(read_attr(LINK_SRC "/nlinks", buf, sizeof(buf)), 0); 367 EXPECT_STREQ(buf, "0\n"); 368 } 369 370 TEST_F(configfs, symlink_pins_both_ends) 371 { 372 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 373 ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); 374 ASSERT_EQ(symlink(ITEM_A, LINK), 0); 375 376 /* A linked item cannot go away under the link. */ 377 ASSERT_EQ(rmdir(ITEM_A), -1); 378 EXPECT_EQ(errno, EBUSY); 379 380 /* The link counts as a child of its source. */ 381 ASSERT_EQ(rmdir(LINK_SRC), -1); 382 EXPECT_EQ(errno, ENOTEMPTY); 383 384 ASSERT_EQ(unlink(LINK), 0); 385 EXPECT_EQ(rmdir(ITEM_A), 0); 386 } 387 388 TEST_F(configfs, symlink_target_outside_configfs) 389 { 390 ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); 391 ASSERT_EQ(symlink("/", LINK), -1); 392 EXPECT_EQ(errno, EPERM); 393 } 394 395 TEST_F(configfs, symlink_target_missing) 396 { 397 ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); 398 ASSERT_EQ(symlink(SIMPLE "/kselftest-gone", LINK), -1); 399 EXPECT_EQ(errno, ENOENT); 400 } 401 402 TEST_F(configfs, symlink_target_is_an_attribute) 403 { 404 ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); 405 406 /* The target is resolved with LOOKUP_DIRECTORY. */ 407 ASSERT_EQ(symlink(CHILDLESS "/storeme", LINK), -1); 408 EXPECT_EQ(errno, ENOTDIR); 409 } 410 411 static volatile int race_stop; 412 413 static void *rmdir_target(void *arg) 414 { 415 while (!race_stop) { 416 if (mkdir(ITEM_A, 0755) == 0 || errno == EEXIST) 417 rmdir(ITEM_A); 418 } 419 420 return NULL; 421 } 422 423 /* -1 if the kernel does not export a warning counter. */ 424 static long warn_count(void) 425 { 426 char buf[32]; 427 428 if (read_attr("/sys/kernel/warn_count", buf, sizeof(buf)) < 0) 429 return -1; 430 431 return strtol(buf, NULL, 10); 432 } 433 434 TEST_F(configfs, symlink_races_with_target_rmdir) 435 { 436 pthread_t thread; 437 long warns; 438 int i; 439 440 warns = warn_count(); 441 if (warns < 0) 442 SKIP(return, "no /sys/kernel/warn_count to watch"); 443 444 ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); 445 ASSERT_EQ(pthread_create(&thread, NULL, rmdir_target, NULL), 0); 446 447 /* 448 * configfs_rmdir() drops the last reference to the item while its 449 * dentry is still hashed, and get_target() takes a hashed dentry as 450 * proof that the item behind it is alive. The symlink then walks 451 * ->ci_dentry into a released dirent, which configfs_get() warns 452 * about. KASAN sees the freed item itself. 453 */ 454 for (i = 0; i < RACE_ITERATIONS; i++) { 455 if (symlink(ITEM_A, LINK) == 0) 456 unlink(LINK); 457 458 /* Give up on the first splat rather than flood the log. */ 459 if (!(i % 128) && warn_count() != warns) 460 break; 461 } 462 463 race_stop = 1; 464 ASSERT_EQ(pthread_join(thread, NULL), 0); 465 466 EXPECT_EQ(warn_count(), warns) 467 TH_LOG("kernel warned after %d iterations", i); 468 } 469 470 TEST_F(configfs, module_pinned_by_item) 471 { 472 ASSERT_EQ(mkdir(ITEM_A, 0755), 0); 473 474 /* mkdir() pins both the subsystem's module and the new item's. */ 475 ASSERT_EQ(syscall(__NR_delete_module, SAMPLE_MODULE, O_NONBLOCK), -1); 476 if (errno == ENOSYS) 477 SKIP(return, "kernel built without CONFIG_MODULE_UNLOAD"); 478 EXPECT_EQ(errno, EWOULDBLOCK); 479 } 480 481 TEST_HARNESS_MAIN 482