| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/14 03:13 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"FS_POSIX_ACL",
"FUSE_FS"
],
"FocusSymbols": [
"forget_all_cached_acls",
"forget_cached_acl"
],
"Reasoning": "This patch fixes a bug in `__forget_cached_acl` where `ACL_DONT_CACHE` could be incorrectly replaced with `ACL_NOT_CACHED`, inadvertently enabling ACL caching for inodes that shouldn't have it (e.g., FUSE mounts without `FUSE_POSIX_ACL`). This is a functional change in core VFS/ACL logic. Fuzzing should focus on `forget_all_cached_acls` and `forget_cached_acl` to ensure ACL caching state transitions are handled correctly.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/14 03:13 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 75761cd55d82760ed8a8f7f588e9726cfabf5a0c\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Jul 14 03:13:29 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/fs/posix_acl.c b/fs/posix_acl.c\nindex b4bfe4ddf64ea5..3dc62c1c27087a 100644\n--- a/fs/posix_acl.c\n+++ b/fs/posix_acl.c\n@@ -93,6 +93,13 @@ static void __forget_cached_acl(struct posix_acl **p)\n {\n \tstruct posix_acl *old;\n \n+\t/*\n+\t * ACL_DONT_CACHE is expected to be a \"const\" value and xchg it with\n+\t * ACL_NOT_CACHED would enable acl caching for the inode -\n+\t * clearly not what the caller has intended.\n+\t */\n+\tif (READ_ONCE(*p) == ACL_DONT_CACHE)\n+\t\treturn;\n \told = xchg(p, ACL_NOT_CACHED);\n \tif (!is_uncached_acl(old))\n \t\tposix_acl_release(old);\ndiff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile\nindex 612aad69a93aa4..f471414842750a 100644\n--- a/tools/testing/selftests/filesystems/fuse/Makefile\n+++ b/tools/testing/selftests/filesystems/fuse/Makefile\n@@ -5,6 +5,13 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)\n TEST_GEN_PROGS := fusectl_test\n TEST_GEN_FILES := fuse_mnt\n \n+# fuse_acl_cache_test requires libfuse3; add it only when the library is present.\n+ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2\u003e/dev/null)\n+ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2\u003e/dev/null)\n+ifneq ($(ACL_CFLAGS),)\n+TEST_GEN_PROGS += fuse_acl_cache_test\n+endif\n+\n include ../../lib.mk\n \n VAR_CFLAGS := $(shell pkg-config fuse --cflags 2\u003e/dev/null)\n@@ -19,3 +26,6 @@ endif\n \n $(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS)\n $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS)\n+\n+$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS)\n+$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS)\ndiff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c\nnew file mode 100644\nindex 00000000000000..b2e5db85550409\n--- /dev/null\n+++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c\n@@ -0,0 +1,348 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC\n+ *\n+ * A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode\n+ * with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed\n+ * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls\n+ * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel,\n+ * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED,\n+ * inadvertently enabling the kernel ACL cache for that inode. The next\n+ * getxattr populates the cache. Because fuse_set_acl() skips\n+ * forget_all_cached_acls() for !fc-\u003eposix_acl mounts, any subsequent change to\n+ * the ACL leaves the stale kernel entry in place, and the next getxattr returns\n+ * wrong data without ever reaching the FUSE daemon.\n+ *\n+ * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is\n+ * ACL_DONT_CACHE, preserving the \"never cache\" invariant for the inode's\n+ * lifetime.\n+ *\n+ * Test outline:\n+ * 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated).\n+ * 2. lgetxattr -\u003e daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE).\n+ * 3. statx(AT_STATX_FORCE_SYNC) -\u003e forget_all_cached_acls() called.\n+ * Buggy: ACL_DONT_CACHE -\u003e ACL_NOT_CACHED (cache enabled).\n+ * Fixed: ACL_DONT_CACHE preserved.\n+ * 4. lgetxattr -\u003e daemon called, ACL_A returned.\n+ * Buggy: result now cached (ACL_NOT_CACHED -\u003e cached).\n+ * Fixed: result still not cached.\n+ * 5. Daemon switches to ACL_B internally (different size).\n+ * 6. lgetxattr -\u003e should return ACL_B (44 bytes).\n+ * Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL.\n+ * Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS.\n+ */\n+\n+#define _GNU_SOURCE\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003clinux/limits.h\u003e\n+#include \u003cpthread.h\u003e\n+#include \u003cstdint.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/syscall.h\u003e\n+#include \u003csys/xattr.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#define FUSE_USE_VERSION 31\n+#include \u003cfuse_lowlevel.h\u003e\n+\n+#include \"kselftest_harness.h\"\n+\n+/* ---- ACL binary encoding ------------------------------------------------ */\n+/*\n+ * POSIX ACL v2 xattr format (little-endian):\n+ * header: u32 version (= 0x00000002)\n+ * entry: u16 tag | u16 perm | u32 id\n+ *\n+ * Entries must appear in tag-ascending order; named USER/GROUP entries\n+ * require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation.\n+ */\n+\n+/* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */\n+static const uint8_t acl_a[] = {\n+\t0x02, 0x00, 0x00, 0x00, /* v2 header */\n+\t0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */\n+\t0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */\n+\t0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */\n+};\n+\n+/*\n+ * ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes.\n+ * A named USER entry requires a MASK; all tags in ascending order.\n+ */\n+static const uint8_t acl_b[] = {\n+\t0x02, 0x00, 0x00, 0x00, /* v2 header */\n+\t0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */\n+\t0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */\n+\t0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */\n+\t0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */\n+\t0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */\n+};\n+\n+/* ---- Shared state (daemon thread \u003c-\u003e test thread) ----------------------- */\n+\n+#define FILE_INO 2\n+#define FILE_NAME \"testfile\"\n+\n+struct daemon_state {\n+\tpthread_mutex_t lock;\n+\tconst uint8_t *acl;\n+\tsize_t acl_size;\n+\tint getxattr_count;\n+};\n+\n+/*\n+ * Global: callbacks are stateless fns so we use a single global.\n+ * Safe because only one test instance runs at a time.\n+ */\n+static struct daemon_state g_ds = {\n+\t.lock = PTHREAD_MUTEX_INITIALIZER,\n+};\n+\n+/* ---- FUSE lowlevel callbacks -------------------------------------------- */\n+\n+static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)\n+{\n+\tif (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) {\n+\t\tfuse_reply_err(req, ENOENT);\n+\t\treturn;\n+\t}\n+\tstruct fuse_entry_param e = {};\n+\n+\t/*\n+\t * Long attr/entry timeouts so that normal stat() calls do not\n+\t * expire and trigger forget_all_cached_acls() on their own;\n+\t * only the explicit AT_STATX_FORCE_SYNC should trigger it.\n+\t */\n+\te.ino = FILE_INO;\n+\te.generation = 1;\n+\te.attr_timeout = 10.0;\n+\te.entry_timeout = 10.0;\n+\te.attr.st_ino = FILE_INO;\n+\te.attr.st_mode = S_IFREG | 0644;\n+\te.attr.st_nlink = 1;\n+\tfuse_reply_entry(req, \u0026e);\n+}\n+\n+static void fs_getattr(fuse_req_t req, fuse_ino_t ino,\n+\t\t struct fuse_file_info *fi)\n+{\n+\tstruct stat st = {};\n+\n+\t(void)fi;\n+\tif (ino == FUSE_ROOT_ID) {\n+\t\tst.st_ino = FUSE_ROOT_ID;\n+\t\tst.st_mode = S_IFDIR | 0755;\n+\t\tst.st_nlink = 2;\n+\t} else if (ino == FILE_INO) {\n+\t\tst.st_ino = FILE_INO;\n+\t\tst.st_mode = S_IFREG | 0644;\n+\t\tst.st_nlink = 1;\n+\t} else {\n+\t\tfuse_reply_err(req, ENOENT);\n+\t\treturn;\n+\t}\n+\tfuse_reply_attr(req, \u0026st, 10);\n+}\n+\n+static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,\n+\t\t\tsize_t size)\n+{\n+\tif (ino != FILE_INO ||\n+\t strcmp(name, \"system.posix_acl_access\") != 0) {\n+\t\tfuse_reply_err(req, ENODATA);\n+\t\treturn;\n+\t}\n+\n+\tpthread_mutex_lock(\u0026g_ds.lock);\n+\tconst uint8_t *acl = g_ds.acl;\n+\tsize_t acl_size = g_ds.acl_size;\n+\tg_ds.getxattr_count++;\n+\tpthread_mutex_unlock(\u0026g_ds.lock);\n+\n+\tif (size == 0)\n+\t\tfuse_reply_xattr(req, acl_size);\n+\telse if (size \u003c acl_size)\n+\t\tfuse_reply_err(req, ERANGE);\n+\telse\n+\t\tfuse_reply_buf(req, (const char *)acl, acl_size);\n+}\n+\n+static const struct fuse_lowlevel_ops fs_ops = {\n+\t.lookup = fs_lookup,\n+\t.getattr = fs_getattr,\n+\t.getxattr = fs_getxattr,\n+};\n+\n+/* ---- Daemon thread ------------------------------------------------------- */\n+\n+static void *run_daemon(void *arg)\n+{\n+\tfuse_session_loop((struct fuse_session *)arg);\n+\treturn NULL;\n+}\n+\n+/* ---- kselftest harness --------------------------------------------------- */\n+\n+FIXTURE(acl_cache) {\n+\tstruct fuse_session *se;\n+\tchar mountpoint[PATH_MAX];\n+\tchar file_path[PATH_MAX];\n+\tpthread_t thread;\n+};\n+\n+FIXTURE_SETUP(acl_cache)\n+{\n+\tchar *fuse_argv[] = { \"fuse_acl_cache_test\", NULL };\n+\tstruct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);\n+\n+\tg_ds.acl = acl_a;\n+\tg_ds.acl_size = sizeof(acl_a);\n+\tg_ds.getxattr_count = 0;\n+\n+\tstrcpy(self-\u003emountpoint, \"/tmp/acl_cache_test_XXXXXX\");\n+\tif (!mkdtemp(self-\u003emountpoint))\n+\t\tSKIP(return, \"mkdtemp: %s\", strerror(errno));\n+\n+\tsnprintf(self-\u003efile_path, sizeof(self-\u003efile_path),\n+\t\t \"%s/\" FILE_NAME, self-\u003emountpoint);\n+\n+\tself-\u003ese = fuse_session_new(\u0026args, \u0026fs_ops, sizeof(fs_ops), NULL);\n+\tif (!self-\u003ese) {\n+\t\trmdir(self-\u003emountpoint);\n+\t\tSKIP(return, \"fuse_session_new failed\");\n+\t}\n+\n+\tif (fuse_session_mount(self-\u003ese, self-\u003emountpoint)) {\n+\t\tfuse_session_destroy(self-\u003ese);\n+\t\trmdir(self-\u003emountpoint);\n+\t\tSKIP(return, \"fuse_session_mount failed \"\n+\t\t\t \"(missing fusermount3 or insufficient privileges)\");\n+\t}\n+\n+\tif (pthread_create(\u0026self-\u003ethread, NULL, run_daemon, self-\u003ese)) {\n+\t\tfuse_session_unmount(self-\u003ese);\n+\t\tfuse_session_destroy(self-\u003ese);\n+\t\trmdir(self-\u003emountpoint);\n+\t\tSKIP(return, \"pthread_create: %s\", strerror(errno));\n+\t}\n+\n+\tfuse_opt_free_args(\u0026args);\n+}\n+\n+FIXTURE_TEARDOWN(acl_cache)\n+{\n+\tfuse_session_exit(self-\u003ese);\n+\tfuse_session_unmount(self-\u003ese);\n+\tpthread_join(self-\u003ethread, NULL);\n+\tfuse_session_destroy(self-\u003ese);\n+\trmdir(self-\u003emountpoint);\n+}\n+\n+static int do_force_statx(const char *path)\n+{\n+\tstruct statx stx;\n+\n+\treturn syscall(SYS_statx, AT_FDCWD, path,\n+\t\t AT_STATX_FORCE_SYNC, STATX_BASIC_STATS, \u0026stx);\n+}\n+\n+TEST_F(acl_cache, stale_after_force_sync)\n+{\n+\tchar buf[512];\n+\tssize_t sz;\n+\tint count;\n+\n+\t/*\n+\t * Step 1: two getxattr calls before any statx(FORCE_SYNC).\n+\t * i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED,\n+\t * sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel\n+\t * is never placed and the result is never cached. Both calls must\n+\t * reach the daemon, proving ACL_DONT_CACHE suppresses caching.\n+\t */\n+\tsz = lgetxattr(self-\u003efile_path, \"system.posix_acl_access\",\n+\t\t buf, sizeof(buf));\n+\tASSERT_EQ(sz, (ssize_t)sizeof(acl_a));\n+\n+\tsz = lgetxattr(self-\u003efile_path, \"system.posix_acl_access\",\n+\t\t buf, sizeof(buf));\n+\tASSERT_EQ(sz, (ssize_t)sizeof(acl_a));\n+\n+\tpthread_mutex_lock(\u0026g_ds.lock);\n+\tcount = g_ds.getxattr_count;\n+\tpthread_mutex_unlock(\u0026g_ds.lock);\n+\n+\tASSERT_EQ(count, 2);\n+\tTH_LOG(\"step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), \"\n+\t \"ACL_DONT_CACHE is working\", count);\n+\n+\t/*\n+\t * Step 2: statx(AT_STATX_FORCE_SYNC).\n+\t * fuse_update_get_attr() calls forget_all_cached_acls() before sending\n+\t * FUSE_GETATTR.\n+\t * Buggy kernel: ACL_DONT_CACHE -\u003e ACL_NOT_CACHED (cache enabled)\n+\t * Fixed kernel: ACL_DONT_CACHE preserved (no effect)\n+\t */\n+\tASSERT_EQ(do_force_statx(self-\u003efile_path), 0);\n+\tTH_LOG(\"step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded\");\n+\n+\t/*\n+\t * Step 3: getxattr — cache population attempt after the trigger.\n+\t * Buggy: *p == ACL_NOT_CACHED -\u003e sentinel placed -\u003e fuse_get_inode_acl\n+\t * called -\u003e ACL_A parsed and stored in the kernel cache.\n+\t * Fixed: *p == ACL_DONT_CACHE -\u003e sentinel placement skipped -\u003e\n+\t * fuse_get_inode_acl called but result not cached.\n+\t * Either way the correct ACL_A is returned here.\n+\t */\n+\tsz = lgetxattr(self-\u003efile_path, \"system.posix_acl_access\",\n+\t\t buf, sizeof(buf));\n+\tASSERT_EQ(sz, (ssize_t)sizeof(acl_a));\n+\n+\tpthread_mutex_lock(\u0026g_ds.lock);\n+\tcount = g_ds.getxattr_count;\n+\tpthread_mutex_unlock(\u0026g_ds.lock);\n+\n+\tASSERT_EQ(count, 3);\n+\tTH_LOG(\"step 3 OK: post-trigger getxattr reached daemon (count=%d), \"\n+\t \"returned correct ACL_A (%zd bytes)\", count, sz);\n+\n+\t/*\n+\t * Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes).\n+\t * Simulates an ACL change that fuse_set_acl() would NOT invalidate for\n+\t * !fc-\u003eposix_acl mounts (it skips forget_all_cached_acls in that case).\n+\t * On a fixed kernel the ACL was never cached, so this is moot.\n+\t */\n+\tpthread_mutex_lock(\u0026g_ds.lock);\n+\tg_ds.acl = acl_b;\n+\tg_ds.acl_size = sizeof(acl_b);\n+\tpthread_mutex_unlock(\u0026g_ds.lock);\n+\tTH_LOG(\"step 4: daemon switched to ACL_B (%zu bytes)\", sizeof(acl_b));\n+\n+\t/*\n+\t * Step 5: getxattr — the decisive check.\n+\t * Buggy kernel: cache hit -\u003e stale ACL_A (28 bytes), count stays 3.\n+\t * Fixed kernel: no cache -\u003e daemon called -\u003e ACL_B (44 bytes), count 4.\n+\t */\n+\tsz = lgetxattr(self-\u003efile_path, \"system.posix_acl_access\",\n+\t\t buf, sizeof(buf));\n+\n+\tpthread_mutex_lock(\u0026g_ds.lock);\n+\tcount = g_ds.getxattr_count;\n+\tpthread_mutex_unlock(\u0026g_ds.lock);\n+\n+\tif (sz == (ssize_t)sizeof(acl_a))\n+\t\tTH_LOG(\"step 5 BUG: stale ACL_A (%zd bytes) from kernel cache \"\n+\t\t \"(count=%d); ACL_DONT_CACHE corrupted by \"\n+\t\t \"forget_all_cached_acls()\", sz, count);\n+\telse\n+\t\tTH_LOG(\"step 5 OK: daemon reached (count=%d), \"\n+\t\t \"fresh ACL_B (%zd bytes)\", count, sz);\n+\n+\tEXPECT_EQ(sz, (ssize_t)sizeof(acl_b));\n+\tEXPECT_EQ(count, 4);\n+}\n+\n+TEST_HARNESS_MAIN\n"
} |
| 2/1 | 2026/07/14 03:13 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"FS_POSIX_ACL",
"FUSE_FS"
],
"FocusSymbols": [
"forget_all_cached_acls",
"forget_cached_acl"
],
"Reasoning": "This patch fixes a bug in `__forget_cached_acl` where `ACL_DONT_CACHE` could be incorrectly replaced with `ACL_NOT_CACHED`, inadvertently enabling ACL caching for inodes that shouldn't have it (e.g., FUSE mounts without `FUSE_POSIX_ACL`). This is a functional change in core VFS/ACL logic. Fuzzing should focus on `forget_all_cached_acls` and `forget_cached_acl` to ensure ACL caching state transitions are handled correctly.",
"WorthFuzzing": true
}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). 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. 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. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. 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 75761cd55d82760ed8a8f7f588e9726cfabf5a0c
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Jul 14 03:13:29 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index b4bfe4ddf64ea5..3dc62c1c27087a 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -93,6 +93,13 @@ static void __forget_cached_acl(struct posix_acl **p)
{
struct posix_acl *old;
+ /*
+ * ACL_DONT_CACHE is expected to be a "const" value and xchg it with
+ * ACL_NOT_CACHED would enable acl caching for the inode -
+ * clearly not what the caller has intended.
+ */
+ if (READ_ONCE(*p) == ACL_DONT_CACHE)
+ return;
old = xchg(p, ACL_NOT_CACHED);
if (!is_uncached_acl(old))
posix_acl_release(old);
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
index 612aad69a93aa4..f471414842750a 100644
--- a/tools/testing/selftests/filesystems/fuse/Makefile
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -5,6 +5,13 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
TEST_GEN_PROGS := fusectl_test
TEST_GEN_FILES := fuse_mnt
+# fuse_acl_cache_test requires libfuse3; add it only when the library is present.
+ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null)
+ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null)
+ifneq ($(ACL_CFLAGS),)
+TEST_GEN_PROGS += fuse_acl_cache_test
+endif
+
include ../../lib.mk
VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null)
@@ -19,3 +26,6 @@ endif
$(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS)
$(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS)
+
+$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS)
+$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS)
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
new file mode 100644
index 00000000000000..b2e5db85550409
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC
+ *
+ * A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode
+ * with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed
+ * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls
+ * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel,
+ * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED,
+ * inadvertently enabling the kernel ACL cache for that inode. The next
+ * getxattr populates the cache. Because fuse_set_acl() skips
+ * forget_all_cached_acls() for !fc->posix_acl mounts, any subsequent change to
+ * the ACL leaves the stale kernel entry in place, and the next getxattr returns
+ * wrong data without ever reaching the FUSE daemon.
+ *
+ * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is
+ * ACL_DONT_CACHE, preserving the "never cache" invariant for the inode's
+ * lifetime.
+ *
+ * Test outline:
+ * 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated).
+ * 2. lgetxattr -> daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE).
+ * 3. statx(AT_STATX_FORCE_SYNC) -> forget_all_cached_acls() called.
+ * Buggy: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled).
+ * Fixed: ACL_DONT_CACHE preserved.
+ * 4. lgetxattr -> daemon called, ACL_A returned.
+ * Buggy: result now cached (ACL_NOT_CACHED -> cached).
+ * Fixed: result still not cached.
+ * 5. Daemon switches to ACL_B internally (different size).
+ * 6. lgetxattr -> should return ACL_B (44 bytes).
+ * Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL.
+ * Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#define FUSE_USE_VERSION 31
+#include <fuse_lowlevel.h>
+
+#include "kselftest_harness.h"
+
+/* ---- ACL binary encoding ------------------------------------------------ */
+/*
+ * POSIX ACL v2 xattr format (little-endian):
+ * header: u32 version (= 0x00000002)
+ * entry: u16 tag | u16 perm | u32 id
+ *
+ * Entries must appear in tag-ascending order; named USER/GROUP entries
+ * require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation.
+ */
+
+/* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */
+static const uint8_t acl_a[] = {
+ 0x02, 0x00, 0x00, 0x00, /* v2 header */
+ 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */
+ 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */
+ 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */
+};
+
+/*
+ * ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes.
+ * A named USER entry requires a MASK; all tags in ascending order.
+ */
+static const uint8_t acl_b[] = {
+ 0x02, 0x00, 0x00, 0x00, /* v2 header */
+ 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */
+ 0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */
+ 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */
+ 0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */
+ 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */
+};
+
+/* ---- Shared state (daemon thread <-> test thread) ----------------------- */
+
+#define FILE_INO 2
+#define FILE_NAME "testfile"
+
+struct daemon_state {
+ pthread_mutex_t lock;
+ const uint8_t *acl;
+ size_t acl_size;
+ int getxattr_count;
+};
+
+/*
+ * Global: callbacks are stateless fns so we use a single global.
+ * Safe because only one test instance runs at a time.
+ */
+static struct daemon_state g_ds = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+/* ---- FUSE lowlevel callbacks -------------------------------------------- */
+
+static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+ if (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) {
+ fuse_reply_err(req, ENOENT);
+ return;
+ }
+ struct fuse_entry_param e = {};
+
+ /*
+ * Long attr/entry timeouts so that normal stat() calls do not
+ * expire and trigger forget_all_cached_acls() on their own;
+ * only the explicit AT_STATX_FORCE_SYNC should trigger it.
+ */
+ e.ino = FILE_INO;
+ e.generation = 1;
+ e.attr_timeout = 10.0;
+ e.entry_timeout = 10.0;
+ e.attr.st_ino = FILE_INO;
+ e.attr.st_mode = S_IFREG | 0644;
+ e.attr.st_nlink = 1;
+ fuse_reply_entry(req, &e);
+}
+
+static void fs_getattr(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct stat st = {};
+
+ (void)fi;
+ if (ino == FUSE_ROOT_ID) {
+ st.st_ino = FUSE_ROOT_ID;
+ st.st_mode = S_IFDIR | 0755;
+ st.st_nlink = 2;
+ } else if (ino == FILE_INO) {
+ st.st_ino = FILE_INO;
+ st.st_mode = S_IFREG | 0644;
+ st.st_nlink = 1;
+ } else {
+ fuse_reply_err(req, ENOENT);
+ return;
+ }
+ fuse_reply_attr(req, &st, 10);
+}
+
+static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+ size_t size)
+{
+ if (ino != FILE_INO ||
+ strcmp(name, "system.posix_acl_access") != 0) {
+ fuse_reply_err(req, ENODATA);
+ return;
+ }
+
+ pthread_mutex_lock(&g_ds.lock);
+ const uint8_t *acl = g_ds.acl;
+ size_t acl_size = g_ds.acl_size;
+ g_ds.getxattr_count++;
+ pthread_mutex_unlock(&g_ds.lock);
+
+ if (size == 0)
+ fuse_reply_xattr(req, acl_size);
+ else if (size < acl_size)
+ fuse_reply_err(req, ERANGE);
+ else
+ fuse_reply_buf(req, (const char *)acl, acl_size);
+}
+
+static const struct fuse_lowlevel_ops fs_ops = {
+ .lookup = fs_lookup,
+ .getattr = fs_getattr,
+ .getxattr = fs_getxattr,
+};
+
+/* ---- Daemon thread ------------------------------------------------------- */
+
+static void *run_daemon(void *arg)
+{
+ fuse_session_loop((struct fuse_session *)arg);
+ return NULL;
+}
+
+/* ---- kselftest harness --------------------------------------------------- */
+
+FIXTURE(acl_cache) {
+ struct fuse_session *se;
+ char mountpoint[PATH_MAX];
+ char file_path[PATH_MAX];
+ pthread_t thread;
+};
+
+FIXTURE_SETUP(acl_cache)
+{
+ char *fuse_argv[] = { "fuse_acl_cache_test", NULL };
+ struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
+
+ g_ds.acl = acl_a;
+ g_ds.acl_size = sizeof(acl_a);
+ g_ds.getxattr_count = 0;
+
+ strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX");
+ if (!mkdtemp(self->mountpoint))
+ SKIP(return, "mkdtemp: %s", strerror(errno));
+
+ snprintf(self->file_path, sizeof(self->file_path),
+ "%s/" FILE_NAME, self->mountpoint);
+
+ self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
+ if (!self->se) {
+ rmdir(self->mountpoint);
+ SKIP(return, "fuse_session_new failed");
+ }
+
+ if (fuse_session_mount(self->se, self->mountpoint)) {
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+ SKIP(return, "fuse_session_mount failed "
+ "(missing fusermount3 or insufficient privileges)");
+ }
+
+ if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
+ fuse_session_unmount(self->se);
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+ SKIP(return, "pthread_create: %s", strerror(errno));
+ }
+
+ fuse_opt_free_args(&args);
+}
+
+FIXTURE_TEARDOWN(acl_cache)
+{
+ fuse_session_exit(self->se);
+ fuse_session_unmount(self->se);
+ pthread_join(self->thread, NULL);
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+}
+
+static int do_force_statx(const char *path)
+{
+ struct statx stx;
+
+ return syscall(SYS_statx, AT_FDCWD, path,
+ AT_STATX_FORCE_SYNC, STATX_BASIC_STATS, &stx);
+}
+
+TEST_F(acl_cache, stale_after_force_sync)
+{
+ char buf[512];
+ ssize_t sz;
+ int count;
+
+ /*
+ * Step 1: two getxattr calls before any statx(FORCE_SYNC).
+ * i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED,
+ * sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel
+ * is never placed and the result is never cached. Both calls must
+ * reach the daemon, proving ACL_DONT_CACHE suppresses caching.
+ */
+ sz = lgetxattr(self->file_path, "system.posix_acl_access",
+ buf, sizeof(buf));
+ ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
+
+ sz = lgetxattr(self->file_path, "system.posix_acl_access",
+ buf, sizeof(buf));
+ ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
+
+ pthread_mutex_lock(&g_ds.lock);
+ count = g_ds.getxattr_count;
+ pthread_mutex_unlock(&g_ds.lock);
+
+ ASSERT_EQ(count, 2);
+ TH_LOG("step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), "
+ "ACL_DONT_CACHE is working", count);
+
+ /*
+ * Step 2: statx(AT_STATX_FORCE_SYNC).
+ * fuse_update_get_attr() calls forget_all_cached_acls() before sending
+ * FUSE_GETATTR.
+ * Buggy kernel: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled)
+ * Fixed kernel: ACL_DONT_CACHE preserved (no effect)
+ */
+ ASSERT_EQ(do_force_statx(self->file_path), 0);
+ TH_LOG("step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded");
+
+ /*
+ * Step 3: getxattr — cache population attempt after the trigger.
+ * Buggy: *p == ACL_NOT_CACHED -> sentinel placed -> fuse_get_inode_acl
+ * called -> ACL_A parsed and stored in the kernel cache.
+ * Fixed: *p == ACL_DONT_CACHE -> sentinel placement skipped ->
+ * fuse_get_inode_acl called but result not cached.
+ * Either way the correct ACL_A is returned here.
+ */
+ sz = lgetxattr(self->file_path, "system.posix_acl_access",
+ buf, sizeof(buf));
+ ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
+
+ pthread_mutex_lock(&g_ds.lock);
+ count = g_ds.getxattr_count;
+ pthread_mutex_unlock(&g_ds.lock);
+
+ ASSERT_EQ(count, 3);
+ TH_LOG("step 3 OK: post-trigger getxattr reached daemon (count=%d), "
+ "returned correct ACL_A (%zd bytes)", count, sz);
+
+ /*
+ * Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes).
+ * Simulates an ACL change that fuse_set_acl() would NOT invalidate for
+ * !fc->posix_acl mounts (it skips forget_all_cached_acls in that case).
+ * On a fixed kernel the ACL was never cached, so this is moot.
+ */
+ pthread_mutex_lock(&g_ds.lock);
+ g_ds.acl = acl_b;
+ g_ds.acl_size = sizeof(acl_b);
+ pthread_mutex_unlock(&g_ds.lock);
+ TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b));
+
+ /*
+ * Step 5: getxattr — the decisive check.
+ * Buggy kernel: cache hit -> stale ACL_A (28 bytes), count stays 3.
+ * Fixed kernel: no cache -> daemon called -> ACL_B (44 bytes), count 4.
+ */
+ sz = lgetxattr(self->file_path, "system.posix_acl_access",
+ buf, sizeof(buf));
+
+ pthread_mutex_lock(&g_ds.lock);
+ count = g_ds.getxattr_count;
+ pthread_mutex_unlock(&g_ds.lock);
+
+ if (sz == (ssize_t)sizeof(acl_a))
+ TH_LOG("step 5 BUG: stale ACL_A (%zd bytes) from kernel cache "
+ "(count=%d); ACL_DONT_CACHE corrupted by "
+ "forget_all_cached_acls()", sz, count);
+ else
+ TH_LOG("step 5 OK: daemon reached (count=%d), "
+ "fresh ACL_B (%zd bytes)", count, sz);
+
+ EXPECT_EQ(sz, (ssize_t)sizeof(acl_b));
+ EXPECT_EQ(count, 4);
+}
+
+TEST_HARNESS_MAIN
|
| 3/2 | 2026/07/14 03:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 7019 output: 20 thoughts: 262 Thoughts: **My Analysis of the `fs/posix_acl.c` Patch** Okay, so I'm looking at this patch for `fs/posix_acl.c`, specifically the `__forget_cached_acl` function. The change seems straightforward enough, but it has implications for the VFS and ACL handling, so I need to understand this thoroughly. Basically, the patch introduces a check: if the value pointed to by `p` (which, as I recall, likely represents the cached ACL status) is already `ACL_DONT_CACHE`, it immediately returns. The comment explains the rationale, which is crucial here. They are concerned that `xchg`ing `ACL_DONT_CACHE` (which is, by definition, intended to be a *constant* meaning that the ACL *shouldn't* be cached) with `ACL_NOT_CACHED` (meaning the ACL *isn't* cached) would inadvertently *enable* caching when the intention is the opposite. This makes perfect sense; `xchg` can be a blunt tool, and we wouldn't want to accidentally flip a crucial flag. My immediate thought is that this primarily impacts filesystems that deliberately *avoid* caching ACLs, which, as the text notes, will definitely include FUSE without `FUSE_POSIX_ACL`. I can see the potential for subtle, but nasty, bugs if we were to inadvertently enable caching when it's specifically disabled. So, the fix makes sense. Now, I'll use `grepper` to confirm my understanding by taking a look at the code for `__forget_cached_acl` in `fs/posix_acl.c` to see if my mental model is correct. I want to trace the code to be 100% sure this logic applies where I think it does. |
| 4/2 | 2026/07/14 03:13 | tool | grepper |
0mArgs:{
"Expression": "__forget_cached_acl"
}Results: {
"Output": "fs/posix_acl.c=90=EXPORT_SYMBOL(set_cached_acl);\nfs/posix_acl.c-91-\nfs/posix_acl.c:92:static void __forget_cached_acl(struct posix_acl **p)\nfs/posix_acl.c-93-{\n--\nfs/posix_acl.c=108=void forget_cached_acl(struct inode *inode, int type)\nfs/posix_acl.c-109-{\nfs/posix_acl.c:110:\t__forget_cached_acl(acl_by_type(inode, type));\nfs/posix_acl.c-111-}\n--\nfs/posix_acl.c=114=void forget_all_cached_acls(struct inode *inode)\nfs/posix_acl.c-115-{\nfs/posix_acl.c:116:\t__forget_cached_acl(\u0026inode-\u003ei_acl);\nfs/posix_acl.c:117:\t__forget_cached_acl(\u0026inode-\u003ei_default_acl);\nfs/posix_acl.c-118-}\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-8- * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel,\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:9: * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED,\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-10- * inadvertently enabling the kernel ACL cache for that inode. The next\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-15- *\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:16: * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-17- * ACL_DONT_CACHE, preserving the \"never cache\" invariant for the inode's\n"
} |
| 5/2 | 2026/07/14 03:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8260 output: 28 thoughts: 98 Thoughts: **Analyzing the Proposed Patch** Okay, so this patch seems to be focused on ACL caching, specifically targeting the `__forget_cached_acl` function. I know this is called by `forget_cached_acl` and `forget_all_cached_acls`, so those are definitely reachable code paths. Good. It's also adding a selftest for FUSE – important for ensuring functionality after the change. Now, let's dig into the dependencies. I need to figure out the configuration requirements. Looks like `fs/posix_acl.c` is the key file here, and it’s compiled when `CONFIG_FS_POSIX_ACL` is enabled. Time to confirm that, make sure there aren't any sneaky interactions or unexpected conditionals. |
| 6/2 | 2026/07/14 03:13 | tool | grepper |
0mArgs:{
"Expression": "posix_acl.o",
"PathPrefix": "fs/"
}Results: {
"Output": "fs/Makefile=43=obj-$(CONFIG_FS_MBCACHE)\t+= mbcache.o\nfs/Makefile:44:obj-$(CONFIG_FS_POSIX_ACL)\t+= posix_acl.o\nfs/Makefile-45-obj-$(CONFIG_NFS_COMMON)\t+= nfs_common/\n--\nfs/posix_acl.c=735=static int posix_acl_fix_xattr_common(const void *value, size_t size)\n--\nfs/posix_acl.c-776- *\nfs/posix_acl.c:777: * Return: Allocated struct posix_acl on success, NULL for a valid header but\nfs/posix_acl.c-778- * without actual POSIX ACL entries, or ERR_PTR() encoded error code.\n"
} |
| 7/2 | 2026/07/14 03:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8757 output: 22 thoughts: 228 Thoughts: **Analyzing the Posix ACL Configuration and Potential Vulnerabilities** Okay, here's what I'm thinking. We're looking at `CONFIG_FS_POSIX_ACL`, right? That's the primary configuration parameter. The test framework leverages FUSE, which immediately made me think about `CONFIG_FUSE_FS` and potentially fuzzing FUSE paths. However, the root cause is firmly within `fs/posix_acl.c`. Aha, `CONFIG_FS_POSIX_ACL` is more fundamental than I initially thought; it’s a core config option, often indirectly selected by other file systems. That's good to keep in mind. Now, my focus needs to be on specific functions. Since `__forget_cached_acl` is static, my attention needs to be on `forget_cached_acl` and `forget_all_cached_acls`. I’m also going to note that `fuse_update_get_attr` is potentially a trigger function, since it’s related. Wait, to be clear, the bug is within the static function `__forget_cached_acl` itself, but that’s called by `forget_cached_acl` and `forget_all_cached_acls`, so my direct focus is on those. Now, I'll shift my attention to checking the callers of `forget_all_cached_acls`. |
| 8/2 | 2026/07/14 03:13 | tool | grepper |
0mArgs:{
"Expression": "forget_all_cached_acls"
}Results: {
"Output": "fs/ceph/caps.c=3488=static void handle_cap_grant(struct inode *inode,\n--\nfs/ceph/caps.c-3620-\t\t\tci-\u003ei_xattrs.version = version;\nfs/ceph/caps.c:3621:\t\t\tceph_forget_all_cached_acls(inode);\nfs/ceph/caps.c-3622-\t\t\tceph_security_invalidate_secctx(inode);\n--\nfs/ceph/inode.c=1013=int ceph_fill_inode(struct inode *inode, struct page *locked_page,\n--\nfs/ceph/inode.c-1238-\t\tci-\u003ei_xattrs.version = le64_to_cpu(info-\u003exattr_version);\nfs/ceph/inode.c:1239:\t\tceph_forget_all_cached_acls(inode);\nfs/ceph/inode.c-1240-\t\tceph_security_invalidate_secctx(inode);\n--\nfs/ceph/super.h=1232=void ceph_init_inode_acls(struct inode *inode,\n--\nfs/ceph/super.h-1234-\nfs/ceph/super.h:1235:static inline void ceph_forget_all_cached_acls(struct inode *inode)\nfs/ceph/super.h-1236-{\nfs/ceph/super.h:1237: forget_all_cached_acls(inode);\nfs/ceph/super.h-1238-}\n--\nfs/ceph/super.h=1250=static inline void ceph_init_inode_acls(struct inode *inode,\n--\nfs/ceph/super.h-1254-\nfs/ceph/super.h:1255:static inline void ceph_forget_all_cached_acls(struct inode *inode)\nfs/ceph/super.h-1256-{\n--\nfs/fuse/acl.c=93=int fuse_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,\n--\nfs/fuse/acl.c-155-\t\t */\nfs/fuse/acl.c:156:\t\tforget_all_cached_acls(inode);\nfs/fuse/acl.c-157-\t\tfuse_invalidate_attr(inode);\n--\nfs/fuse/dir.c=383=static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,\n--\nfs/fuse/dir.c-446-\nfs/fuse/dir.c:447:\t\tforget_all_cached_acls(inode);\nfs/fuse/dir.c-448-\t\tfuse_change_attributes(inode, \u0026outarg.attr, NULL,\n--\nfs/fuse/dir.c=1522=static int fuse_update_get_attr(struct mnt_idmap *idmap, struct inode *inode,\n--\nfs/fuse/dir.c-1551-\tif (sync) {\nfs/fuse/dir.c:1552:\t\tforget_all_cached_acls(inode);\nfs/fuse/dir.c-1553-\t\t/* Try statx if BTIME is requested */\n--\nfs/fuse/dir.c=1726=static int fuse_perm_getattr(struct inode *inode, int mask)\n--\nfs/fuse/dir.c-1730-\nfs/fuse/dir.c:1731:\tforget_all_cached_acls(inode);\nfs/fuse/dir.c-1732-\treturn fuse_do_getattr(\u0026nop_mnt_idmap, inode, NULL, NULL);\n--\nfs/fuse/dir.c=2307=static int fuse_setattr(struct mnt_idmap *idmap, struct dentry *entry,\n--\nfs/fuse/dir.c-2360-\t\tif (fc-\u003eposix_acl)\nfs/fuse/dir.c:2361:\t\t\tforget_all_cached_acls(inode);\nfs/fuse/dir.c-2362-\n--\nfs/fuse/inode.c=545=int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,\n--\nfs/fuse/inode.c-562-\tfuse_invalidate_attr(inode);\nfs/fuse/inode.c:563:\tforget_all_cached_acls(inode);\nfs/fuse/inode.c-564-\tif (offset \u003e= 0) {\n--\nfs/fuse/readdir.c=153=static int fuse_direntplus_link(struct file *file,\n--\nfs/fuse/readdir.c-228-\nfs/fuse/readdir.c:229:\t\tforget_all_cached_acls(inode);\nfs/fuse/readdir.c-230-\t\tfuse_change_attributes(inode, \u0026o-\u003eattr, NULL,\n--\nfs/gfs2/glops.c=358=static void inode_go_inval(struct gfs2_glock *gl, int flags)\n--\nfs/gfs2/glops.c-368-\t\t\tset_bit(GLF_INSTANTIATE_NEEDED, \u0026gl-\u003egl_flags);\nfs/gfs2/glops.c:369:\t\t\tforget_all_cached_acls(\u0026ip-\u003ei_inode);\nfs/gfs2/glops.c-370-\t\t\tsecurity_inode_invalidate_secctx(\u0026ip-\u003ei_inode);\n--\nfs/nfs/nfs3proc.c=1076=const struct nfs_rpc_ops nfs_v3_clientops = {\n--\nfs/nfs/nfs3proc.c-1118-\t.lock\t\t= nfs3_proc_lock,\nfs/nfs/nfs3proc.c:1119:\t.clear_acl_cache = forget_all_cached_acls,\nfs/nfs/nfs3proc.c-1120-\t.close_context\t= nfs_close_context,\n--\nfs/ocfs2/dlmglue.c=3958=static int ocfs2_data_convert_worker(struct ocfs2_lock_res *lockres,\n--\nfs/ocfs2/dlmglue.c-4002-out_forget:\nfs/ocfs2/dlmglue.c:4003:\tforget_all_cached_acls(inode);\nfs/ocfs2/dlmglue.c-4004-\n--\nfs/posix_acl.c=112=EXPORT_SYMBOL(forget_cached_acl);\nfs/posix_acl.c-113-\nfs/posix_acl.c:114:void forget_all_cached_acls(struct inode *inode)\nfs/posix_acl.c-115-{\n--\nfs/posix_acl.c-118-}\nfs/posix_acl.c:119:EXPORT_SYMBOL(forget_all_cached_acls);\nfs/posix_acl.c-120-\n--\ninclude/linux/posix_acl.h=96=void forget_cached_acl(struct inode *inode, int type);\ninclude/linux/posix_acl.h:97:void forget_all_cached_acls(struct inode *inode);\ninclude/linux/posix_acl.h-98-int posix_acl_valid(struct user_namespace *, const struct posix_acl *);\n--\ninclude/linux/posix_acl.h=133=static inline int posix_acl_create(struct inode *inode, umode_t *mode,\n--\ninclude/linux/posix_acl.h-139-\ninclude/linux/posix_acl.h:140:static inline void forget_all_cached_acls(struct inode *inode)\ninclude/linux/posix_acl.h-141-{\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-7- * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:8: * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel,\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-9- * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED,\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-11- * getxattr populates the cache. Because fuse_set_acl() skips\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:12: * forget_all_cached_acls() for !fc-\u003eposix_acl mounts, any subsequent change to\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-13- * the ACL leaves the stale kernel entry in place, and the next getxattr returns\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-22- * 2. lgetxattr -\u003e daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE).\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:23: * 3. statx(AT_STATX_FORCE_SYNC) -\u003e forget_all_cached_acls() called.\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-24- * Buggy: ACL_DONT_CACHE -\u003e ACL_NOT_CACHED (cache enabled).\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c=107=static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-116-\t * Long attr/entry timeouts so that normal stat() calls do not\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:117:\t * expire and trigger forget_all_cached_acls() on their own;\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-118-\t * only the explicit AT_STATX_FORCE_SYNC should trigger it.\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c=253=TEST_F(acl_cache, stale_after_force_sync)\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-283-\t * Step 2: statx(AT_STATX_FORCE_SYNC).\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:284:\t * fuse_update_get_attr() calls forget_all_cached_acls() before sending\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-285-\t * FUSE_GETATTR.\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-314-\t * Simulates an ACL change that fuse_set_acl() would NOT invalidate for\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:315:\t * !fc-\u003eposix_acl mounts (it skips forget_all_cached_acls in that case).\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-316-\t * On a fixed kernel the ACL was never cached, so this is moot.\n--\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-338-\t\t \"(count=%d); ACL_DONT_CACHE corrupted by \"\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c:339:\t\t \"forget_all_cached_acls()\", sz, count);\ntools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c-340-\telse\n"
} |
| 9/2 | 2026/07/14 03:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12370 output: 28 thoughts: 420 Thoughts: **My Analysis of ACL Caching and FUSE Vulnerabilities** Okay, let's break this down. My initial thought is that `forget_all_cached_acls` is *everywhere* – it's being called by a slew of network and distributed filesystems: FUSE, NFS, OCFS2, Ceph, and GFS2. That really highlights the scope of this potentially problematic area, especially since the bug is linked to `ACL_DONT_CACHE`, which means we're dealing with filesystems that *aren't* supposed to be caching ACLs. Since we're zeroing in on this specific behavior, I need to focus on `forget_all_cached_acls` and, more importantly, `forget_cached_acl`. FUSE uses this when `!fc->posix_acl` is set. While `fuse_update_get_attr` sounds like a useful symbol for targeted FUSE testing, it looks like the core of the bug lies within `__forget_cached_acl`. Although `__forget_cached_acl` is static, so I can't use it directly as a fuzzer focus symbol, `forget_all_cached_acls` and `forget_cached_acl` are exported and therefore useful. Now, I need to understand the config implications. Is `CONFIG_FS_POSIX_ACL` the only relevant configuration option? It seems like it, but let's double-check. Okay, yes it is the main config, though `CONFIG_FUSE_FS` is probably also useful since our test environment uses FUSE. Let's make sure those two are enabled. And yes, `CONFIG_FS_POSIX_ACL` is definitely a valid option – it's right there in `fs/Kconfig`. Excellent. We're on the right track. |
| 10/2 | 2026/07/14 03:13 | tool | grepper |
0mArgs:{
"Expression": "config FS_POSIX_ACL",
"PathPrefix": "fs/"
}Results: {
"Output": "fs/Kconfig=90=config FS_DAX_PMD\n--\nfs/Kconfig-101-#\nfs/Kconfig:102:config FS_POSIX_ACL\nfs/Kconfig-103-\tdef_bool n\n"
} |
| 11/2 | 2026/07/14 03:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13284 output: 27 thoughts: 26 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Validating Filesystem Configurations** Okay, so I'm running through my mental checklist. Yep, `FS_POSIX_ACL` is definitely a legitimate configuration option. No red flags there. That’s standard practice for access control and is usually straightforward. Then, let's see... ah, and of course, `FUSE_FS` is a go as well. No surprises. We've got the expected configurations available; nothing out of the ordinary so far. That confirms a base level of operational competence; no immediate issues to address on this front. Time to move on. |
| 12/2 | 2026/07/14 03:13 | tool | grepper |
0mArgs:{
"Expression": "config FUSE_FS",
"PathPrefix": "fs/"
}Results: {
"Output": "fs/fuse/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/fuse/Kconfig:2:config FUSE_FS\nfs/fuse/Kconfig-3-\ttristate \"FUSE (Filesystem in Userspace) support\"\n"
} |
| 13/2 | 2026/07/14 03:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13554 output: 175 thoughts: 199 Thoughts: **Analyzing the ACL Caching Bug Fix** Okay, so we're looking at configurations related to file system ACLs, specifically `FS_POSIX_ACL` and `FUSE_FS`. The key symbols we need to keep an eye on are `forget_all_cached_acls` and `forget_cached_acl`. I need to confirm something: is `__forget_cached_acl` defined in `fs/posix_acl.c`? Yep, it's there. The crux of the matter is this: there's a bug in `__forget_cached_acl` that mishandles the `ACL_DONT_CACHE` flag, potentially leading to ACL caching being enabled when it shouldn't be. This affects the core VFS/ACL logic, meaning it's a functional change that requires careful examination. The best approach for fuzzing this to confirm correct behavior will be to focus on those calls to `forget_all_cached_acls` and `forget_cached_acl` and pay close attention to the transitions of the ACL caching state. I'm going to get the results ready. |
| 14/2 | 2026/07/14 03:14 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"FS_POSIX_ACL",
"FUSE_FS"
],
"FocusSymbols": [
"forget_all_cached_acls",
"forget_cached_acl"
],
"Reasoning": "This patch fixes a bug in `__forget_cached_acl` where `ACL_DONT_CACHE` could be incorrectly replaced with `ACL_NOT_CACHED`, inadvertently enabling ACL caching for inodes that shouldn't have it (e.g., FUSE mounts without `FUSE_POSIX_ACL`). This is a functional change in core VFS/ACL logic. Fuzzing should focus on `forget_all_cached_acls` and `forget_cached_acl` to ensure ACL caching state transitions are handled correctly.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"FS_POSIX_ACL",
"FUSE_FS"
],
"FocusSymbols": [
"forget_all_cached_acls",
"forget_cached_acl"
],
"Reasoning": "This patch fixes a bug in `__forget_cached_acl` where `ACL_DONT_CACHE` could be incorrectly replaced with `ACL_NOT_CACHED`, inadvertently enabling ACL caching for inodes that shouldn't have it (e.g., FUSE mounts without `FUSE_POSIX_ACL`). This is a functional change in core VFS/ACL logic. Fuzzing should focus on `forget_all_cached_acls` and `forget_cached_acl` to ensure ACL caching state transitions are handled correctly.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|