1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC 4 * 5 * A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode 6 * with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed 7 * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls 8 * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel, 9 * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED, 10 * inadvertently enabling the kernel ACL cache for that inode. The next 11 * getxattr populates the cache. Because fuse_set_acl() skips 12 * forget_all_cached_acls() for !fc->posix_acl mounts, any subsequent change to 13 * the ACL leaves the stale kernel entry in place, and the next getxattr returns 14 * wrong data without ever reaching the FUSE daemon. 15 * 16 * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is 17 * ACL_DONT_CACHE, preserving the "never cache" invariant for the inode's 18 * lifetime. 19 * 20 * Test outline: 21 * 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated). 22 * 2. lgetxattr -> daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE). 23 * 3. statx(AT_STATX_FORCE_SYNC) -> forget_all_cached_acls() called. 24 * Buggy: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled). 25 * Fixed: ACL_DONT_CACHE preserved. 26 * 4. lgetxattr -> daemon called, ACL_A returned. 27 * Buggy: result now cached (ACL_NOT_CACHED -> cached). 28 * Fixed: result still not cached. 29 * 5. Daemon switches to ACL_B internally (different size). 30 * 6. lgetxattr -> should return ACL_B (44 bytes). 31 * Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL. 32 * Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS. 33 */ 34 35 #define _GNU_SOURCE 36 #include <errno.h> 37 #include <fcntl.h> 38 #include <linux/limits.h> 39 #include <pthread.h> 40 #include <stdint.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <sys/stat.h> 45 #include <sys/xattr.h> 46 #include <unistd.h> 47 48 #define FUSE_USE_VERSION 31 49 #include <fuse_lowlevel.h> 50 51 #include "kselftest_harness.h" 52 53 /* ---- ACL binary encoding ------------------------------------------------ */ 54 /* 55 * POSIX ACL v2 xattr format (little-endian): 56 * header: u32 version (= 0x00000002) 57 * entry: u16 tag | u16 perm | u32 id 58 * 59 * Entries must appear in tag-ascending order; named USER/GROUP entries 60 * require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation. 61 */ 62 63 /* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */ 64 static const uint8_t acl_a[] = { 65 0x02, 0x00, 0x00, 0x00, /* v2 header */ 66 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */ 67 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */ 68 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */ 69 }; 70 71 /* 72 * ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes. 73 * A named USER entry requires a MASK; all tags in ascending order. 74 */ 75 static const uint8_t acl_b[] = { 76 0x02, 0x00, 0x00, 0x00, /* v2 header */ 77 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */ 78 0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */ 79 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */ 80 0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */ 81 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */ 82 }; 83 84 /* ---- Shared state (daemon thread <-> test thread) ----------------------- */ 85 86 #define FILE_INO 2 87 #define FILE_NAME "testfile" 88 89 struct daemon_state { 90 pthread_mutex_t lock; 91 const uint8_t *acl; 92 size_t acl_size; 93 int getxattr_count; 94 }; 95 96 /* 97 * Global: callbacks are stateless fns so we use a single global. 98 * Safe because only one test instance runs at a time. 99 */ 100 static struct daemon_state g_ds = { 101 .lock = PTHREAD_MUTEX_INITIALIZER, 102 }; 103 104 /* ---- FUSE lowlevel callbacks -------------------------------------------- */ 105 106 static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) 107 { 108 if (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) { 109 fuse_reply_err(req, ENOENT); 110 return; 111 } 112 struct fuse_entry_param e = {}; 113 114 /* 115 * Long attr/entry timeouts so that normal stat() calls do not 116 * expire and trigger forget_all_cached_acls() on their own; 117 * only the explicit AT_STATX_FORCE_SYNC should trigger it. 118 */ 119 e.ino = FILE_INO; 120 e.generation = 1; 121 e.attr_timeout = 10.0; 122 e.entry_timeout = 10.0; 123 e.attr.st_ino = FILE_INO; 124 e.attr.st_mode = S_IFREG | 0644; 125 e.attr.st_nlink = 1; 126 fuse_reply_entry(req, &e); 127 } 128 129 static void fs_getattr(fuse_req_t req, fuse_ino_t ino, 130 struct fuse_file_info *fi) 131 { 132 struct stat st = {}; 133 134 (void)fi; 135 if (ino == FUSE_ROOT_ID) { 136 st.st_ino = FUSE_ROOT_ID; 137 st.st_mode = S_IFDIR | 0755; 138 st.st_nlink = 2; 139 } else if (ino == FILE_INO) { 140 st.st_ino = FILE_INO; 141 st.st_mode = S_IFREG | 0644; 142 st.st_nlink = 1; 143 } else { 144 fuse_reply_err(req, ENOENT); 145 return; 146 } 147 fuse_reply_attr(req, &st, 10); 148 } 149 150 static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, 151 size_t size) 152 { 153 if (ino != FILE_INO || 154 strcmp(name, "system.posix_acl_access") != 0) { 155 fuse_reply_err(req, ENODATA); 156 return; 157 } 158 159 pthread_mutex_lock(&g_ds.lock); 160 const uint8_t *acl = g_ds.acl; 161 size_t acl_size = g_ds.acl_size; 162 g_ds.getxattr_count++; 163 pthread_mutex_unlock(&g_ds.lock); 164 165 if (size == 0) 166 fuse_reply_xattr(req, acl_size); 167 else if (size < acl_size) 168 fuse_reply_err(req, ERANGE); 169 else 170 fuse_reply_buf(req, (const char *)acl, acl_size); 171 } 172 173 static const struct fuse_lowlevel_ops fs_ops = { 174 .lookup = fs_lookup, 175 .getattr = fs_getattr, 176 .getxattr = fs_getxattr, 177 }; 178 179 /* ---- Daemon thread ------------------------------------------------------- */ 180 181 static void *run_daemon(void *arg) 182 { 183 fuse_session_loop((struct fuse_session *)arg); 184 return NULL; 185 } 186 187 /* ---- kselftest harness --------------------------------------------------- */ 188 189 FIXTURE(acl_cache) { 190 struct fuse_session *se; 191 char mountpoint[PATH_MAX]; 192 char file_path[PATH_MAX]; 193 pthread_t thread; 194 }; 195 196 FIXTURE_SETUP(acl_cache) 197 { 198 char *fuse_argv[] = { "fuse_acl_cache_test", NULL }; 199 struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv); 200 201 g_ds.acl = acl_a; 202 g_ds.acl_size = sizeof(acl_a); 203 g_ds.getxattr_count = 0; 204 205 strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX"); 206 if (!mkdtemp(self->mountpoint)) 207 SKIP(return, "mkdtemp: %s", strerror(errno)); 208 209 snprintf(self->file_path, sizeof(self->file_path), 210 "%s/" FILE_NAME, self->mountpoint); 211 212 self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL); 213 if (!self->se) { 214 rmdir(self->mountpoint); 215 SKIP(return, "fuse_session_new failed"); 216 } 217 218 if (fuse_session_mount(self->se, self->mountpoint)) { 219 fuse_session_destroy(self->se); 220 rmdir(self->mountpoint); 221 SKIP(return, "fuse_session_mount failed " 222 "(missing fusermount3 or insufficient privileges)"); 223 } 224 225 if (pthread_create(&self->thread, NULL, run_daemon, self->se)) { 226 fuse_session_unmount(self->se); 227 fuse_session_destroy(self->se); 228 rmdir(self->mountpoint); 229 SKIP(return, "pthread_create: %s", strerror(errno)); 230 } 231 232 fuse_opt_free_args(&args); 233 } 234 235 FIXTURE_TEARDOWN(acl_cache) 236 { 237 fuse_session_exit(self->se); 238 fuse_session_unmount(self->se); 239 pthread_join(self->thread, NULL); 240 fuse_session_destroy(self->se); 241 rmdir(self->mountpoint); 242 } 243 244 static int do_force_statx(const char *path) 245 { 246 struct statx stx; 247 248 return statx(AT_FDCWD, path, AT_STATX_FORCE_SYNC, STATX_BASIC_STATS, 249 &stx); 250 } 251 252 TEST_F(acl_cache, stale_after_force_sync) 253 { 254 char buf[512]; 255 ssize_t sz; 256 int count; 257 258 /* 259 * Step 1: two getxattr calls before any statx(FORCE_SYNC). 260 * i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED, 261 * sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel 262 * is never placed and the result is never cached. Both calls must 263 * reach the daemon, proving ACL_DONT_CACHE suppresses caching. 264 */ 265 sz = lgetxattr(self->file_path, "system.posix_acl_access", 266 buf, sizeof(buf)); 267 ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); 268 269 sz = lgetxattr(self->file_path, "system.posix_acl_access", 270 buf, sizeof(buf)); 271 ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); 272 273 pthread_mutex_lock(&g_ds.lock); 274 count = g_ds.getxattr_count; 275 pthread_mutex_unlock(&g_ds.lock); 276 277 ASSERT_EQ(count, 2); 278 TH_LOG("step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), " 279 "ACL_DONT_CACHE is working", count); 280 281 /* 282 * Step 2: statx(AT_STATX_FORCE_SYNC). 283 * fuse_update_get_attr() calls forget_all_cached_acls() before sending 284 * FUSE_GETATTR. 285 * Buggy kernel: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled) 286 * Fixed kernel: ACL_DONT_CACHE preserved (no effect) 287 */ 288 ASSERT_EQ(do_force_statx(self->file_path), 0); 289 TH_LOG("step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded"); 290 291 /* 292 * Step 3: getxattr — cache population attempt after the trigger. 293 * Buggy: *p == ACL_NOT_CACHED -> sentinel placed -> fuse_get_inode_acl 294 * called -> ACL_A parsed and stored in the kernel cache. 295 * Fixed: *p == ACL_DONT_CACHE -> sentinel placement skipped -> 296 * fuse_get_inode_acl called but result not cached. 297 * Either way the correct ACL_A is returned here. 298 */ 299 sz = lgetxattr(self->file_path, "system.posix_acl_access", 300 buf, sizeof(buf)); 301 ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); 302 303 pthread_mutex_lock(&g_ds.lock); 304 count = g_ds.getxattr_count; 305 pthread_mutex_unlock(&g_ds.lock); 306 307 ASSERT_EQ(count, 3); 308 TH_LOG("step 3 OK: post-trigger getxattr reached daemon (count=%d), " 309 "returned correct ACL_A (%zd bytes)", count, sz); 310 311 /* 312 * Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes). 313 * Simulates an ACL change that fuse_set_acl() would NOT invalidate for 314 * !fc->posix_acl mounts (it skips forget_all_cached_acls in that case). 315 * On a fixed kernel the ACL was never cached, so this is moot. 316 */ 317 pthread_mutex_lock(&g_ds.lock); 318 g_ds.acl = acl_b; 319 g_ds.acl_size = sizeof(acl_b); 320 pthread_mutex_unlock(&g_ds.lock); 321 TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b)); 322 323 /* 324 * Step 5: getxattr — the decisive check. 325 * Buggy kernel: cache hit -> stale ACL_A (28 bytes), count stays 3. 326 * Fixed kernel: no cache -> daemon called -> ACL_B (44 bytes), count 4. 327 */ 328 sz = lgetxattr(self->file_path, "system.posix_acl_access", 329 buf, sizeof(buf)); 330 331 pthread_mutex_lock(&g_ds.lock); 332 count = g_ds.getxattr_count; 333 pthread_mutex_unlock(&g_ds.lock); 334 335 if (sz == (ssize_t)sizeof(acl_a)) 336 TH_LOG("step 5 BUG: stale ACL_A (%zd bytes) from kernel cache " 337 "(count=%d); ACL_DONT_CACHE corrupted by " 338 "forget_all_cached_acls()", sz, count); 339 else 340 TH_LOG("step 5 OK: daemon reached (count=%d), " 341 "fresh ACL_B (%zd bytes)", count, sz); 342 343 EXPECT_EQ(sz, (ssize_t)sizeof(acl_b)); 344 EXPECT_EQ(count, 4); 345 } 346 347 TEST_HARNESS_MAIN 348