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