xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 6be3386466ab79a84b48429ae66244f21526d3df)
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 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_guid)
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 component of the search path lacks execute permissions */
838 TEST_F(Lookup, eacces)
839 {
840 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
841 	const char RELDIRPATH[] = "some_dir";
842 	uint64_t dir_ino = 42;
843 
844 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
845 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
846 
847 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
848 	EXPECT_EQ(EACCES, errno);
849 }
850 
851 TEST_F(Open, eacces)
852 {
853 	const char FULLPATH[] = "mountpoint/some_file.txt";
854 	const char RELPATH[] = "some_file.txt";
855 	uint64_t ino = 42;
856 
857 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
858 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
859 
860 	EXPECT_EQ(-1, open(FULLPATH, O_RDWR));
861 	EXPECT_EQ(EACCES, errno);
862 }
863 
864 TEST_F(Open, ok)
865 {
866 	const char FULLPATH[] = "mountpoint/some_file.txt";
867 	const char RELPATH[] = "some_file.txt";
868 	uint64_t ino = 42;
869 	int fd;
870 
871 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
872 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
873 	expect_open(ino, 0, 1);
874 
875 	fd = open(FULLPATH, O_RDONLY);
876 	ASSERT_LE(0, fd) << strerror(errno);
877 	leak(fd);
878 }
879 
880 TEST_F(Rename, eacces_on_srcdir)
881 {
882 	const char FULLDST[] = "mountpoint/d/dst";
883 	const char RELDST[] = "d/dst";
884 	const char FULLSRC[] = "mountpoint/src";
885 	const char RELSRC[] = "src";
886 	uint64_t ino = 42;
887 
888 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
889 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
890 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
891 		.Times(AnyNumber())
892 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
893 
894 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
895 	ASSERT_EQ(EACCES, errno);
896 }
897 
898 TEST_F(Rename, eacces_on_dstdir_for_creating)
899 {
900 	const char FULLDST[] = "mountpoint/d/dst";
901 	const char RELDSTDIR[] = "d";
902 	const char RELDST[] = "dst";
903 	const char FULLSRC[] = "mountpoint/src";
904 	const char RELSRC[] = "src";
905 	uint64_t src_ino = 42;
906 	uint64_t dstdir_ino = 43;
907 
908 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
909 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
910 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
911 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
912 
913 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
914 	ASSERT_EQ(EACCES, errno);
915 }
916 
917 TEST_F(Rename, eacces_on_dstdir_for_removing)
918 {
919 	const char FULLDST[] = "mountpoint/d/dst";
920 	const char RELDSTDIR[] = "d";
921 	const char RELDST[] = "dst";
922 	const char FULLSRC[] = "mountpoint/src";
923 	const char RELSRC[] = "src";
924 	uint64_t src_ino = 42;
925 	uint64_t dstdir_ino = 43;
926 
927 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
928 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
929 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
930 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
931 
932 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
933 	ASSERT_EQ(EACCES, errno);
934 }
935 
936 TEST_F(Rename, eperm_on_sticky_srcdir)
937 {
938 	const char FULLDST[] = "mountpoint/d/dst";
939 	const char FULLSRC[] = "mountpoint/src";
940 	const char RELSRC[] = "src";
941 	uint64_t ino = 42;
942 
943 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
944 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
945 
946 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
947 	ASSERT_EQ(EPERM, errno);
948 }
949 
950 /*
951  * A user cannot move out a subdirectory that he does not own, because that
952  * would require changing the subdirectory's ".." dirent
953  */
954 TEST_F(Rename, eperm_for_subdirectory)
955 {
956 	const char FULLDST[] = "mountpoint/d/dst";
957 	const char FULLSRC[] = "mountpoint/src";
958 	const char RELDSTDIR[] = "d";
959 	const char RELDST[] = "dst";
960 	const char RELSRC[] = "src";
961 	uint64_t ino = 42;
962 	uint64_t dstdir_ino = 43;
963 
964 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
965 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
966 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
967 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
968 
969 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
970 	ASSERT_EQ(EACCES, errno);
971 }
972 
973 /*
974  * A user _can_ rename a subdirectory to which he lacks write permissions, if
975  * it will keep the same parent
976  */
977 TEST_F(Rename, subdirectory_to_same_dir)
978 {
979 	const char FULLDST[] = "mountpoint/dst";
980 	const char FULLSRC[] = "mountpoint/src";
981 	const char RELDST[] = "dst";
982 	const char RELSRC[] = "src";
983 	uint64_t ino = 42;
984 
985 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
986 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
987 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
988 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
989 	expect_rename(0);
990 
991 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
992 }
993 
994 TEST_F(Rename, eperm_on_sticky_dstdir)
995 {
996 	const char FULLDST[] = "mountpoint/d/dst";
997 	const char RELDSTDIR[] = "d";
998 	const char RELDST[] = "dst";
999 	const char FULLSRC[] = "mountpoint/src";
1000 	const char RELSRC[] = "src";
1001 	uint64_t src_ino = 42;
1002 	uint64_t dstdir_ino = 43;
1003 	uint64_t dst_ino = 44;
1004 
1005 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1006 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1007 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
1008 	EXPECT_LOOKUP(dstdir_ino, RELDST)
1009 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1010 		SET_OUT_HEADER_LEN(out, entry);
1011 		out.body.entry.attr.mode = S_IFREG | 0644;
1012 		out.body.entry.nodeid = dst_ino;
1013 		out.body.entry.attr_valid = UINT64_MAX;
1014 		out.body.entry.entry_valid = UINT64_MAX;
1015 		out.body.entry.attr.uid = 0;
1016 	})));
1017 
1018 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1019 	ASSERT_EQ(EPERM, errno);
1020 }
1021 
1022 /* Successfully rename a file, overwriting the destination */
1023 TEST_F(Rename, ok)
1024 {
1025 	const char FULLDST[] = "mountpoint/dst";
1026 	const char RELDST[] = "dst";
1027 	const char FULLSRC[] = "mountpoint/src";
1028 	const char RELSRC[] = "src";
1029 	// The inode of the already-existing destination file
1030 	uint64_t dst_ino = 2;
1031 	uint64_t ino = 42;
1032 
1033 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
1034 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1035 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
1036 	expect_rename(0);
1037 
1038 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1039 }
1040 
1041 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
1042 {
1043 	const char FULLDST[] = "mountpoint/dst";
1044 	const char RELDST[] = "dst";
1045 	const char FULLSRC[] = "mountpoint/src";
1046 	const char RELSRC[] = "src";
1047 	uint64_t ino = 42;
1048 
1049 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1050 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1051 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1052 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1053 	expect_rename(0);
1054 
1055 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1056 }
1057 
1058 TEST_F(Setattr, ok)
1059 {
1060 	const char FULLPATH[] = "mountpoint/some_file.txt";
1061 	const char RELPATH[] = "some_file.txt";
1062 	const uint64_t ino = 42;
1063 	const mode_t oldmode = 0755;
1064 	const mode_t newmode = 0644;
1065 
1066 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1067 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1068 	EXPECT_CALL(*m_mock, process(
1069 		ResultOf([](auto in) {
1070 			return (in.header.opcode == FUSE_SETATTR &&
1071 				in.header.nodeid == ino &&
1072 				in.body.setattr.mode == newmode);
1073 		}, Eq(true)),
1074 		_)
1075 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1076 		SET_OUT_HEADER_LEN(out, attr);
1077 		out.body.attr.attr.mode = S_IFREG | newmode;
1078 	})));
1079 
1080 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1081 }
1082 
1083 TEST_F(Setattr, eacces)
1084 {
1085 	const char FULLPATH[] = "mountpoint/some_file.txt";
1086 	const char RELPATH[] = "some_file.txt";
1087 	const uint64_t ino = 42;
1088 	const mode_t oldmode = 0755;
1089 	const mode_t newmode = 0644;
1090 
1091 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1092 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
1093 	EXPECT_CALL(*m_mock, process(
1094 		ResultOf([](auto in) {
1095 			return (in.header.opcode == FUSE_SETATTR);
1096 		}, Eq(true)),
1097 		_)
1098 	).Times(0);
1099 
1100 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1101 	EXPECT_EQ(EPERM, errno);
1102 }
1103 
1104 /*
1105  * ftruncate() of a file without writable permissions should succeed as long as
1106  * the file descriptor is writable.  This is important when combined with
1107  * O_CREAT
1108  */
1109 TEST_F(Setattr, ftruncate_of_newly_created_file)
1110 {
1111 	const char FULLPATH[] = "mountpoint/some_file.txt";
1112 	const char RELPATH[] = "some_file.txt";
1113 	const uint64_t ino = 42;
1114 	const mode_t mode = 0000;
1115 	int fd;
1116 
1117 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1118 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1119 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1120 	expect_create(RELPATH, ino);
1121 	EXPECT_CALL(*m_mock, process(
1122 		ResultOf([](auto in) {
1123 			return (in.header.opcode == FUSE_SETATTR &&
1124 				in.header.nodeid == ino &&
1125 				(in.body.setattr.valid & FATTR_SIZE));
1126 		}, Eq(true)),
1127 		_)
1128 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1129 		SET_OUT_HEADER_LEN(out, attr);
1130 		out.body.attr.attr.ino = ino;
1131 		out.body.attr.attr.mode = S_IFREG | mode;
1132 		out.body.attr.attr_valid = UINT64_MAX;
1133 	})));
1134 
1135 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1136 	ASSERT_LE(0, fd) << strerror(errno);
1137 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
1138 	leak(fd);
1139 }
1140 
1141 /*
1142  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
1143  * to the file's group
1144  */
1145 TEST_F(Setattr, sgid_by_non_group_member)
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 = 02755;
1152 	uid_t uid = geteuid();
1153 	gid_t gid = excluded_group();
1154 
1155 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1156 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
1157 	EXPECT_CALL(*m_mock, process(
1158 		ResultOf([](auto in) {
1159 			return (in.header.opcode == FUSE_SETATTR);
1160 		}, Eq(true)),
1161 		_)
1162 	).Times(0);
1163 
1164 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1165 	EXPECT_EQ(EPERM, errno);
1166 }
1167 
1168 /* Only the superuser may set the sticky bit on a non-directory */
1169 TEST_F(Setattr, sticky_regular_file)
1170 {
1171 	const char FULLPATH[] = "mountpoint/some_file.txt";
1172 	const char RELPATH[] = "some_file.txt";
1173 	const uint64_t ino = 42;
1174 	const mode_t oldmode = 0644;
1175 	const mode_t newmode = 01644;
1176 
1177 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1178 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1179 	EXPECT_CALL(*m_mock, process(
1180 		ResultOf([](auto in) {
1181 			return (in.header.opcode == FUSE_SETATTR);
1182 		}, Eq(true)),
1183 		_)
1184 	).Times(0);
1185 
1186 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1187 	EXPECT_EQ(EFTYPE, errno);
1188 }
1189 
1190 TEST_F(Setextattr, ok)
1191 {
1192 	const char FULLPATH[] = "mountpoint/some_file.txt";
1193 	const char RELPATH[] = "some_file.txt";
1194 	uint64_t ino = 42;
1195 	const char value[] = "whatever";
1196 	ssize_t value_len = strlen(value) + 1;
1197 	int ns = EXTATTR_NAMESPACE_USER;
1198 	ssize_t r;
1199 
1200 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1201 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1202 	expect_setxattr(0);
1203 
1204 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1205 		value_len);
1206 	ASSERT_EQ(value_len, r) << strerror(errno);
1207 }
1208 
1209 TEST_F(Setextattr, eacces)
1210 {
1211 	const char FULLPATH[] = "mountpoint/some_file.txt";
1212 	const char RELPATH[] = "some_file.txt";
1213 	uint64_t ino = 42;
1214 	const char value[] = "whatever";
1215 	ssize_t value_len = strlen(value) + 1;
1216 	int ns = EXTATTR_NAMESPACE_USER;
1217 
1218 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1219 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1220 
1221 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1222 		value_len));
1223 	ASSERT_EQ(EACCES, errno);
1224 }
1225 
1226 // Setting system attributes requires superuser privileges
1227 TEST_F(Setextattr, system)
1228 {
1229 	const char FULLPATH[] = "mountpoint/some_file.txt";
1230 	const char RELPATH[] = "some_file.txt";
1231 	uint64_t ino = 42;
1232 	const char value[] = "whatever";
1233 	ssize_t value_len = strlen(value) + 1;
1234 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1235 
1236 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1237 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1238 
1239 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1240 		value_len));
1241 	ASSERT_EQ(EPERM, errno);
1242 }
1243 
1244 // Setting user attributes merely requires write privileges
1245 TEST_F(Setextattr, user)
1246 {
1247 	const char FULLPATH[] = "mountpoint/some_file.txt";
1248 	const char RELPATH[] = "some_file.txt";
1249 	uint64_t ino = 42;
1250 	const char value[] = "whatever";
1251 	ssize_t value_len = strlen(value) + 1;
1252 	int ns = EXTATTR_NAMESPACE_USER;
1253 	ssize_t r;
1254 
1255 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1256 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1257 	expect_setxattr(0);
1258 
1259 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1260 		value_len);
1261 	ASSERT_EQ(value_len, r) << strerror(errno);
1262 }
1263 
1264 TEST_F(Unlink, ok)
1265 {
1266 	const char FULLPATH[] = "mountpoint/some_file.txt";
1267 	const char RELPATH[] = "some_file.txt";
1268 	uint64_t ino = 42;
1269 	sem_t sem;
1270 
1271 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
1272 
1273 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1274 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1275 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
1276 	expect_forget(ino, 1, &sem);
1277 
1278 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1279 
1280 	sem_wait(&sem);
1281 	sem_destroy(&sem);
1282 }
1283 
1284 /*
1285  * Ensure that a cached name doesn't cause unlink to bypass permission checks
1286  * in VOP_LOOKUP.
1287  *
1288  * This test should pass because lookup(9) purges the namecache entry by doing
1289  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
1290  */
1291 TEST_F(Unlink, cached_unwritable_directory)
1292 {
1293 	const char FULLPATH[] = "mountpoint/some_file.txt";
1294 	const char RELPATH[] = "some_file.txt";
1295 	uint64_t ino = 42;
1296 
1297 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1298 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1299 	.Times(AnyNumber())
1300 	.WillRepeatedly(Invoke(
1301 		ReturnImmediate([=](auto i __unused, auto& out) {
1302 			SET_OUT_HEADER_LEN(out, entry);
1303 			out.body.entry.attr.mode = S_IFREG | 0644;
1304 			out.body.entry.nodeid = ino;
1305 			out.body.entry.entry_valid = UINT64_MAX;
1306 		}))
1307 	);
1308 
1309 	/* Fill name cache */
1310 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
1311 	/* Despite cached name , unlink should fail */
1312 	ASSERT_EQ(-1, unlink(FULLPATH));
1313 	ASSERT_EQ(EACCES, errno);
1314 }
1315 
1316 TEST_F(Unlink, unwritable_directory)
1317 {
1318 	const char FULLPATH[] = "mountpoint/some_file.txt";
1319 	const char RELPATH[] = "some_file.txt";
1320 	uint64_t ino = 42;
1321 
1322 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1323 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1324 
1325 	ASSERT_EQ(-1, unlink(FULLPATH));
1326 	ASSERT_EQ(EACCES, errno);
1327 }
1328 
1329 TEST_F(Unlink, sticky_directory)
1330 {
1331 	const char FULLPATH[] = "mountpoint/some_file.txt";
1332 	const char RELPATH[] = "some_file.txt";
1333 	uint64_t ino = 42;
1334 
1335 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1336 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1337 
1338 	ASSERT_EQ(-1, unlink(FULLPATH));
1339 	ASSERT_EQ(EPERM, errno);
1340 }
1341 
1342 /* A write by a non-owner should clear a file's SUID bit */
1343 TEST_F(Write, clear_suid)
1344 {
1345 	const char FULLPATH[] = "mountpoint/some_file.txt";
1346 	const char RELPATH[] = "some_file.txt";
1347 	struct stat sb;
1348 	uint64_t ino = 42;
1349 	mode_t oldmode = 04777;
1350 	mode_t newmode = 0777;
1351 	char wbuf[1] = {'x'};
1352 	int fd;
1353 
1354 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1355 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1356 	expect_open(ino, 0, 1);
1357 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1358 	expect_chmod(ino, newmode, sizeof(wbuf));
1359 
1360 	fd = open(FULLPATH, O_WRONLY);
1361 	ASSERT_LE(0, fd) << strerror(errno);
1362 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1363 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1364 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1365 	leak(fd);
1366 }
1367 
1368 /* A write by a non-owner should clear a file's SGID bit */
1369 TEST_F(Write, clear_sgid)
1370 {
1371 	const char FULLPATH[] = "mountpoint/some_file.txt";
1372 	const char RELPATH[] = "some_file.txt";
1373 	struct stat sb;
1374 	uint64_t ino = 42;
1375 	mode_t oldmode = 02777;
1376 	mode_t newmode = 0777;
1377 	char wbuf[1] = {'x'};
1378 	int fd;
1379 
1380 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1381 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1382 	expect_open(ino, 0, 1);
1383 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1384 	expect_chmod(ino, newmode, sizeof(wbuf));
1385 
1386 	fd = open(FULLPATH, O_WRONLY);
1387 	ASSERT_LE(0, fd) << strerror(errno);
1388 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1389 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1390 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1391 	leak(fd);
1392 }
1393 
1394 /* Regression test for a specific recurse-of-nonrecursive-lock panic
1395  *
1396  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
1397  * may panic.  That happens if the FUSE_SETATTR response indicates that the
1398  * file's size has changed since the write.
1399  */
1400 TEST_F(Write, recursion_panic_while_clearing_suid)
1401 {
1402 	const char FULLPATH[] = "mountpoint/some_file.txt";
1403 	const char RELPATH[] = "some_file.txt";
1404 	uint64_t ino = 42;
1405 	mode_t oldmode = 04777;
1406 	mode_t newmode = 0777;
1407 	char wbuf[1] = {'x'};
1408 	int fd;
1409 
1410 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1411 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1412 	expect_open(ino, 0, 1);
1413 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1414 	/* XXX Return a smaller file size than what we just wrote! */
1415 	expect_chmod(ino, newmode, 0);
1416 
1417 	fd = open(FULLPATH, O_WRONLY);
1418 	ASSERT_LE(0, fd) << strerror(errno);
1419 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1420 	leak(fd);
1421 }
1422