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