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
fs_lookup(fuse_req_t req,fuse_ino_t parent,const char * name)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
fs_getattr(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)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
fs_getxattr(fuse_req_t req,fuse_ino_t ino,const char * name,size_t size)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
run_daemon(void * arg)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
FIXTURE(acl_cache)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
FIXTURE_SETUP(acl_cache)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
FIXTURE_TEARDOWN(acl_cache)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
do_force_statx(const char * path)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
TEST_F(acl_cache,stale_after_force_sync)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