xref: /linux/tools/testing/selftests/filesystems/configfs/configfs_test.c (revision bb29b957eaaa15be33243550d9ee576c2d601669)
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