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