xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 5956d97f4b3204318ceb6aa9c77bd0bc6ea87a41)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 The FreeBSD Foundation
5  *
6  * This software was developed by BFF Storage Systems, LLC under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  * $FreeBSD$
31  */
32 
33 /*
34  * Tests for the "default_permissions" mount option.  They must be in their own
35  * file so they can be run as an unprivileged user
36  */
37 
38 extern "C" {
39 #include <sys/types.h>
40 #include <sys/extattr.h>
41 
42 #include <fcntl.h>
43 #include <semaphore.h>
44 #include <unistd.h>
45 }
46 
47 #include "mockfs.hh"
48 #include "utils.hh"
49 
50 using namespace testing;
51 
52 class DefaultPermissions: public FuseTest {
53 
54 virtual void SetUp() {
55 	m_default_permissions = true;
56 	FuseTest::SetUp();
57 	if (HasFatalFailure() || IsSkipped())
58 		return;
59 
60 	if (geteuid() == 0) {
61 		GTEST_SKIP() << "This test requires an unprivileged user";
62 	}
63 
64 	/* With -o default_permissions, FUSE_ACCESS should never be called */
65 	EXPECT_CALL(*m_mock, process(
66 		ResultOf([=](auto in) {
67 			return (in.header.opcode == FUSE_ACCESS);
68 		}, Eq(true)),
69 		_)
70 	).Times(0);
71 }
72 
73 public:
74 void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0)
75 {
76 	EXPECT_CALL(*m_mock, process(
77 		ResultOf([=](auto in) {
78 			return (in.header.opcode == FUSE_SETATTR &&
79 				in.header.nodeid == ino &&
80 				in.body.setattr.valid == FATTR_MODE &&
81 				in.body.setattr.mode == mode);
82 		}, Eq(true)),
83 		_)
84 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
85 		SET_OUT_HEADER_LEN(out, attr);
86 		out.body.attr.attr.ino = ino;	// Must match nodeid
87 		out.body.attr.attr.mode = S_IFREG | mode;
88 		out.body.attr.attr.size = size;
89 		out.body.attr.attr_valid = UINT64_MAX;
90 	})));
91 }
92 
93 void expect_create(const char *relpath, uint64_t ino)
94 {
95 	EXPECT_CALL(*m_mock, process(
96 		ResultOf([=](auto in) {
97 			const char *name = (const char*)in.body.bytes +
98 				sizeof(fuse_create_in);
99 			return (in.header.opcode == FUSE_CREATE &&
100 				(0 == strcmp(relpath, name)));
101 		}, Eq(true)),
102 		_)
103 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
104 		SET_OUT_HEADER_LEN(out, create);
105 		out.body.create.entry.attr.mode = S_IFREG | 0644;
106 		out.body.create.entry.nodeid = ino;
107 		out.body.create.entry.entry_valid = UINT64_MAX;
108 		out.body.create.entry.attr_valid = UINT64_MAX;
109 	})));
110 }
111 
112 void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
113     uint64_t off_out, uint64_t len)
114 {
115 	EXPECT_CALL(*m_mock, process(
116 		ResultOf([=](auto in) {
117 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
118 				in.header.nodeid == ino_in &&
119 				in.body.copy_file_range.off_in == off_in &&
120 				in.body.copy_file_range.nodeid_out == ino_out &&
121 				in.body.copy_file_range.off_out == off_out &&
122 				in.body.copy_file_range.len == len);
123 		}, Eq(true)),
124 		_)
125 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
126 		SET_OUT_HEADER_LEN(out, write);
127 		out.body.write.size = len;
128 	})));
129 }
130 
131 void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
132 	uid_t uid = 0, gid_t gid = 0)
133 {
134 	EXPECT_CALL(*m_mock, process(
135 		ResultOf([=](auto in) {
136 			return (in.header.opcode == FUSE_GETATTR &&
137 				in.header.nodeid == ino);
138 		}, Eq(true)),
139 		_)
140 	).Times(times)
141 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
142 		SET_OUT_HEADER_LEN(out, attr);
143 		out.body.attr.attr.ino = ino;	// Must match nodeid
144 		out.body.attr.attr.mode = mode;
145 		out.body.attr.attr.size = 0;
146 		out.body.attr.attr.uid = uid;
147 		out.body.attr.attr.gid = gid;
148 		out.body.attr.attr_valid = attr_valid;
149 	})));
150 }
151 
152 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
153 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
154 {
155 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
156 }
157 
158 };
159 
160 class Access: public DefaultPermissions {};
161 class Chown: public DefaultPermissions {};
162 class Chgrp: public DefaultPermissions {};
163 class CopyFileRange: public DefaultPermissions {};
164 class Fspacectl: public DefaultPermissions {};
165 class Lookup: public DefaultPermissions {};
166 class Open: public DefaultPermissions {};
167 class PosixFallocate: public DefaultPermissions {};
168 class Setattr: public DefaultPermissions {};
169 class Unlink: public DefaultPermissions {};
170 class Utimensat: public DefaultPermissions {};
171 class Write: public DefaultPermissions {};
172 
173 /*
174  * Test permission handling during create, mkdir, mknod, link, symlink, and
175  * rename vops (they all share a common path for permission checks in
176  * VOP_LOOKUP)
177  */
178 class Create: public DefaultPermissions {};
179 
180 class Deleteextattr: public DefaultPermissions {
181 public:
182 void expect_removexattr()
183 {
184 	EXPECT_CALL(*m_mock, process(
185 		ResultOf([=](auto in) {
186 			return (in.header.opcode == FUSE_REMOVEXATTR);
187 		}, Eq(true)),
188 		_)
189 	).WillOnce(Invoke(ReturnErrno(0)));
190 }
191 };
192 
193 class Getextattr: public DefaultPermissions {
194 public:
195 void expect_getxattr(ProcessMockerT r)
196 {
197 	EXPECT_CALL(*m_mock, process(
198 		ResultOf([=](auto in) {
199 			return (in.header.opcode == FUSE_GETXATTR);
200 		}, Eq(true)),
201 		_)
202 	).WillOnce(Invoke(r));
203 }
204 };
205 
206 class Listextattr: public DefaultPermissions {
207 public:
208 void expect_listxattr()
209 {
210 	EXPECT_CALL(*m_mock, process(
211 		ResultOf([=](auto in) {
212 			return (in.header.opcode == FUSE_LISTXATTR);
213 		}, Eq(true)),
214 		_)
215 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
216 		out.body.listxattr.size = 0;
217 		SET_OUT_HEADER_LEN(out, listxattr);
218 	})));
219 }
220 };
221 
222 class Rename: public DefaultPermissions {
223 public:
224 	/*
225 	 * Expect a rename and respond with the given error.  Don't both to
226 	 * validate arguments; the tests in rename.cc do that.
227 	 */
228 	void expect_rename(int error)
229 	{
230 		EXPECT_CALL(*m_mock, process(
231 			ResultOf([=](auto in) {
232 				return (in.header.opcode == FUSE_RENAME);
233 			}, Eq(true)),
234 			_)
235 		).WillOnce(Invoke(ReturnErrno(error)));
236 	}
237 };
238 
239 class Setextattr: public DefaultPermissions {
240 public:
241 void expect_setxattr(int error)
242 {
243 	EXPECT_CALL(*m_mock, process(
244 		ResultOf([=](auto in) {
245 			return (in.header.opcode == FUSE_SETXATTR);
246 		}, Eq(true)),
247 		_)
248 	).WillOnce(Invoke(ReturnErrno(error)));
249 }
250 };
251 
252 /* Return a group to which this user does not belong */
253 static gid_t excluded_group()
254 {
255 	int i, ngroups = 64;
256 	gid_t newgid, groups[ngroups];
257 
258 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
259 	for (newgid = 0; ; newgid++) {
260 		bool belongs = false;
261 
262 		for (i = 0; i < ngroups; i++) {
263 			if (groups[i] == newgid)
264 				belongs = true;
265 		}
266 		if (!belongs)
267 			break;
268 	}
269 	/* newgid is now a group to which the current user does not belong */
270 	return newgid;
271 }
272 
273 TEST_F(Access, eacces)
274 {
275 	const char FULLPATH[] = "mountpoint/some_file.txt";
276 	const char RELPATH[] = "some_file.txt";
277 	uint64_t ino = 42;
278 	mode_t	access_mode = X_OK;
279 
280 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
281 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
282 
283 	ASSERT_NE(0, access(FULLPATH, access_mode));
284 	ASSERT_EQ(EACCES, errno);
285 }
286 
287 TEST_F(Access, eacces_no_cached_attrs)
288 {
289 	const char FULLPATH[] = "mountpoint/some_file.txt";
290 	const char RELPATH[] = "some_file.txt";
291 	uint64_t ino = 42;
292 	mode_t	access_mode = X_OK;
293 
294 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1);
295 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
296 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
297 	/*
298 	 * Once default_permissions is properly implemented, there might be
299 	 * another FUSE_GETATTR or something in here.  But there should not be
300 	 * a FUSE_ACCESS
301 	 */
302 
303 	ASSERT_NE(0, access(FULLPATH, access_mode));
304 	ASSERT_EQ(EACCES, errno);
305 }
306 
307 TEST_F(Access, ok)
308 {
309 	const char FULLPATH[] = "mountpoint/some_file.txt";
310 	const char RELPATH[] = "some_file.txt";
311 	uint64_t ino = 42;
312 	mode_t	access_mode = R_OK;
313 
314 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
315 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
316 	/*
317 	 * Once default_permissions is properly implemented, there might be
318 	 * another FUSE_GETATTR or something in here.
319 	 */
320 
321 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
322 }
323 
324 /* Unprivileged users may chown a file to their own uid */
325 TEST_F(Chown, chown_to_self)
326 {
327 	const char FULLPATH[] = "mountpoint/some_file.txt";
328 	const char RELPATH[] = "some_file.txt";
329 	const uint64_t ino = 42;
330 	const mode_t mode = 0755;
331 	uid_t uid;
332 
333 	uid = geteuid();
334 
335 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
336 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
337 	/* The OS may optimize chown by omitting the redundant setattr */
338 	EXPECT_CALL(*m_mock, process(
339 		ResultOf([](auto in) {
340 			return (in.header.opcode == FUSE_SETATTR);
341 		}, Eq(true)),
342 		_)
343 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
344 		SET_OUT_HEADER_LEN(out, attr);
345 		out.body.attr.attr.mode = S_IFREG | mode;
346 		out.body.attr.attr.uid = uid;
347 	})));
348 
349 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
350 }
351 
352 /*
353  * A successful chown by a non-privileged non-owner should clear a file's SUID
354  * bit
355  */
356 TEST_F(Chown, clear_suid)
357 {
358 	const char FULLPATH[] = "mountpoint/some_file.txt";
359 	const char RELPATH[] = "some_file.txt";
360 	uint64_t ino = 42;
361 	const mode_t oldmode = 06755;
362 	const mode_t newmode = 0755;
363 	uid_t uid = geteuid();
364 	uint32_t valid = FATTR_UID | FATTR_MODE;
365 
366 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
367 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
368 	EXPECT_CALL(*m_mock, process(
369 		ResultOf([=](auto in) {
370 			return (in.header.opcode == FUSE_SETATTR &&
371 				in.header.nodeid == ino &&
372 				in.body.setattr.valid == valid &&
373 				in.body.setattr.mode == newmode);
374 		}, Eq(true)),
375 		_)
376 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
377 		SET_OUT_HEADER_LEN(out, attr);
378 		out.body.attr.attr.ino = ino;	// Must match nodeid
379 		out.body.attr.attr.mode = S_IFREG | newmode;
380 		out.body.attr.attr_valid = UINT64_MAX;
381 	})));
382 
383 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
384 }
385 
386 
387 /* Only root may change a file's owner */
388 TEST_F(Chown, eperm)
389 {
390 	const char FULLPATH[] = "mountpoint/some_file.txt";
391 	const char RELPATH[] = "some_file.txt";
392 	const uint64_t ino = 42;
393 	const mode_t mode = 0755;
394 
395 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
396 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
397 	EXPECT_CALL(*m_mock, process(
398 		ResultOf([](auto in) {
399 			return (in.header.opcode == FUSE_SETATTR);
400 		}, Eq(true)),
401 		_)
402 	).Times(0);
403 
404 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
405 	EXPECT_EQ(EPERM, errno);
406 }
407 
408 /*
409  * A successful chgrp by a non-privileged non-owner should clear a file's SUID
410  * bit
411  */
412 TEST_F(Chgrp, clear_suid)
413 {
414 	const char FULLPATH[] = "mountpoint/some_file.txt";
415 	const char RELPATH[] = "some_file.txt";
416 	uint64_t ino = 42;
417 	const mode_t oldmode = 06755;
418 	const mode_t newmode = 0755;
419 	uid_t uid = geteuid();
420 	gid_t gid = getegid();
421 	uint32_t valid = FATTR_GID | FATTR_MODE;
422 
423 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
424 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
425 	EXPECT_CALL(*m_mock, process(
426 		ResultOf([=](auto in) {
427 			return (in.header.opcode == FUSE_SETATTR &&
428 				in.header.nodeid == ino &&
429 				in.body.setattr.valid == valid &&
430 				in.body.setattr.mode == newmode);
431 		}, Eq(true)),
432 		_)
433 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
434 		SET_OUT_HEADER_LEN(out, attr);
435 		out.body.attr.attr.ino = ino;	// Must match nodeid
436 		out.body.attr.attr.mode = S_IFREG | newmode;
437 		out.body.attr.attr_valid = UINT64_MAX;
438 	})));
439 
440 	EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
441 }
442 
443 /* non-root users may only chgrp a file to a group they belong to */
444 TEST_F(Chgrp, eperm)
445 {
446 	const char FULLPATH[] = "mountpoint/some_file.txt";
447 	const char RELPATH[] = "some_file.txt";
448 	const uint64_t ino = 42;
449 	const mode_t mode = 0755;
450 	uid_t uid;
451 	gid_t gid, newgid;
452 
453 	uid = geteuid();
454 	gid = getegid();
455 	newgid = excluded_group();
456 
457 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
458 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
459 	EXPECT_CALL(*m_mock, process(
460 		ResultOf([](auto in) {
461 			return (in.header.opcode == FUSE_SETATTR);
462 		}, Eq(true)),
463 		_)
464 	).Times(0);
465 
466 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
467 	EXPECT_EQ(EPERM, errno);
468 }
469 
470 TEST_F(Chgrp, ok)
471 {
472 	const char FULLPATH[] = "mountpoint/some_file.txt";
473 	const char RELPATH[] = "some_file.txt";
474 	const uint64_t ino = 42;
475 	const mode_t mode = 0755;
476 	uid_t uid;
477 	gid_t gid, newgid;
478 
479 	uid = geteuid();
480 	gid = 0;
481 	newgid = getegid();
482 
483 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
484 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
485 	/* The OS may optimize chgrp by omitting the redundant setattr */
486 	EXPECT_CALL(*m_mock, process(
487 		ResultOf([](auto in) {
488 			return (in.header.opcode == FUSE_SETATTR &&
489 				in.header.nodeid == ino);
490 		}, Eq(true)),
491 		_)
492 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
493 		SET_OUT_HEADER_LEN(out, attr);
494 		out.body.attr.attr.mode = S_IFREG | mode;
495 		out.body.attr.attr.uid = uid;
496 		out.body.attr.attr.gid = newgid;
497 	})));
498 
499 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
500 }
501 
502 /* A write by a non-owner should clear a file's SGID bit */
503 TEST_F(CopyFileRange, clear_sgid)
504 {
505 	const char FULLPATH_IN[] = "mountpoint/in.txt";
506 	const char RELPATH_IN[] = "in.txt";
507 	const char FULLPATH_OUT[] = "mountpoint/out.txt";
508 	const char RELPATH_OUT[] = "out.txt";
509 	struct stat sb;
510 	uint64_t ino_in = 42;
511 	uint64_t ino_out = 43;
512 	mode_t oldmode = 02777;
513 	mode_t newmode = 0777;
514 	off_t fsize = 16;
515 	off_t off_in = 0;
516 	off_t off_out = 8;
517 	off_t len = 8;
518 	int fd_in, fd_out;
519 
520 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
521 	FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
522 	    UINT64_MAX, 0, 0);
523 	expect_open(ino_in, 0, 1);
524 	FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
525 	    1, UINT64_MAX, 0, 0);
526 	expect_open(ino_out, 0, 1);
527 	expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
528 	expect_chmod(ino_out, newmode, fsize);
529 
530 	fd_in = open(FULLPATH_IN, O_RDONLY);
531 	ASSERT_LE(0, fd_in) << strerror(errno);
532 	fd_out = open(FULLPATH_OUT, O_WRONLY);
533 	ASSERT_LE(0, fd_out) << strerror(errno);
534 	ASSERT_EQ(len,
535 	    copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
536 	    << strerror(errno);
537 	ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
538 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
539 	ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
540 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
541 
542 	leak(fd_in);
543 	leak(fd_out);
544 }
545 
546 /* A write by a non-owner should clear a file's SUID bit */
547 TEST_F(CopyFileRange, clear_suid)
548 {
549 	const char FULLPATH_IN[] = "mountpoint/in.txt";
550 	const char RELPATH_IN[] = "in.txt";
551 	const char FULLPATH_OUT[] = "mountpoint/out.txt";
552 	const char RELPATH_OUT[] = "out.txt";
553 	struct stat sb;
554 	uint64_t ino_in = 42;
555 	uint64_t ino_out = 43;
556 	mode_t oldmode = 04777;
557 	mode_t newmode = 0777;
558 	off_t fsize = 16;
559 	off_t off_in = 0;
560 	off_t off_out = 8;
561 	off_t len = 8;
562 	int fd_in, fd_out;
563 
564 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
565 	FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
566 	    UINT64_MAX, 0, 0);
567 	expect_open(ino_in, 0, 1);
568 	FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
569 	    1, UINT64_MAX, 0, 0);
570 	expect_open(ino_out, 0, 1);
571 	expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
572 	expect_chmod(ino_out, newmode, fsize);
573 
574 	fd_in = open(FULLPATH_IN, O_RDONLY);
575 	ASSERT_LE(0, fd_in) << strerror(errno);
576 	fd_out = open(FULLPATH_OUT, O_WRONLY);
577 	ASSERT_LE(0, fd_out) << strerror(errno);
578 	ASSERT_EQ(len,
579 	    copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
580 	    << strerror(errno);
581 	ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
582 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
583 	ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
584 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
585 
586 	leak(fd_in);
587 	leak(fd_out);
588 }
589 
590 TEST_F(Create, ok)
591 {
592 	const char FULLPATH[] = "mountpoint/some_file.txt";
593 	const char RELPATH[] = "some_file.txt";
594 	uint64_t ino = 42;
595 	int fd;
596 
597 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
598 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
599 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
600 	expect_create(RELPATH, ino);
601 
602 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
603 	ASSERT_LE(0, fd) << strerror(errno);
604 	leak(fd);
605 }
606 
607 TEST_F(Create, eacces)
608 {
609 	const char FULLPATH[] = "mountpoint/some_file.txt";
610 	const char RELPATH[] = "some_file.txt";
611 
612 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
613 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
614 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
615 
616 	ASSERT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
617 	EXPECT_EQ(EACCES, errno);
618 }
619 
620 TEST_F(Deleteextattr, eacces)
621 {
622 	const char FULLPATH[] = "mountpoint/some_file.txt";
623 	const char RELPATH[] = "some_file.txt";
624 	uint64_t ino = 42;
625 	int ns = EXTATTR_NAMESPACE_USER;
626 
627 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
628 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
629 
630 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
631 	ASSERT_EQ(EACCES, errno);
632 }
633 
634 TEST_F(Deleteextattr, ok)
635 {
636 	const char FULLPATH[] = "mountpoint/some_file.txt";
637 	const char RELPATH[] = "some_file.txt";
638 	uint64_t ino = 42;
639 	int ns = EXTATTR_NAMESPACE_USER;
640 
641 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
642 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
643 	expect_removexattr();
644 
645 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
646 		<< strerror(errno);
647 }
648 
649 /* Delete system attributes requires superuser privilege */
650 TEST_F(Deleteextattr, system)
651 {
652 	const char FULLPATH[] = "mountpoint/some_file.txt";
653 	const char RELPATH[] = "some_file.txt";
654 	uint64_t ino = 42;
655 	int ns = EXTATTR_NAMESPACE_SYSTEM;
656 
657 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
658 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
659 
660 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
661 	ASSERT_EQ(EPERM, errno);
662 }
663 
664 /* Anybody with write permission can set both timestamps to UTIME_NOW */
665 TEST_F(Utimensat, utime_now)
666 {
667 	const char FULLPATH[] = "mountpoint/some_file.txt";
668 	const char RELPATH[] = "some_file.txt";
669 	const uint64_t ino = 42;
670 	/* Write permissions for everybody */
671 	const mode_t mode = 0666;
672 	uid_t owner = 0;
673 	const timespec times[2] = {
674 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
675 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
676 	};
677 
678 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
679 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
680 	EXPECT_CALL(*m_mock, process(
681 		ResultOf([](auto in) {
682 			return (in.header.opcode == FUSE_SETATTR &&
683 				in.header.nodeid == ino &&
684 				in.body.setattr.valid & FATTR_ATIME &&
685 				in.body.setattr.valid & FATTR_MTIME);
686 		}, Eq(true)),
687 		_)
688 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
689 		SET_OUT_HEADER_LEN(out, attr);
690 		out.body.attr.attr.mode = S_IFREG | mode;
691 	})));
692 
693 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
694 		<< strerror(errno);
695 }
696 
697 /* Anybody can set both timestamps to UTIME_OMIT */
698 TEST_F(Utimensat, utime_omit)
699 {
700 	const char FULLPATH[] = "mountpoint/some_file.txt";
701 	const char RELPATH[] = "some_file.txt";
702 	const uint64_t ino = 42;
703 	/* Write permissions for no one */
704 	const mode_t mode = 0444;
705 	uid_t owner = 0;
706 	const timespec times[2] = {
707 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
708 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
709 	};
710 
711 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
712 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
713 
714 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
715 		<< strerror(errno);
716 }
717 
718 /* Deleting user attributes merely requires WRITE privilege */
719 TEST_F(Deleteextattr, user)
720 {
721 	const char FULLPATH[] = "mountpoint/some_file.txt";
722 	const char RELPATH[] = "some_file.txt";
723 	uint64_t ino = 42;
724 	int ns = EXTATTR_NAMESPACE_USER;
725 
726 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
727 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
728 	expect_removexattr();
729 
730 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
731 		<< strerror(errno);
732 }
733 
734 TEST_F(Getextattr, eacces)
735 {
736 	const char FULLPATH[] = "mountpoint/some_file.txt";
737 	const char RELPATH[] = "some_file.txt";
738 	uint64_t ino = 42;
739 	char data[80];
740 	int ns = EXTATTR_NAMESPACE_USER;
741 
742 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
743 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
744 
745 	ASSERT_EQ(-1,
746 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
747 	ASSERT_EQ(EACCES, errno);
748 }
749 
750 TEST_F(Getextattr, ok)
751 {
752 	const char FULLPATH[] = "mountpoint/some_file.txt";
753 	const char RELPATH[] = "some_file.txt";
754 	uint64_t ino = 42;
755 	char data[80];
756 	const char value[] = "whatever";
757 	ssize_t value_len = strlen(value) + 1;
758 	int ns = EXTATTR_NAMESPACE_USER;
759 	ssize_t r;
760 
761 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
762 	/* Getting user attributes only requires read access */
763 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
764 	expect_getxattr(
765 		ReturnImmediate([&](auto in __unused, auto& out) {
766 			memcpy((void*)out.body.bytes, value, value_len);
767 			out.header.len = sizeof(out.header) + value_len;
768 		})
769 	);
770 
771 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
772 	ASSERT_EQ(value_len, r)  << strerror(errno);
773 	EXPECT_STREQ(value, data);
774 }
775 
776 /* Getting system attributes requires superuser privileges */
777 TEST_F(Getextattr, system)
778 {
779 	const char FULLPATH[] = "mountpoint/some_file.txt";
780 	const char RELPATH[] = "some_file.txt";
781 	uint64_t ino = 42;
782 	char data[80];
783 	int ns = EXTATTR_NAMESPACE_SYSTEM;
784 
785 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
786 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
787 
788 	ASSERT_EQ(-1,
789 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
790 	ASSERT_EQ(EPERM, errno);
791 }
792 
793 TEST_F(Listextattr, eacces)
794 {
795 	const char FULLPATH[] = "mountpoint/some_file.txt";
796 	const char RELPATH[] = "some_file.txt";
797 	uint64_t ino = 42;
798 	int ns = EXTATTR_NAMESPACE_USER;
799 
800 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
801 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
802 
803 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
804 	ASSERT_EQ(EACCES, errno);
805 }
806 
807 TEST_F(Listextattr, ok)
808 {
809 	const char FULLPATH[] = "mountpoint/some_file.txt";
810 	const char RELPATH[] = "some_file.txt";
811 	uint64_t ino = 42;
812 	int ns = EXTATTR_NAMESPACE_USER;
813 
814 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
815 	/* Listing user extended attributes merely requires read access */
816 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
817 	expect_listxattr();
818 
819 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
820 		<< strerror(errno);
821 }
822 
823 /* Listing system xattrs requires superuser privileges */
824 TEST_F(Listextattr, system)
825 {
826 	const char FULLPATH[] = "mountpoint/some_file.txt";
827 	const char RELPATH[] = "some_file.txt";
828 	uint64_t ino = 42;
829 	int ns = EXTATTR_NAMESPACE_SYSTEM;
830 
831 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
832 	/* Listing user extended attributes merely requires read access */
833 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
834 
835 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
836 	ASSERT_EQ(EPERM, errno);
837 }
838 
839 /* A write by a non-owner should clear a file's SGID bit */
840 TEST_F(Fspacectl, clear_sgid)
841 {
842 	const char FULLPATH[] = "mountpoint/file.txt";
843 	const char RELPATH[] = "file.txt";
844 	struct stat sb;
845 	struct spacectl_range rqsr;
846 	uint64_t ino = 42;
847 	mode_t oldmode = 02777;
848 	mode_t newmode = 0777;
849 	off_t fsize = 16;
850 	off_t off = 8;
851 	off_t len = 8;
852 	int fd;
853 
854 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
855 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
856 	    1, UINT64_MAX, 0, 0);
857 	expect_open(ino, 0, 1);
858 	expect_fallocate(ino, off, len,
859 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
860 	expect_chmod(ino, newmode, fsize);
861 
862 	fd = open(FULLPATH, O_WRONLY);
863 	ASSERT_LE(0, fd) << strerror(errno);
864 	rqsr.r_len = len;
865 	rqsr.r_offset = off;
866 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
867 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
868 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
869 
870 	leak(fd);
871 }
872 
873 /* A write by a non-owner should clear a file's SUID bit */
874 TEST_F(Fspacectl, clear_suid)
875 {
876 	const char FULLPATH[] = "mountpoint/file.txt";
877 	const char RELPATH[] = "file.txt";
878 	struct stat sb;
879 	struct spacectl_range rqsr;
880 	uint64_t ino = 42;
881 	mode_t oldmode = 04777;
882 	mode_t newmode = 0777;
883 	off_t fsize = 16;
884 	off_t off = 8;
885 	off_t len = 8;
886 	int fd;
887 
888 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
889 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
890 	    1, UINT64_MAX, 0, 0);
891 	expect_open(ino, 0, 1);
892 	expect_fallocate(ino, off, len,
893 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
894 	expect_chmod(ino, newmode, fsize);
895 
896 	fd = open(FULLPATH, O_WRONLY);
897 	ASSERT_LE(0, fd) << strerror(errno);
898 	rqsr.r_len = len;
899 	rqsr.r_offset = off;
900 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
901 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
902 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
903 
904 	leak(fd);
905 }
906 
907 /*
908  * fspacectl() of a file without writable permissions should succeed as
909  * long as the file descriptor is writable.  This is important when combined
910  * with O_CREAT
911  */
912 TEST_F(Fspacectl, posix_fallocate_of_newly_created_file)
913 {
914 	const char FULLPATH[] = "mountpoint/some_file.txt";
915 	const char RELPATH[] = "some_file.txt";
916 	struct spacectl_range rqsr;
917 	const uint64_t ino = 42;
918 	off_t off = 8;
919 	off_t len = 8;
920 	int fd;
921 
922 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
923 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
924 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
925 	expect_create(RELPATH, ino);
926 	expect_fallocate(ino, off, len,
927 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
928 
929 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
930 	ASSERT_LE(0, fd) << strerror(errno);
931 	rqsr.r_len = len;
932 	rqsr.r_offset = off;
933 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
934 	leak(fd);
935 }
936 
937 /* A component of the search path lacks execute permissions */
938 TEST_F(Lookup, eacces)
939 {
940 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
941 	const char RELDIRPATH[] = "some_dir";
942 	uint64_t dir_ino = 42;
943 
944 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
945 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
946 
947 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
948 	EXPECT_EQ(EACCES, errno);
949 }
950 
951 TEST_F(Open, eacces)
952 {
953 	const char FULLPATH[] = "mountpoint/some_file.txt";
954 	const char RELPATH[] = "some_file.txt";
955 	uint64_t ino = 42;
956 
957 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
958 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
959 
960 	EXPECT_EQ(-1, open(FULLPATH, O_RDWR));
961 	EXPECT_EQ(EACCES, errno);
962 }
963 
964 TEST_F(Open, ok)
965 {
966 	const char FULLPATH[] = "mountpoint/some_file.txt";
967 	const char RELPATH[] = "some_file.txt";
968 	uint64_t ino = 42;
969 	int fd;
970 
971 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
972 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
973 	expect_open(ino, 0, 1);
974 
975 	fd = open(FULLPATH, O_RDONLY);
976 	ASSERT_LE(0, fd) << strerror(errno);
977 	leak(fd);
978 }
979 
980 /* A write by a non-owner should clear a file's SGID bit */
981 TEST_F(PosixFallocate, clear_sgid)
982 {
983 	const char FULLPATH[] = "mountpoint/file.txt";
984 	const char RELPATH[] = "file.txt";
985 	struct stat sb;
986 	uint64_t ino = 42;
987 	mode_t oldmode = 02777;
988 	mode_t newmode = 0777;
989 	off_t fsize = 16;
990 	off_t off = 8;
991 	off_t len = 8;
992 	int fd;
993 
994 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
995 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
996 	    1, UINT64_MAX, 0, 0);
997 	expect_open(ino, 0, 1);
998 	expect_fallocate(ino, off, len, 0, 0);
999 	expect_chmod(ino, newmode, fsize);
1000 
1001 	fd = open(FULLPATH, O_WRONLY);
1002 	ASSERT_LE(0, fd) << strerror(errno);
1003 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1004 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1005 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1006 
1007 	leak(fd);
1008 }
1009 
1010 /* A write by a non-owner should clear a file's SUID bit */
1011 TEST_F(PosixFallocate, clear_suid)
1012 {
1013 	const char FULLPATH[] = "mountpoint/file.txt";
1014 	const char RELPATH[] = "file.txt";
1015 	struct stat sb;
1016 	uint64_t ino = 42;
1017 	mode_t oldmode = 04777;
1018 	mode_t newmode = 0777;
1019 	off_t fsize = 16;
1020 	off_t off = 8;
1021 	off_t len = 8;
1022 	int fd;
1023 
1024 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1025 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
1026 	    1, UINT64_MAX, 0, 0);
1027 	expect_open(ino, 0, 1);
1028 	expect_fallocate(ino, off, len, 0, 0);
1029 	expect_chmod(ino, newmode, fsize);
1030 
1031 	fd = open(FULLPATH, O_WRONLY);
1032 	ASSERT_LE(0, fd) << strerror(errno);
1033 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1034 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1035 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1036 
1037 	leak(fd);
1038 }
1039 
1040 /*
1041  * posix_fallocate() of a file without writable permissions should succeed as
1042  * long as the file descriptor is writable.  This is important when combined
1043  * with O_CREAT
1044  */
1045 TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file)
1046 {
1047 	const char FULLPATH[] = "mountpoint/some_file.txt";
1048 	const char RELPATH[] = "some_file.txt";
1049 	const uint64_t ino = 42;
1050 	off_t off = 8;
1051 	off_t len = 8;
1052 	int fd;
1053 
1054 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1055 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1056 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1057 	expect_create(RELPATH, ino);
1058 	expect_fallocate(ino, off, len, 0, 0);
1059 
1060 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1061 	ASSERT_LE(0, fd) << strerror(errno);
1062 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1063 	leak(fd);
1064 }
1065 
1066 TEST_F(Rename, eacces_on_srcdir)
1067 {
1068 	const char FULLDST[] = "mountpoint/d/dst";
1069 	const char RELDST[] = "d/dst";
1070 	const char FULLSRC[] = "mountpoint/src";
1071 	const char RELSRC[] = "src";
1072 	uint64_t ino = 42;
1073 
1074 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
1075 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1076 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1077 		.Times(AnyNumber())
1078 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
1079 
1080 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1081 	ASSERT_EQ(EACCES, errno);
1082 }
1083 
1084 TEST_F(Rename, eacces_on_dstdir_for_creating)
1085 {
1086 	const char FULLDST[] = "mountpoint/d/dst";
1087 	const char RELDSTDIR[] = "d";
1088 	const char RELDST[] = "dst";
1089 	const char FULLSRC[] = "mountpoint/src";
1090 	const char RELSRC[] = "src";
1091 	uint64_t src_ino = 42;
1092 	uint64_t dstdir_ino = 43;
1093 
1094 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1095 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1096 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1097 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1098 
1099 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1100 	ASSERT_EQ(EACCES, errno);
1101 }
1102 
1103 TEST_F(Rename, eacces_on_dstdir_for_removing)
1104 {
1105 	const char FULLDST[] = "mountpoint/d/dst";
1106 	const char RELDSTDIR[] = "d";
1107 	const char RELDST[] = "dst";
1108 	const char FULLSRC[] = "mountpoint/src";
1109 	const char RELSRC[] = "src";
1110 	uint64_t src_ino = 42;
1111 	uint64_t dstdir_ino = 43;
1112 
1113 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1114 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1115 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1116 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1117 
1118 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1119 	ASSERT_EQ(EACCES, errno);
1120 }
1121 
1122 TEST_F(Rename, eperm_on_sticky_srcdir)
1123 {
1124 	const char FULLDST[] = "mountpoint/d/dst";
1125 	const char FULLSRC[] = "mountpoint/src";
1126 	const char RELSRC[] = "src";
1127 	uint64_t ino = 42;
1128 
1129 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1130 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1131 
1132 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1133 	ASSERT_EQ(EPERM, errno);
1134 }
1135 
1136 /*
1137  * A user cannot move out a subdirectory that he does not own, because that
1138  * would require changing the subdirectory's ".." dirent
1139  */
1140 TEST_F(Rename, eperm_for_subdirectory)
1141 {
1142 	const char FULLDST[] = "mountpoint/d/dst";
1143 	const char FULLSRC[] = "mountpoint/src";
1144 	const char RELDSTDIR[] = "d";
1145 	const char RELDST[] = "dst";
1146 	const char RELSRC[] = "src";
1147 	uint64_t ino = 42;
1148 	uint64_t dstdir_ino = 43;
1149 
1150 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1151 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
1152 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
1153 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1154 
1155 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1156 	ASSERT_EQ(EACCES, errno);
1157 }
1158 
1159 /*
1160  * A user _can_ rename a subdirectory to which he lacks write permissions, if
1161  * it will keep the same parent
1162  */
1163 TEST_F(Rename, subdirectory_to_same_dir)
1164 {
1165 	const char FULLDST[] = "mountpoint/dst";
1166 	const char FULLSRC[] = "mountpoint/src";
1167 	const char RELDST[] = "dst";
1168 	const char RELSRC[] = "src";
1169 	uint64_t ino = 42;
1170 
1171 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1172 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
1173 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1174 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1175 	expect_rename(0);
1176 
1177 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1178 }
1179 
1180 TEST_F(Rename, eperm_on_sticky_dstdir)
1181 {
1182 	const char FULLDST[] = "mountpoint/d/dst";
1183 	const char RELDSTDIR[] = "d";
1184 	const char RELDST[] = "dst";
1185 	const char FULLSRC[] = "mountpoint/src";
1186 	const char RELSRC[] = "src";
1187 	uint64_t src_ino = 42;
1188 	uint64_t dstdir_ino = 43;
1189 	uint64_t dst_ino = 44;
1190 
1191 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1192 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1193 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
1194 	EXPECT_LOOKUP(dstdir_ino, RELDST)
1195 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1196 		SET_OUT_HEADER_LEN(out, entry);
1197 		out.body.entry.attr.mode = S_IFREG | 0644;
1198 		out.body.entry.nodeid = dst_ino;
1199 		out.body.entry.attr_valid = UINT64_MAX;
1200 		out.body.entry.entry_valid = UINT64_MAX;
1201 		out.body.entry.attr.uid = 0;
1202 	})));
1203 
1204 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1205 	ASSERT_EQ(EPERM, errno);
1206 }
1207 
1208 /* Successfully rename a file, overwriting the destination */
1209 TEST_F(Rename, ok)
1210 {
1211 	const char FULLDST[] = "mountpoint/dst";
1212 	const char RELDST[] = "dst";
1213 	const char FULLSRC[] = "mountpoint/src";
1214 	const char RELSRC[] = "src";
1215 	// The inode of the already-existing destination file
1216 	uint64_t dst_ino = 2;
1217 	uint64_t ino = 42;
1218 
1219 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
1220 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1221 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
1222 	expect_rename(0);
1223 
1224 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1225 }
1226 
1227 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
1228 {
1229 	const char FULLDST[] = "mountpoint/dst";
1230 	const char RELDST[] = "dst";
1231 	const char FULLSRC[] = "mountpoint/src";
1232 	const char RELSRC[] = "src";
1233 	uint64_t ino = 42;
1234 
1235 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1236 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1237 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1238 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1239 	expect_rename(0);
1240 
1241 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1242 }
1243 
1244 TEST_F(Setattr, ok)
1245 {
1246 	const char FULLPATH[] = "mountpoint/some_file.txt";
1247 	const char RELPATH[] = "some_file.txt";
1248 	const uint64_t ino = 42;
1249 	const mode_t oldmode = 0755;
1250 	const mode_t newmode = 0644;
1251 
1252 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1253 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1254 	EXPECT_CALL(*m_mock, process(
1255 		ResultOf([](auto in) {
1256 			return (in.header.opcode == FUSE_SETATTR &&
1257 				in.header.nodeid == ino &&
1258 				in.body.setattr.mode == newmode);
1259 		}, Eq(true)),
1260 		_)
1261 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1262 		SET_OUT_HEADER_LEN(out, attr);
1263 		out.body.attr.attr.mode = S_IFREG | newmode;
1264 	})));
1265 
1266 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1267 }
1268 
1269 TEST_F(Setattr, eacces)
1270 {
1271 	const char FULLPATH[] = "mountpoint/some_file.txt";
1272 	const char RELPATH[] = "some_file.txt";
1273 	const uint64_t ino = 42;
1274 	const mode_t oldmode = 0755;
1275 	const mode_t newmode = 0644;
1276 
1277 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1278 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
1279 	EXPECT_CALL(*m_mock, process(
1280 		ResultOf([](auto in) {
1281 			return (in.header.opcode == FUSE_SETATTR);
1282 		}, Eq(true)),
1283 		_)
1284 	).Times(0);
1285 
1286 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1287 	EXPECT_EQ(EPERM, errno);
1288 }
1289 
1290 /*
1291  * ftruncate() of a file without writable permissions should succeed as long as
1292  * the file descriptor is writable.  This is important when combined with
1293  * O_CREAT
1294  */
1295 TEST_F(Setattr, ftruncate_of_newly_created_file)
1296 {
1297 	const char FULLPATH[] = "mountpoint/some_file.txt";
1298 	const char RELPATH[] = "some_file.txt";
1299 	const uint64_t ino = 42;
1300 	const mode_t mode = 0000;
1301 	int fd;
1302 
1303 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1304 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1305 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1306 	expect_create(RELPATH, ino);
1307 	EXPECT_CALL(*m_mock, process(
1308 		ResultOf([](auto in) {
1309 			return (in.header.opcode == FUSE_SETATTR &&
1310 				in.header.nodeid == ino &&
1311 				(in.body.setattr.valid & FATTR_SIZE));
1312 		}, Eq(true)),
1313 		_)
1314 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1315 		SET_OUT_HEADER_LEN(out, attr);
1316 		out.body.attr.attr.ino = ino;
1317 		out.body.attr.attr.mode = S_IFREG | mode;
1318 		out.body.attr.attr_valid = UINT64_MAX;
1319 	})));
1320 
1321 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1322 	ASSERT_LE(0, fd) << strerror(errno);
1323 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
1324 	leak(fd);
1325 }
1326 
1327 /*
1328  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
1329  * to the file's group
1330  */
1331 TEST_F(Setattr, sgid_by_non_group_member)
1332 {
1333 	const char FULLPATH[] = "mountpoint/some_file.txt";
1334 	const char RELPATH[] = "some_file.txt";
1335 	const uint64_t ino = 42;
1336 	const mode_t oldmode = 0755;
1337 	const mode_t newmode = 02755;
1338 	uid_t uid = geteuid();
1339 	gid_t gid = excluded_group();
1340 
1341 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1342 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
1343 	EXPECT_CALL(*m_mock, process(
1344 		ResultOf([](auto in) {
1345 			return (in.header.opcode == FUSE_SETATTR);
1346 		}, Eq(true)),
1347 		_)
1348 	).Times(0);
1349 
1350 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1351 	EXPECT_EQ(EPERM, errno);
1352 }
1353 
1354 /* Only the superuser may set the sticky bit on a non-directory */
1355 TEST_F(Setattr, sticky_regular_file)
1356 {
1357 	const char FULLPATH[] = "mountpoint/some_file.txt";
1358 	const char RELPATH[] = "some_file.txt";
1359 	const uint64_t ino = 42;
1360 	const mode_t oldmode = 0644;
1361 	const mode_t newmode = 01644;
1362 
1363 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1364 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1365 	EXPECT_CALL(*m_mock, process(
1366 		ResultOf([](auto in) {
1367 			return (in.header.opcode == FUSE_SETATTR);
1368 		}, Eq(true)),
1369 		_)
1370 	).Times(0);
1371 
1372 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1373 	EXPECT_EQ(EFTYPE, errno);
1374 }
1375 
1376 TEST_F(Setextattr, ok)
1377 {
1378 	const char FULLPATH[] = "mountpoint/some_file.txt";
1379 	const char RELPATH[] = "some_file.txt";
1380 	uint64_t ino = 42;
1381 	const char value[] = "whatever";
1382 	ssize_t value_len = strlen(value) + 1;
1383 	int ns = EXTATTR_NAMESPACE_USER;
1384 	ssize_t r;
1385 
1386 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1387 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1388 	expect_setxattr(0);
1389 
1390 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1391 		value_len);
1392 	ASSERT_EQ(value_len, r) << strerror(errno);
1393 }
1394 
1395 TEST_F(Setextattr, eacces)
1396 {
1397 	const char FULLPATH[] = "mountpoint/some_file.txt";
1398 	const char RELPATH[] = "some_file.txt";
1399 	uint64_t ino = 42;
1400 	const char value[] = "whatever";
1401 	ssize_t value_len = strlen(value) + 1;
1402 	int ns = EXTATTR_NAMESPACE_USER;
1403 
1404 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1405 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1406 
1407 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1408 		value_len));
1409 	ASSERT_EQ(EACCES, errno);
1410 }
1411 
1412 // Setting system attributes requires superuser privileges
1413 TEST_F(Setextattr, system)
1414 {
1415 	const char FULLPATH[] = "mountpoint/some_file.txt";
1416 	const char RELPATH[] = "some_file.txt";
1417 	uint64_t ino = 42;
1418 	const char value[] = "whatever";
1419 	ssize_t value_len = strlen(value) + 1;
1420 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1421 
1422 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1423 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1424 
1425 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1426 		value_len));
1427 	ASSERT_EQ(EPERM, errno);
1428 }
1429 
1430 // Setting user attributes merely requires write privileges
1431 TEST_F(Setextattr, user)
1432 {
1433 	const char FULLPATH[] = "mountpoint/some_file.txt";
1434 	const char RELPATH[] = "some_file.txt";
1435 	uint64_t ino = 42;
1436 	const char value[] = "whatever";
1437 	ssize_t value_len = strlen(value) + 1;
1438 	int ns = EXTATTR_NAMESPACE_USER;
1439 	ssize_t r;
1440 
1441 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1442 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1443 	expect_setxattr(0);
1444 
1445 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1446 		value_len);
1447 	ASSERT_EQ(value_len, r) << strerror(errno);
1448 }
1449 
1450 TEST_F(Unlink, ok)
1451 {
1452 	const char FULLPATH[] = "mountpoint/some_file.txt";
1453 	const char RELPATH[] = "some_file.txt";
1454 	uint64_t ino = 42;
1455 	sem_t sem;
1456 
1457 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
1458 
1459 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1460 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1461 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
1462 	expect_forget(ino, 1, &sem);
1463 
1464 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1465 
1466 	sem_wait(&sem);
1467 	sem_destroy(&sem);
1468 }
1469 
1470 /*
1471  * Ensure that a cached name doesn't cause unlink to bypass permission checks
1472  * in VOP_LOOKUP.
1473  *
1474  * This test should pass because lookup(9) purges the namecache entry by doing
1475  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
1476  */
1477 TEST_F(Unlink, cached_unwritable_directory)
1478 {
1479 	const char FULLPATH[] = "mountpoint/some_file.txt";
1480 	const char RELPATH[] = "some_file.txt";
1481 	uint64_t ino = 42;
1482 
1483 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1484 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1485 	.Times(AnyNumber())
1486 	.WillRepeatedly(Invoke(
1487 		ReturnImmediate([=](auto i __unused, auto& out) {
1488 			SET_OUT_HEADER_LEN(out, entry);
1489 			out.body.entry.attr.mode = S_IFREG | 0644;
1490 			out.body.entry.nodeid = ino;
1491 			out.body.entry.entry_valid = UINT64_MAX;
1492 		}))
1493 	);
1494 
1495 	/* Fill name cache */
1496 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
1497 	/* Despite cached name , unlink should fail */
1498 	ASSERT_EQ(-1, unlink(FULLPATH));
1499 	ASSERT_EQ(EACCES, errno);
1500 }
1501 
1502 TEST_F(Unlink, unwritable_directory)
1503 {
1504 	const char FULLPATH[] = "mountpoint/some_file.txt";
1505 	const char RELPATH[] = "some_file.txt";
1506 	uint64_t ino = 42;
1507 
1508 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1509 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1510 
1511 	ASSERT_EQ(-1, unlink(FULLPATH));
1512 	ASSERT_EQ(EACCES, errno);
1513 }
1514 
1515 TEST_F(Unlink, sticky_directory)
1516 {
1517 	const char FULLPATH[] = "mountpoint/some_file.txt";
1518 	const char RELPATH[] = "some_file.txt";
1519 	uint64_t ino = 42;
1520 
1521 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1522 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1523 
1524 	ASSERT_EQ(-1, unlink(FULLPATH));
1525 	ASSERT_EQ(EPERM, errno);
1526 }
1527 
1528 /* A write by a non-owner should clear a file's SUID bit */
1529 TEST_F(Write, clear_suid)
1530 {
1531 	const char FULLPATH[] = "mountpoint/some_file.txt";
1532 	const char RELPATH[] = "some_file.txt";
1533 	struct stat sb;
1534 	uint64_t ino = 42;
1535 	mode_t oldmode = 04777;
1536 	mode_t newmode = 0777;
1537 	char wbuf[1] = {'x'};
1538 	int fd;
1539 
1540 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1541 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1542 	expect_open(ino, 0, 1);
1543 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1544 	expect_chmod(ino, newmode, sizeof(wbuf));
1545 
1546 	fd = open(FULLPATH, O_WRONLY);
1547 	ASSERT_LE(0, fd) << strerror(errno);
1548 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1549 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1550 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1551 	leak(fd);
1552 }
1553 
1554 /* A write by a non-owner should clear a file's SGID bit */
1555 TEST_F(Write, clear_sgid)
1556 {
1557 	const char FULLPATH[] = "mountpoint/some_file.txt";
1558 	const char RELPATH[] = "some_file.txt";
1559 	struct stat sb;
1560 	uint64_t ino = 42;
1561 	mode_t oldmode = 02777;
1562 	mode_t newmode = 0777;
1563 	char wbuf[1] = {'x'};
1564 	int fd;
1565 
1566 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1567 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1568 	expect_open(ino, 0, 1);
1569 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1570 	expect_chmod(ino, newmode, sizeof(wbuf));
1571 
1572 	fd = open(FULLPATH, O_WRONLY);
1573 	ASSERT_LE(0, fd) << strerror(errno);
1574 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1575 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1576 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1577 	leak(fd);
1578 }
1579 
1580 /* Regression test for a specific recurse-of-nonrecursive-lock panic
1581  *
1582  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
1583  * may panic.  That happens if the FUSE_SETATTR response indicates that the
1584  * file's size has changed since the write.
1585  */
1586 TEST_F(Write, recursion_panic_while_clearing_suid)
1587 {
1588 	const char FULLPATH[] = "mountpoint/some_file.txt";
1589 	const char RELPATH[] = "some_file.txt";
1590 	uint64_t ino = 42;
1591 	mode_t oldmode = 04777;
1592 	mode_t newmode = 0777;
1593 	char wbuf[1] = {'x'};
1594 	int fd;
1595 
1596 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1597 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1598 	expect_open(ino, 0, 1);
1599 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1600 	/* XXX Return a smaller file size than what we just wrote! */
1601 	expect_chmod(ino, newmode, 0);
1602 
1603 	fd = open(FULLPATH, O_WRONLY);
1604 	ASSERT_LE(0, fd) << strerror(errno);
1605 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1606 	leak(fd);
1607 }
1608