AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/26 11:04 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch only adds a new selftest for configfs and modifies the `configfs_sample` module in `samples/` to support symlinks for the test. It does not modify any core kernel logic, real drivers, or architectures. Therefore, it does not require fuzzing.",
  "WorthFuzzing": false
}

1/1 2026/08/26 11:04 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 20bc6afa352de6047a2d718aef3f5917535b6019\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Aug 26 11:04:28 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex fe10d29b7657b..69aea9931b774 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -6604,6 +6604,7 @@ S:\tSupported\n F:\tfs/configfs/\n F:\tinclude/linux/configfs.h\n F:\tsamples/configfs/\n+F:\ttools/testing/selftests/filesystems/configfs/\n \n CONFIGFS [RUST]\n M:\tAndreas Hindborg \u003ca.hindborg@kernel.org\u003e\ndiff --git a/samples/configfs/configfs_sample.c b/samples/configfs/configfs_sample.c\nindex c1b108ec4ea02..08403e787aef6 100644\n--- a/samples/configfs/configfs_sample.c\n+++ b/samples/configfs/configfs_sample.c\n@@ -313,6 +313,126 @@ static struct configfs_subsystem group_children_subsys = {\n \n /* ----------------------------------------------------------------- */\n \n+/*\n+ * 04-symlink-children\n+ *\n+ * This example has children that are valid sources for symlink(2).  A\n+ * child accepts a link to any other config_item and reports how many\n+ * links it currently holds, so -\u003eallow_link() and -\u003edrop_link() are\n+ * observable from userspace.\n+ */\n+\n+struct symlink_child {\n+\tstruct config_item item;\n+\tint nlinks;\n+};\n+\n+static inline struct symlink_child *to_symlink_child(struct config_item *item)\n+{\n+\treturn container_of(item, struct symlink_child, item);\n+}\n+\n+static ssize_t symlink_child_nlinks_show(struct config_item *item, char *page)\n+{\n+\treturn sprintf(page, \"%d\\n\", to_symlink_child(item)-\u003enlinks);\n+}\n+\n+CONFIGFS_ATTR_RO(symlink_child_, nlinks);\n+\n+static struct configfs_attribute *symlink_child_attrs[] = {\n+\t\u0026symlink_child_attr_nlinks,\n+\tNULL,\n+};\n+\n+/*\n+ * The VFS holds the source item's directory locked across symlink(2) and\n+ * unlink(2), so -\u003enlinks needs no lock of its own.\n+ */\n+static int symlink_child_allow_link(struct config_item *src,\n+\t\tstruct config_item *target)\n+{\n+\tto_symlink_child(src)-\u003enlinks++;\n+\n+\treturn 0;\n+}\n+\n+static void symlink_child_drop_link(struct config_item *src,\n+\t\tstruct config_item *target)\n+{\n+\tto_symlink_child(src)-\u003enlinks--;\n+}\n+\n+static void symlink_child_release(struct config_item *item)\n+{\n+\tkfree(to_symlink_child(item));\n+}\n+\n+static const struct configfs_item_operations symlink_child_item_ops = {\n+\t.release\t= symlink_child_release,\n+\t.allow_link\t= symlink_child_allow_link,\n+\t.drop_link\t= symlink_child_drop_link,\n+};\n+\n+static const struct config_item_type symlink_child_type = {\n+\t.ct_item_ops\t= \u0026symlink_child_item_ops,\n+\t.ct_attrs\t= symlink_child_attrs,\n+\t.ct_owner\t= THIS_MODULE,\n+};\n+\n+static struct config_item *symlink_children_make_item(\n+\t\tstruct config_group *group, const char *name)\n+{\n+\tstruct symlink_child *symlink_child;\n+\n+\tsymlink_child = kzalloc_obj(*symlink_child, GFP_KERNEL);\n+\tif (!symlink_child)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tconfig_item_init_type_name(\u0026symlink_child-\u003eitem, name,\n+\t\t\t\t   \u0026symlink_child_type);\n+\n+\treturn \u0026symlink_child-\u003eitem;\n+}\n+\n+static ssize_t symlink_children_description_show(struct config_item *item,\n+\t\tchar *page)\n+{\n+\treturn sprintf(page,\n+\"[04-symlink-children]\\n\"\n+\"\\n\"\n+\"This subsystem allows the creation of child config_items that\\n\"\n+\"symlink(2) can point at other config_items from.  Each child\\n\"\n+\"reports the number of links it holds.\\n\");\n+}\n+\n+CONFIGFS_ATTR_RO(symlink_children_, description);\n+\n+static struct configfs_attribute *symlink_children_attrs[] = {\n+\t\u0026symlink_children_attr_description,\n+\tNULL,\n+};\n+\n+static const struct configfs_group_operations symlink_children_group_ops = {\n+\t.make_item\t= symlink_children_make_item,\n+};\n+\n+static const struct config_item_type symlink_children_type = {\n+\t.ct_group_ops\t= \u0026symlink_children_group_ops,\n+\t.ct_attrs\t= symlink_children_attrs,\n+\t.ct_owner\t= THIS_MODULE,\n+};\n+\n+static struct configfs_subsystem symlink_children_subsys = {\n+\t.su_group = {\n+\t\t.cg_item = {\n+\t\t\t.ci_namebuf = \"04-symlink-children\",\n+\t\t\t.ci_type = \u0026symlink_children_type,\n+\t\t},\n+\t},\n+};\n+\n+/* ----------------------------------------------------------------- */\n+\n /*\n  * We're now done with our subsystem definitions.\n  * For convenience in this module, here's a list of them all.  It\n@@ -324,6 +444,7 @@ static struct configfs_subsystem *example_subsys[] = {\n \t\u0026childless_subsys.subsys,\n \t\u0026simple_children_subsys,\n \t\u0026group_children_subsys,\n+\t\u0026symlink_children_subsys,\n \tNULL,\n };\n \ndiff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile\nindex 2d960626750e3..b328a235f1295 100644\n--- a/tools/testing/selftests/Makefile\n+++ b/tools/testing/selftests/Makefile\n@@ -34,6 +34,7 @@ TARGETS += exec\n TARGETS += fchmodat2\n TARGETS += filesystems\n TARGETS += filesystems/binderfs\n+TARGETS += filesystems/configfs\n TARGETS += filesystems/epoll\n TARGETS += filesystems/failfs\n TARGETS += filesystems/fat\ndiff --git a/tools/testing/selftests/filesystems/configfs/.gitignore b/tools/testing/selftests/filesystems/configfs/.gitignore\nnew file mode 100644\nindex 0000000000000..accfb6bb48268\n--- /dev/null\n+++ b/tools/testing/selftests/filesystems/configfs/.gitignore\n@@ -0,0 +1,2 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+configfs_test\ndiff --git a/tools/testing/selftests/filesystems/configfs/Makefile b/tools/testing/selftests/filesystems/configfs/Makefile\nnew file mode 100644\nindex 0000000000000..40a91ed788ed2\n--- /dev/null\n+++ b/tools/testing/selftests/filesystems/configfs/Makefile\n@@ -0,0 +1,8 @@\n+# SPDX-License-Identifier: GPL-2.0\n+# Copyright (c) 2026 Meta Platforms, Inc. and affiliates\n+# Copyright (c) 2026 Breno Leitao \u003cleitao@debian.org\u003e\n+\n+CFLAGS += -Wall -Werror -pthread\n+TEST_GEN_PROGS := configfs_test\n+\n+include ../../lib.mk\ndiff --git a/tools/testing/selftests/filesystems/configfs/config b/tools/testing/selftests/filesystems/configfs/config\nnew file mode 100644\nindex 0000000000000..5ea17df535b3f\n--- /dev/null\n+++ b/tools/testing/selftests/filesystems/configfs/config\n@@ -0,0 +1,5 @@\n+CONFIG_CONFIGFS_FS=y\n+CONFIG_MODULES=y\n+CONFIG_MODULE_UNLOAD=y\n+CONFIG_SAMPLES=y\n+CONFIG_SAMPLE_CONFIGFS=m\ndiff --git a/tools/testing/selftests/filesystems/configfs/configfs_test.c b/tools/testing/selftests/filesystems/configfs/configfs_test.c\nnew file mode 100644\nindex 0000000000000..c6a1049e5852e\n--- /dev/null\n+++ b/tools/testing/selftests/filesystems/configfs/configfs_test.c\n@@ -0,0 +1,481 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Exercise the configfs userspace interface through the subsystems\n+ * registered by samples/configfs.\n+ *\n+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates\n+ * Copyright (c) 2026 Breno Leitao \u003cleitao@debian.org\u003e\n+ */\n+#define _GNU_SOURCE\n+\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003climits.h\u003e\n+#include \u003cpthread.h\u003e\n+#include \u003csched.h\u003e\n+#include \u003cstdbool.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/mount.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/syscall.h\u003e\n+#include \u003csys/vfs.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"kselftest_harness.h\"\n+\n+/* Private to fs/configfs/mount.c. */\n+#define CONFIGFS_MAGIC\t\t0x62656570\n+\n+#define SAMPLE_MODULE\t\t\"configfs_sample\"\n+\n+#define CHILDLESS\t\t\"01-childless\"\n+#define SIMPLE\t\t\t\"02-simple-children\"\n+#define GROUPS\t\t\t\"03-group-children\"\n+#define SYMLINKS\t\t\"04-symlink-children\"\n+\n+#define ITEM_A\t\t\tSIMPLE \"/kselftest-a\"\n+#define ITEM_B\t\t\tSIMPLE \"/kselftest-b\"\n+#define GROUP\t\t\tGROUPS \"/kselftest-group\"\n+#define GROUP_ITEM\t\tGROUP \"/kselftest-a\"\n+#define LINK_SRC\t\tSYMLINKS \"/kselftest-src\"\n+#define LINK\t\t\tLINK_SRC \"/kselftest-link\"\n+\n+#define RACE_ITERATIONS\t\t20000\n+\n+static const char * const test_links[] = {\n+\tLINK,\n+};\n+\n+/* Deepest first, so one pass empties the tree. */\n+static const char * const test_dirs[] = {\n+\tGROUP_ITEM,\n+\tGROUP,\n+\tLINK_SRC,\n+\tITEM_A,\n+\tITEM_B,\n+};\n+\n+static void drop_test_dirs(void)\n+{\n+\tsize_t i;\n+\n+\t/* Links first: they hold both their source and their target. */\n+\tfor (i = 0; i \u003c ARRAY_SIZE(test_links); i++)\n+\t\tunlink(test_links[i]);\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(test_dirs); i++)\n+\t\trmdir(test_dirs[i]);\n+}\n+\n+static ssize_t read_attr(const char *path, char *buf, size_t len)\n+{\n+\tssize_t ret;\n+\tint fd;\n+\n+\tfd = open(path, O_RDONLY);\n+\tif (fd \u003c 0)\n+\t\treturn -1;\n+\n+\tret = read(fd, buf, len - 1);\n+\tclose(fd);\n+\tif (ret \u003c 0)\n+\t\treturn -1;\n+\n+\tbuf[ret] = '\\0';\n+\treturn ret;\n+}\n+\n+static ssize_t write_attr(const char *path, const char *val)\n+{\n+\tssize_t ret;\n+\tint fd, err;\n+\n+\tfd = open(path, O_WRONLY);\n+\tif (fd \u003c 0)\n+\t\treturn -1;\n+\n+\tret = write(fd, val, strlen(val));\n+\terr = errno;\n+\tclose(fd);\n+\terrno = err;\n+\n+\treturn ret;\n+}\n+\n+FIXTURE(configfs) {\n+\tchar mnt[sizeof(P_tmpdir \"/configfs_XXXXXX\")];\n+\tbool mounted;\n+};\n+\n+FIXTURE_SETUP(configfs)\n+{\n+\tchar tmpl[] = P_tmpdir \"/configfs_XXXXXX\";\n+\n+\tif (geteuid())\n+\t\tSKIP(return, \"need root to load modules and mount configfs\");\n+\n+\tASSERT_EQ(system(\"modprobe -q \" SAMPLE_MODULE), 0)\n+\t\tTH_LOG(SAMPLE_MODULE \" missing, is CONFIG_SAMPLE_CONFIGFS=m?\");\n+\n+\tASSERT_EQ(unshare(CLONE_NEWNS), 0);\n+\tASSERT_EQ(mount(NULL, \"/\", NULL, MS_REC | MS_PRIVATE, NULL), 0);\n+\n+\tASSERT_NE(mkdtemp(tmpl), NULL);\n+\tstrcpy(self-\u003emnt, tmpl);\n+\n+\tASSERT_EQ(mount(\"configfs\", self-\u003emnt, \"configfs\", 0, NULL), 0);\n+\tASSERT_EQ(chdir(self-\u003emnt), 0);\n+\tself-\u003emounted = true;\n+\n+\t/* configfs items outlive the mount, so a killed run leaves some. */\n+\tdrop_test_dirs();\n+}\n+\n+FIXTURE_TEARDOWN(configfs)\n+{\n+\tif (self-\u003emounted) {\n+\t\tdrop_test_dirs();\n+\t\tEXPECT_EQ(chdir(\"/\"), 0);\n+\t\tEXPECT_EQ(umount2(self-\u003emnt, MNT_DETACH), 0);\n+\t}\n+\n+\tif (self-\u003emnt[0])\n+\t\tEXPECT_EQ(rmdir(self-\u003emnt), 0);\n+}\n+\n+TEST_F(configfs, mount_and_subsystems)\n+{\n+\tconst char * const subsys[] = { CHILDLESS, SIMPLE, GROUPS, SYMLINKS };\n+\tstruct statfs sfs;\n+\tstruct stat st;\n+\tsize_t i;\n+\n+\tASSERT_EQ(statfs(\".\", \u0026sfs), 0);\n+\tEXPECT_EQ(sfs.f_type, CONFIGFS_MAGIC);\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(subsys); i++) {\n+\t\tASSERT_EQ(stat(subsys[i], \u0026st), 0)\n+\t\t\tTH_LOG(\"%s is missing\", subsys[i]);\n+\t\tEXPECT_TRUE(S_ISDIR(st.st_mode));\n+\t}\n+}\n+\n+TEST_F(configfs, mkdir_at_root)\n+{\n+\t/* The root has no -\u003emkdir(); only subsystems register there. */\n+\tASSERT_EQ(mkdir(\"kselftest-root\", 0755), -1);\n+\tEXPECT_EQ(errno, EPERM);\n+}\n+\n+TEST_F(configfs, rmdir_subsystem)\n+{\n+\tASSERT_EQ(rmdir(CHILDLESS), -1);\n+\tEXPECT_EQ(errno, EPERM);\n+}\n+\n+TEST_F(configfs, mkdir_without_group_ops)\n+{\n+\t/* 01-childless has attributes but no -\u003emake_item()/-\u003emake_group(). */\n+\tASSERT_EQ(mkdir(CHILDLESS \"/kselftest-a\", 0755), -1);\n+\tEXPECT_EQ(errno, EPERM);\n+}\n+\n+TEST_F(configfs, attr_store_and_show)\n+{\n+\tchar buf[64];\n+\n+\tASSERT_GT(write_attr(CHILDLESS \"/storeme\", \"42\"), 0);\n+\tASSERT_GT(read_attr(CHILDLESS \"/storeme\", buf, sizeof(buf)), 0);\n+\tEXPECT_STREQ(buf, \"42\\n\");\n+}\n+\n+TEST_F(configfs, attr_store_rejects_garbage)\n+{\n+\tASSERT_EQ(write_attr(CHILDLESS \"/storeme\", \"not-a-number\"), -1);\n+\tEXPECT_EQ(errno, EINVAL);\n+}\n+\n+TEST_F(configfs, attr_show_runs_on_every_open)\n+{\n+\tchar first[64], second[64];\n+\n+\t/* 01-childless/showme increments the value it just returned. */\n+\tASSERT_GT(read_attr(CHILDLESS \"/showme\", first, sizeof(first)), 0);\n+\tASSERT_GT(read_attr(CHILDLESS \"/showme\", second, sizeof(second)), 0);\n+\tEXPECT_EQ(atoi(second), atoi(first) + 1);\n+}\n+\n+TEST_F(configfs, attr_read_only)\n+{\n+\tASSERT_EQ(open(CHILDLESS \"/description\", O_WRONLY), -1);\n+\tEXPECT_EQ(errno, EACCES);\n+}\n+\n+TEST_F(configfs, attr_unlink)\n+{\n+\t/* -\u003eunlink() only accepts the symlinks configfs itself created. */\n+\tASSERT_EQ(unlink(CHILDLESS \"/storeme\"), -1);\n+\tEXPECT_EQ(errno, EPERM);\n+}\n+\n+TEST_F(configfs, attr_read_length)\n+{\n+\tchar buf[8192];\n+\tstruct stat st;\n+\tssize_t n;\n+\tint fd;\n+\n+\tfd = open(CHILDLESS \"/description\", O_RDONLY);\n+\tASSERT_GE(fd, 0);\n+\n+\t/* Attributes report a page, whatever -\u003eshow() ends up producing. */\n+\tASSERT_EQ(fstat(fd, \u0026st), 0);\n+\tEXPECT_EQ(st.st_size, sysconf(_SC_PAGESIZE));\n+\n+\tn = read(fd, buf, sizeof(buf));\n+\tASSERT_GT(n, 0);\n+\tEXPECT_LT(n, st.st_size);\n+\tEXPECT_EQ(read(fd, buf, sizeof(buf)), 0);\n+\n+\tEXPECT_EQ(close(fd), 0);\n+}\n+\n+TEST_F(configfs, attr_write_is_not_incremental)\n+{\n+\tchar buf[64];\n+\tint fd;\n+\n+\t/*\n+\t * Every write hands the whole buffer to -\u003estore() and the file\n+\t * position is ignored, so the second write replaces the first.\n+\t */\n+\tfd = open(CHILDLESS \"/storeme\", O_WRONLY);\n+\tASSERT_GE(fd, 0);\n+\tASSERT_EQ(write(fd, \"1\", 1), 1);\n+\tASSERT_EQ(write(fd, \"2\", 1), 1);\n+\tEXPECT_EQ(close(fd), 0);\n+\n+\tASSERT_GT(read_attr(CHILDLESS \"/storeme\", buf, sizeof(buf)), 0);\n+\tEXPECT_STREQ(buf, \"2\\n\");\n+}\n+\n+TEST_F(configfs, item_create_and_drop)\n+{\n+\tstruct stat st;\n+\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\tEXPECT_EQ(stat(ITEM_A \"/storeme\", \u0026st), 0);\n+\n+\t/* The item carries its own attributes, not the subsystem's. */\n+\tASSERT_EQ(stat(ITEM_A \"/description\", \u0026st), -1);\n+\tEXPECT_EQ(errno, ENOENT);\n+\n+\tASSERT_EQ(rmdir(ITEM_A), 0);\n+\tASSERT_EQ(stat(ITEM_A, \u0026st), -1);\n+\tEXPECT_EQ(errno, ENOENT);\n+}\n+\n+TEST_F(configfs, item_create_twice)\n+{\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), -1);\n+\tEXPECT_EQ(errno, EEXIST);\n+}\n+\n+TEST_F(configfs, item_has_no_children)\n+{\n+\t/* -\u003emake_item() produces an item, so it cannot nest. */\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\tASSERT_EQ(mkdir(ITEM_A \"/kselftest-b\", 0755), -1);\n+\tEXPECT_EQ(errno, EPERM);\n+}\n+\n+TEST_F(configfs, item_attrs_are_private)\n+{\n+\tchar buf[64];\n+\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\tASSERT_EQ(mkdir(ITEM_B, 0755), 0);\n+\n+\tASSERT_GT(write_attr(ITEM_A \"/storeme\", \"11\"), 0);\n+\tASSERT_GT(write_attr(ITEM_B \"/storeme\", \"22\"), 0);\n+\n+\tASSERT_GT(read_attr(ITEM_A \"/storeme\", buf, sizeof(buf)), 0);\n+\tEXPECT_STREQ(buf, \"11\\n\");\n+\tASSERT_GT(read_attr(ITEM_B \"/storeme\", buf, sizeof(buf)), 0);\n+\tEXPECT_STREQ(buf, \"22\\n\");\n+}\n+\n+TEST_F(configfs, group_create_and_drop)\n+{\n+\tstruct stat st;\n+\n+\t/* 03-group-children hands out groups that take items of their own. */\n+\tASSERT_EQ(mkdir(GROUP, 0755), 0);\n+\tEXPECT_EQ(stat(GROUP \"/description\", \u0026st), 0);\n+\n+\tASSERT_EQ(mkdir(GROUP_ITEM, 0755), 0);\n+\tEXPECT_EQ(stat(GROUP_ITEM \"/storeme\", \u0026st), 0);\n+\n+\tASSERT_EQ(rmdir(GROUP), -1);\n+\tEXPECT_EQ(errno, ENOTEMPTY);\n+\n+\tASSERT_EQ(rmdir(GROUP_ITEM), 0);\n+\tASSERT_EQ(rmdir(GROUP), 0);\n+}\n+\n+TEST_F(configfs, rename_item)\n+{\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\tASSERT_EQ(rename(ITEM_A, ITEM_B), -1);\n+\tEXPECT_EQ(errno, EPERM);\n+}\n+\n+TEST_F(configfs, symlink_without_allow_link)\n+{\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\tASSERT_EQ(symlink(ITEM_A, SIMPLE \"/kselftest-link\"), -1);\n+\tEXPECT_EQ(errno, EPERM);\n+}\n+\n+TEST_F(configfs, symlink_and_unlink)\n+{\n+\tchar buf[PATH_MAX];\n+\tstruct stat st;\n+\tssize_t n;\n+\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\tASSERT_EQ(mkdir(LINK_SRC, 0755), 0);\n+\n+\tASSERT_EQ(symlink(ITEM_A, LINK), 0);\n+\n+\t/* configfs stores its own body, a path relative to the link. */\n+\tn = readlink(LINK, buf, sizeof(buf) - 1);\n+\tASSERT_GT(n, 0);\n+\tbuf[n] = '\\0';\n+\tEXPECT_STREQ(buf, \"../../\" ITEM_A);\n+\tEXPECT_EQ(stat(LINK \"/storeme\", \u0026st), 0);\n+\n+\t/* -\u003eallow_link() ran on the source, not on the target. */\n+\tASSERT_GT(read_attr(LINK_SRC \"/nlinks\", buf, sizeof(buf)), 0);\n+\tEXPECT_STREQ(buf, \"1\\n\");\n+\n+\tASSERT_EQ(unlink(LINK), 0);\n+\tASSERT_GT(read_attr(LINK_SRC \"/nlinks\", buf, sizeof(buf)), 0);\n+\tEXPECT_STREQ(buf, \"0\\n\");\n+}\n+\n+TEST_F(configfs, symlink_pins_both_ends)\n+{\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\tASSERT_EQ(mkdir(LINK_SRC, 0755), 0);\n+\tASSERT_EQ(symlink(ITEM_A, LINK), 0);\n+\n+\t/* A linked item cannot go away under the link. */\n+\tASSERT_EQ(rmdir(ITEM_A), -1);\n+\tEXPECT_EQ(errno, EBUSY);\n+\n+\t/* The link counts as a child of its source. */\n+\tASSERT_EQ(rmdir(LINK_SRC), -1);\n+\tEXPECT_EQ(errno, ENOTEMPTY);\n+\n+\tASSERT_EQ(unlink(LINK), 0);\n+\tEXPECT_EQ(rmdir(ITEM_A), 0);\n+}\n+\n+TEST_F(configfs, symlink_target_outside_configfs)\n+{\n+\tASSERT_EQ(mkdir(LINK_SRC, 0755), 0);\n+\tASSERT_EQ(symlink(\"/\", LINK), -1);\n+\tEXPECT_EQ(errno, EPERM);\n+}\n+\n+TEST_F(configfs, symlink_target_missing)\n+{\n+\tASSERT_EQ(mkdir(LINK_SRC, 0755), 0);\n+\tASSERT_EQ(symlink(SIMPLE \"/kselftest-gone\", LINK), -1);\n+\tEXPECT_EQ(errno, ENOENT);\n+}\n+\n+TEST_F(configfs, symlink_target_is_an_attribute)\n+{\n+\tASSERT_EQ(mkdir(LINK_SRC, 0755), 0);\n+\n+\t/* The target is resolved with LOOKUP_DIRECTORY. */\n+\tASSERT_EQ(symlink(CHILDLESS \"/storeme\", LINK), -1);\n+\tEXPECT_EQ(errno, ENOTDIR);\n+}\n+\n+static volatile int race_stop;\n+\n+static void *rmdir_target(void *arg)\n+{\n+\twhile (!race_stop) {\n+\t\tif (mkdir(ITEM_A, 0755) == 0 || errno == EEXIST)\n+\t\t\trmdir(ITEM_A);\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+/* -1 if the kernel does not export a warning counter. */\n+static long warn_count(void)\n+{\n+\tchar buf[32];\n+\n+\tif (read_attr(\"/sys/kernel/warn_count\", buf, sizeof(buf)) \u003c 0)\n+\t\treturn -1;\n+\n+\treturn strtol(buf, NULL, 10);\n+}\n+\n+TEST_F(configfs, symlink_races_with_target_rmdir)\n+{\n+\tpthread_t thread;\n+\tlong warns;\n+\tint i;\n+\n+\twarns = warn_count();\n+\tif (warns \u003c 0)\n+\t\tSKIP(return, \"no /sys/kernel/warn_count to watch\");\n+\n+\tASSERT_EQ(mkdir(LINK_SRC, 0755), 0);\n+\tASSERT_EQ(pthread_create(\u0026thread, NULL, rmdir_target, NULL), 0);\n+\n+\t/*\n+\t * configfs_rmdir() drops the last reference to the item while its\n+\t * dentry is still hashed, and get_target() takes a hashed dentry as\n+\t * proof that the item behind it is alive.  The symlink then walks\n+\t * -\u003eci_dentry into a released dirent, which configfs_get() warns\n+\t * about.  KASAN sees the freed item itself.\n+\t */\n+\tfor (i = 0; i \u003c RACE_ITERATIONS; i++) {\n+\t\tif (symlink(ITEM_A, LINK) == 0)\n+\t\t\tunlink(LINK);\n+\n+\t\t/* Give up on the first splat rather than flood the log. */\n+\t\tif (!(i % 128) \u0026\u0026 warn_count() != warns)\n+\t\t\tbreak;\n+\t}\n+\n+\trace_stop = 1;\n+\tASSERT_EQ(pthread_join(thread, NULL), 0);\n+\n+\tEXPECT_EQ(warn_count(), warns)\n+\t\tTH_LOG(\"kernel warned after %d iterations\", i);\n+}\n+\n+TEST_F(configfs, module_pinned_by_item)\n+{\n+\tASSERT_EQ(mkdir(ITEM_A, 0755), 0);\n+\n+\t/* mkdir() pins both the subsystem's module and the new item's. */\n+\tASSERT_EQ(syscall(__NR_delete_module, SAMPLE_MODULE, O_NONBLOCK), -1);\n+\tif (errno == ENOSYS)\n+\t\tSKIP(return, \"kernel built without CONFIG_MODULE_UNLOAD\");\n+\tEXPECT_EQ(errno, EWOULDBLOCK);\n+}\n+\n+TEST_HARNESS_MAIN\n"
}

2/1 2026/08/26 11:04 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only adds a new selftest for configfs and modifies the `configfs_sample` module in `samples/` to support symlinks for the test. It does not modify any core kernel logic, real drivers, or architectures. Therefore, it does not require fuzzing.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 20bc6afa352de6047a2d718aef3f5917535b6019
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Aug 26 11:04:28 2026 +0000

    syz-cluster: applied patch under review

diff --git a/MAINTAINERS b/MAINTAINERS
index fe10d29b7657b..69aea9931b774 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6604,6 +6604,7 @@ S:	Supported
 F:	fs/configfs/
 F:	include/linux/configfs.h
 F:	samples/configfs/
+F:	tools/testing/selftests/filesystems/configfs/
 
 CONFIGFS [RUST]
 M:	Andreas Hindborg <a.hindborg@kernel.org>
diff --git a/samples/configfs/configfs_sample.c b/samples/configfs/configfs_sample.c
index c1b108ec4ea02..08403e787aef6 100644
--- a/samples/configfs/configfs_sample.c
+++ b/samples/configfs/configfs_sample.c
@@ -313,6 +313,126 @@ static struct configfs_subsystem group_children_subsys = {
 
 /* ----------------------------------------------------------------- */
 
+/*
+ * 04-symlink-children
+ *
+ * This example has children that are valid sources for symlink(2).  A
+ * child accepts a link to any other config_item and reports how many
+ * links it currently holds, so ->allow_link() and ->drop_link() are
+ * observable from userspace.
+ */
+
+struct symlink_child {
+	struct config_item item;
+	int nlinks;
+};
+
+static inline struct symlink_child *to_symlink_child(struct config_item *item)
+{
+	return container_of(item, struct symlink_child, item);
+}
+
+static ssize_t symlink_child_nlinks_show(struct config_item *item, char *page)
+{
+	return sprintf(page, "%d\n", to_symlink_child(item)->nlinks);
+}
+
+CONFIGFS_ATTR_RO(symlink_child_, nlinks);
+
+static struct configfs_attribute *symlink_child_attrs[] = {
+	&symlink_child_attr_nlinks,
+	NULL,
+};
+
+/*
+ * The VFS holds the source item's directory locked across symlink(2) and
+ * unlink(2), so ->nlinks needs no lock of its own.
+ */
+static int symlink_child_allow_link(struct config_item *src,
+		struct config_item *target)
+{
+	to_symlink_child(src)->nlinks++;
+
+	return 0;
+}
+
+static void symlink_child_drop_link(struct config_item *src,
+		struct config_item *target)
+{
+	to_symlink_child(src)->nlinks--;
+}
+
+static void symlink_child_release(struct config_item *item)
+{
+	kfree(to_symlink_child(item));
+}
+
+static const struct configfs_item_operations symlink_child_item_ops = {
+	.release	= symlink_child_release,
+	.allow_link	= symlink_child_allow_link,
+	.drop_link	= symlink_child_drop_link,
+};
+
+static const struct config_item_type symlink_child_type = {
+	.ct_item_ops	= &symlink_child_item_ops,
+	.ct_attrs	= symlink_child_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_item *symlink_children_make_item(
+		struct config_group *group, const char *name)
+{
+	struct symlink_child *symlink_child;
+
+	symlink_child = kzalloc_obj(*symlink_child, GFP_KERNEL);
+	if (!symlink_child)
+		return ERR_PTR(-ENOMEM);
+
+	config_item_init_type_name(&symlink_child->item, name,
+				   &symlink_child_type);
+
+	return &symlink_child->item;
+}
+
+static ssize_t symlink_children_description_show(struct config_item *item,
+		char *page)
+{
+	return sprintf(page,
+"[04-symlink-children]\n"
+"\n"
+"This subsystem allows the creation of child config_items that\n"
+"symlink(2) can point at other config_items from.  Each child\n"
+"reports the number of links it holds.\n");
+}
+
+CONFIGFS_ATTR_RO(symlink_children_, description);
+
+static struct configfs_attribute *symlink_children_attrs[] = {
+	&symlink_children_attr_description,
+	NULL,
+};
+
+static const struct configfs_group_operations symlink_children_group_ops = {
+	.make_item	= symlink_children_make_item,
+};
+
+static const struct config_item_type symlink_children_type = {
+	.ct_group_ops	= &symlink_children_group_ops,
+	.ct_attrs	= symlink_children_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct configfs_subsystem symlink_children_subsys = {
+	.su_group = {
+		.cg_item = {
+			.ci_namebuf = "04-symlink-children",
+			.ci_type = &symlink_children_type,
+		},
+	},
+};
+
+/* ----------------------------------------------------------------- */
+
 /*
  * We're now done with our subsystem definitions.
  * For convenience in this module, here's a list of them all.  It
@@ -324,6 +444,7 @@ static struct configfs_subsystem *example_subsys[] = {
 	&childless_subsys.subsys,
 	&simple_children_subsys,
 	&group_children_subsys,
+	&symlink_children_subsys,
 	NULL,
 };
 
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 2d960626750e3..b328a235f1295 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -34,6 +34,7 @@ TARGETS += exec
 TARGETS += fchmodat2
 TARGETS += filesystems
 TARGETS += filesystems/binderfs
+TARGETS += filesystems/configfs
 TARGETS += filesystems/epoll
 TARGETS += filesystems/failfs
 TARGETS += filesystems/fat
diff --git a/tools/testing/selftests/filesystems/configfs/.gitignore b/tools/testing/selftests/filesystems/configfs/.gitignore
new file mode 100644
index 0000000000000..accfb6bb48268
--- /dev/null
+++ b/tools/testing/selftests/filesystems/configfs/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+configfs_test
diff --git a/tools/testing/selftests/filesystems/configfs/Makefile b/tools/testing/selftests/filesystems/configfs/Makefile
new file mode 100644
index 0000000000000..40a91ed788ed2
--- /dev/null
+++ b/tools/testing/selftests/filesystems/configfs/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Meta Platforms, Inc. and affiliates
+# Copyright (c) 2026 Breno Leitao <leitao@debian.org>
+
+CFLAGS += -Wall -Werror -pthread
+TEST_GEN_PROGS := configfs_test
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/filesystems/configfs/config b/tools/testing/selftests/filesystems/configfs/config
new file mode 100644
index 0000000000000..5ea17df535b3f
--- /dev/null
+++ b/tools/testing/selftests/filesystems/configfs/config
@@ -0,0 +1,5 @@
+CONFIG_CONFIGFS_FS=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SAMPLES=y
+CONFIG_SAMPLE_CONFIGFS=m
diff --git a/tools/testing/selftests/filesystems/configfs/configfs_test.c b/tools/testing/selftests/filesystems/configfs/configfs_test.c
new file mode 100644
index 0000000000000..c6a1049e5852e
--- /dev/null
+++ b/tools/testing/selftests/filesystems/configfs/configfs_test.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Exercise the configfs userspace interface through the subsystems
+ * registered by samples/configfs.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates
+ * Copyright (c) 2026 Breno Leitao <leitao@debian.org>
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pthread.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include "kselftest_harness.h"
+
+/* Private to fs/configfs/mount.c. */
+#define CONFIGFS_MAGIC		0x62656570
+
+#define SAMPLE_MODULE		"configfs_sample"
+
+#define CHILDLESS		"01-childless"
+#define SIMPLE			"02-simple-children"
+#define GROUPS			"03-group-children"
+#define SYMLINKS		"04-symlink-children"
+
+#define ITEM_A			SIMPLE "/kselftest-a"
+#define ITEM_B			SIMPLE "/kselftest-b"
+#define GROUP			GROUPS "/kselftest-group"
+#define GROUP_ITEM		GROUP "/kselftest-a"
+#define LINK_SRC		SYMLINKS "/kselftest-src"
+#define LINK			LINK_SRC "/kselftest-link"
+
+#define RACE_ITERATIONS		20000
+
+static const char * const test_links[] = {
+	LINK,
+};
+
+/* Deepest first, so one pass empties the tree. */
+static const char * const test_dirs[] = {
+	GROUP_ITEM,
+	GROUP,
+	LINK_SRC,
+	ITEM_A,
+	ITEM_B,
+};
+
+static void drop_test_dirs(void)
+{
+	size_t i;
+
+	/* Links first: they hold both their source and their target. */
+	for (i = 0; i < ARRAY_SIZE(test_links); i++)
+		unlink(test_links[i]);
+
+	for (i = 0; i < ARRAY_SIZE(test_dirs); i++)
+		rmdir(test_dirs[i]);
+}
+
+static ssize_t read_attr(const char *path, char *buf, size_t len)
+{
+	ssize_t ret;
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -1;
+
+	ret = read(fd, buf, len - 1);
+	close(fd);
+	if (ret < 0)
+		return -1;
+
+	buf[ret] = '\0';
+	return ret;
+}
+
+static ssize_t write_attr(const char *path, const char *val)
+{
+	ssize_t ret;
+	int fd, err;
+
+	fd = open(path, O_WRONLY);
+	if (fd < 0)
+		return -1;
+
+	ret = write(fd, val, strlen(val));
+	err = errno;
+	close(fd);
+	errno = err;
+
+	return ret;
+}
+
+FIXTURE(configfs) {
+	char mnt[sizeof(P_tmpdir "/configfs_XXXXXX")];
+	bool mounted;
+};
+
+FIXTURE_SETUP(configfs)
+{
+	char tmpl[] = P_tmpdir "/configfs_XXXXXX";
+
+	if (geteuid())
+		SKIP(return, "need root to load modules and mount configfs");
+
+	ASSERT_EQ(system("modprobe -q " SAMPLE_MODULE), 0)
+		TH_LOG(SAMPLE_MODULE " missing, is CONFIG_SAMPLE_CONFIGFS=m?");
+
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+
+	ASSERT_NE(mkdtemp(tmpl), NULL);
+	strcpy(self->mnt, tmpl);
+
+	ASSERT_EQ(mount("configfs", self->mnt, "configfs", 0, NULL), 0);
+	ASSERT_EQ(chdir(self->mnt), 0);
+	self->mounted = true;
+
+	/* configfs items outlive the mount, so a killed run leaves some. */
+	drop_test_dirs();
+}
+
+FIXTURE_TEARDOWN(configfs)
+{
+	if (self->mounted) {
+		drop_test_dirs();
+		EXPECT_EQ(chdir("/"), 0);
+		EXPECT_EQ(umount2(self->mnt, MNT_DETACH), 0);
+	}
+
+	if (self->mnt[0])
+		EXPECT_EQ(rmdir(self->mnt), 0);
+}
+
+TEST_F(configfs, mount_and_subsystems)
+{
+	const char * const subsys[] = { CHILDLESS, SIMPLE, GROUPS, SYMLINKS };
+	struct statfs sfs;
+	struct stat st;
+	size_t i;
+
+	ASSERT_EQ(statfs(".", &sfs), 0);
+	EXPECT_EQ(sfs.f_type, CONFIGFS_MAGIC);
+
+	for (i = 0; i < ARRAY_SIZE(subsys); i++) {
+		ASSERT_EQ(stat(subsys[i], &st), 0)
+			TH_LOG("%s is missing", subsys[i]);
+		EXPECT_TRUE(S_ISDIR(st.st_mode));
+	}
+}
+
+TEST_F(configfs, mkdir_at_root)
+{
+	/* The root has no ->mkdir(); only subsystems register there. */
+	ASSERT_EQ(mkdir("kselftest-root", 0755), -1);
+	EXPECT_EQ(errno, EPERM);
+}
+
+TEST_F(configfs, rmdir_subsystem)
+{
+	ASSERT_EQ(rmdir(CHILDLESS), -1);
+	EXPECT_EQ(errno, EPERM);
+}
+
+TEST_F(configfs, mkdir_without_group_ops)
+{
+	/* 01-childless has attributes but no ->make_item()/->make_group(). */
+	ASSERT_EQ(mkdir(CHILDLESS "/kselftest-a", 0755), -1);
+	EXPECT_EQ(errno, EPERM);
+}
+
+TEST_F(configfs, attr_store_and_show)
+{
+	char buf[64];
+
+	ASSERT_GT(write_attr(CHILDLESS "/storeme", "42"), 0);
+	ASSERT_GT(read_attr(CHILDLESS "/storeme", buf, sizeof(buf)), 0);
+	EXPECT_STREQ(buf, "42\n");
+}
+
+TEST_F(configfs, attr_store_rejects_garbage)
+{
+	ASSERT_EQ(write_attr(CHILDLESS "/storeme", "not-a-number"), -1);
+	EXPECT_EQ(errno, EINVAL);
+}
+
+TEST_F(configfs, attr_show_runs_on_every_open)
+{
+	char first[64], second[64];
+
+	/* 01-childless/showme increments the value it just returned. */
+	ASSERT_GT(read_attr(CHILDLESS "/showme", first, sizeof(first)), 0);
+	ASSERT_GT(read_attr(CHILDLESS "/showme", second, sizeof(second)), 0);
+	EXPECT_EQ(atoi(second), atoi(first) + 1);
+}
+
+TEST_F(configfs, attr_read_only)
+{
+	ASSERT_EQ(open(CHILDLESS "/description", O_WRONLY), -1);
+	EXPECT_EQ(errno, EACCES);
+}
+
+TEST_F(configfs, attr_unlink)
+{
+	/* ->unlink() only accepts the symlinks configfs itself created. */
+	ASSERT_EQ(unlink(CHILDLESS "/storeme"), -1);
+	EXPECT_EQ(errno, EPERM);
+}
+
+TEST_F(configfs, attr_read_length)
+{
+	char buf[8192];
+	struct stat st;
+	ssize_t n;
+	int fd;
+
+	fd = open(CHILDLESS "/description", O_RDONLY);
+	ASSERT_GE(fd, 0);
+
+	/* Attributes report a page, whatever ->show() ends up producing. */
+	ASSERT_EQ(fstat(fd, &st), 0);
+	EXPECT_EQ(st.st_size, sysconf(_SC_PAGESIZE));
+
+	n = read(fd, buf, sizeof(buf));
+	ASSERT_GT(n, 0);
+	EXPECT_LT(n, st.st_size);
+	EXPECT_EQ(read(fd, buf, sizeof(buf)), 0);
+
+	EXPECT_EQ(close(fd), 0);
+}
+
+TEST_F(configfs, attr_write_is_not_incremental)
+{
+	char buf[64];
+	int fd;
+
+	/*
+	 * Every write hands the whole buffer to ->store() and the file
+	 * position is ignored, so the second write replaces the first.
+	 */
+	fd = open(CHILDLESS "/storeme", O_WRONLY);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(write(fd, "1", 1), 1);
+	ASSERT_EQ(write(fd, "2", 1), 1);
+	EXPECT_EQ(close(fd), 0);
+
+	ASSERT_GT(read_attr(CHILDLESS "/storeme", buf, sizeof(buf)), 0);
+	EXPECT_STREQ(buf, "2\n");
+}
+
+TEST_F(configfs, item_create_and_drop)
+{
+	struct stat st;
+
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+	EXPECT_EQ(stat(ITEM_A "/storeme", &st), 0);
+
+	/* The item carries its own attributes, not the subsystem's. */
+	ASSERT_EQ(stat(ITEM_A "/description", &st), -1);
+	EXPECT_EQ(errno, ENOENT);
+
+	ASSERT_EQ(rmdir(ITEM_A), 0);
+	ASSERT_EQ(stat(ITEM_A, &st), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(configfs, item_create_twice)
+{
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+	ASSERT_EQ(mkdir(ITEM_A, 0755), -1);
+	EXPECT_EQ(errno, EEXIST);
+}
+
+TEST_F(configfs, item_has_no_children)
+{
+	/* ->make_item() produces an item, so it cannot nest. */
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+	ASSERT_EQ(mkdir(ITEM_A "/kselftest-b", 0755), -1);
+	EXPECT_EQ(errno, EPERM);
+}
+
+TEST_F(configfs, item_attrs_are_private)
+{
+	char buf[64];
+
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+	ASSERT_EQ(mkdir(ITEM_B, 0755), 0);
+
+	ASSERT_GT(write_attr(ITEM_A "/storeme", "11"), 0);
+	ASSERT_GT(write_attr(ITEM_B "/storeme", "22"), 0);
+
+	ASSERT_GT(read_attr(ITEM_A "/storeme", buf, sizeof(buf)), 0);
+	EXPECT_STREQ(buf, "11\n");
+	ASSERT_GT(read_attr(ITEM_B "/storeme", buf, sizeof(buf)), 0);
+	EXPECT_STREQ(buf, "22\n");
+}
+
+TEST_F(configfs, group_create_and_drop)
+{
+	struct stat st;
+
+	/* 03-group-children hands out groups that take items of their own. */
+	ASSERT_EQ(mkdir(GROUP, 0755), 0);
+	EXPECT_EQ(stat(GROUP "/description", &st), 0);
+
+	ASSERT_EQ(mkdir(GROUP_ITEM, 0755), 0);
+	EXPECT_EQ(stat(GROUP_ITEM "/storeme", &st), 0);
+
+	ASSERT_EQ(rmdir(GROUP), -1);
+	EXPECT_EQ(errno, ENOTEMPTY);
+
+	ASSERT_EQ(rmdir(GROUP_ITEM), 0);
+	ASSERT_EQ(rmdir(GROUP), 0);
+}
+
+TEST_F(configfs, rename_item)
+{
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+	ASSERT_EQ(rename(ITEM_A, ITEM_B), -1);
+	EXPECT_EQ(errno, EPERM);
+}
+
+TEST_F(configfs, symlink_without_allow_link)
+{
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+	ASSERT_EQ(symlink(ITEM_A, SIMPLE "/kselftest-link"), -1);
+	EXPECT_EQ(errno, EPERM);
+}
+
+TEST_F(configfs, symlink_and_unlink)
+{
+	char buf[PATH_MAX];
+	struct stat st;
+	ssize_t n;
+
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+	ASSERT_EQ(mkdir(LINK_SRC, 0755), 0);
+
+	ASSERT_EQ(symlink(ITEM_A, LINK), 0);
+
+	/* configfs stores its own body, a path relative to the link. */
+	n = readlink(LINK, buf, sizeof(buf) - 1);
+	ASSERT_GT(n, 0);
+	buf[n] = '\0';
+	EXPECT_STREQ(buf, "../../" ITEM_A);
+	EXPECT_EQ(stat(LINK "/storeme", &st), 0);
+
+	/* ->allow_link() ran on the source, not on the target. */
+	ASSERT_GT(read_attr(LINK_SRC "/nlinks", buf, sizeof(buf)), 0);
+	EXPECT_STREQ(buf, "1\n");
+
+	ASSERT_EQ(unlink(LINK), 0);
+	ASSERT_GT(read_attr(LINK_SRC "/nlinks", buf, sizeof(buf)), 0);
+	EXPECT_STREQ(buf, "0\n");
+}
+
+TEST_F(configfs, symlink_pins_both_ends)
+{
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+	ASSERT_EQ(mkdir(LINK_SRC, 0755), 0);
+	ASSERT_EQ(symlink(ITEM_A, LINK), 0);
+
+	/* A linked item cannot go away under the link. */
+	ASSERT_EQ(rmdir(ITEM_A), -1);
+	EXPECT_EQ(errno, EBUSY);
+
+	/* The link counts as a child of its source. */
+	ASSERT_EQ(rmdir(LINK_SRC), -1);
+	EXPECT_EQ(errno, ENOTEMPTY);
+
+	ASSERT_EQ(unlink(LINK), 0);
+	EXPECT_EQ(rmdir(ITEM_A), 0);
+}
+
+TEST_F(configfs, symlink_target_outside_configfs)
+{
+	ASSERT_EQ(mkdir(LINK_SRC, 0755), 0);
+	ASSERT_EQ(symlink("/", LINK), -1);
+	EXPECT_EQ(errno, EPERM);
+}
+
+TEST_F(configfs, symlink_target_missing)
+{
+	ASSERT_EQ(mkdir(LINK_SRC, 0755), 0);
+	ASSERT_EQ(symlink(SIMPLE "/kselftest-gone", LINK), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(configfs, symlink_target_is_an_attribute)
+{
+	ASSERT_EQ(mkdir(LINK_SRC, 0755), 0);
+
+	/* The target is resolved with LOOKUP_DIRECTORY. */
+	ASSERT_EQ(symlink(CHILDLESS "/storeme", LINK), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+}
+
+static volatile int race_stop;
+
+static void *rmdir_target(void *arg)
+{
+	while (!race_stop) {
+		if (mkdir(ITEM_A, 0755) == 0 || errno == EEXIST)
+			rmdir(ITEM_A);
+	}
+
+	return NULL;
+}
+
+/* -1 if the kernel does not export a warning counter. */
+static long warn_count(void)
+{
+	char buf[32];
+
+	if (read_attr("/sys/kernel/warn_count", buf, sizeof(buf)) < 0)
+		return -1;
+
+	return strtol(buf, NULL, 10);
+}
+
+TEST_F(configfs, symlink_races_with_target_rmdir)
+{
+	pthread_t thread;
+	long warns;
+	int i;
+
+	warns = warn_count();
+	if (warns < 0)
+		SKIP(return, "no /sys/kernel/warn_count to watch");
+
+	ASSERT_EQ(mkdir(LINK_SRC, 0755), 0);
+	ASSERT_EQ(pthread_create(&thread, NULL, rmdir_target, NULL), 0);
+
+	/*
+	 * configfs_rmdir() drops the last reference to the item while its
+	 * dentry is still hashed, and get_target() takes a hashed dentry as
+	 * proof that the item behind it is alive.  The symlink then walks
+	 * ->ci_dentry into a released dirent, which configfs_get() warns
+	 * about.  KASAN sees the freed item itself.
+	 */
+	for (i = 0; i < RACE_ITERATIONS; i++) {
+		if (symlink(ITEM_A, LINK) == 0)
+			unlink(LINK);
+
+		/* Give up on the first splat rather than flood the log. */
+		if (!(i % 128) && warn_count() != warns)
+			break;
+	}
+
+	race_stop = 1;
+	ASSERT_EQ(pthread_join(thread, NULL), 0);
+
+	EXPECT_EQ(warn_count(), warns)
+		TH_LOG("kernel warned after %d iterations", i);
+}
+
+TEST_F(configfs, module_pinned_by_item)
+{
+	ASSERT_EQ(mkdir(ITEM_A, 0755), 0);
+
+	/* mkdir() pins both the subsystem's module and the new item's. */
+	ASSERT_EQ(syscall(__NR_delete_module, SAMPLE_MODULE, O_NONBLOCK), -1);
+	if (errno == ENOSYS)
+		SKIP(return, "kernel built without CONFIG_MODULE_UNLOAD");
+	EXPECT_EQ(errno, EWOULDBLOCK);
+}
+
+TEST_HARNESS_MAIN

3/2 2026/08/26 11:04 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 9233
							output: 89
							thoughts: 1107
						

Thoughts:
**Fuzzing Decision for ConfigFS Selftest Patch**

Okay, so I'm looking at a patch that introduces a new selftest for `configfs`. My initial thought is to assess if this patch warrants fuzzing, and I have to be very precise. Let's break it down:

First, the patch touches `MAINTAINERS`, the sample module `samples/configfs/configfs_sample.c`, the selftest `tools/testing/selftests/Makefile`, and adds files under `tools/testing/selftests/filesystems/configfs/`. The crucial point is that `samples/configfs/configfs_sample.c` is being modified with a new subsystem, `04-symlink-children`. That raises a flag: is this sample module now critical?

My first instinct is to dismiss this; samples are *samples*. They aren't core kernel logic. However, since the selftest *uses* this sample, I need to be sure. I have to consider the prompt's rules: I need to determine if it modifies *reachable core kernel logic, drivers, or architectures*.

I need to confirm if this code path is exposed. The prompt states that if the patch only contains modifications to Documentation, Kconfig files, code comments, decorative changes, changes to numeric constants or macros that do not functionally alter execution flow, unreachable code, or teardown callbacks, it doesn't warrant fuzzing.

My analysis confirms that the primary code modifications are within a sample module, specifically, `configfs_sample.c` and selftests. The sample module gets compiled as a module (`CONFIG_SAMPLE_CONFIGFS`). The patch defines new sample configfs callbacks, such as `symlink_children_make_item`, etc. These are *sample* implementations.

I should check if `configfs_sample` is loaded by syzkaller. If it is, then the module may be loaded. However, my understanding is that syzkaller *doesn't* load sample modules unless explicitly configured to do so. I'll need to confirm that. Importantly, it doesn't look like this patch alters the *core* `configfs` logic in `fs/configfs/`. All the functional changes are contained within the sample.

Now, does a selftest *using* a sample warrant fuzzing? In the vast majority of cases, no. Selftests are userspace programs, not kernel code. They shouldn't be the focus. The prompt says that sample modules aren't core.

I'm confident that the core kernel is not directly modified in a way that creates a new attack surface. I will set WorthFuzzing=false.

My reasoning? The patch adds a selftest and modifies a sample module. It does *not* modify core kernel logic, real drivers, or architectures. The sample module is used to enable the new test. Therefore, no fuzzing is required.



4/2 2026/08/26 11:04 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch only adds a new selftest for configfs and modifies the `configfs_sample` module in `samples/` to support symlinks for the test. It does not modify any core kernel logic, real drivers, or architectures. Therefore, it does not require fuzzing.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only adds a new selftest for configfs and modifies the `configfs_sample` module in `samples/` to support symlinks for the test. It does not modify any core kernel logic, real drivers, or architectures. Therefore, it does not require fuzzing.",
  "WorthFuzzing": false
}

5/1 2026/08/26 11:04 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)