xref: /linux/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c (revision f1529936c0b65fb343f62f50e5313078719fc336)
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