1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2019 The FreeBSD Foundation
5  *
6  * This software was developed by BFF Storage Systems, LLC under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 /*
32  * Tests for the "default_permissions" mount option.  They must be in their own
33  * file so they can be run as an unprivileged user.
34  */
35 
36 extern "C" {
37 #include <sys/types.h>
38 #include <sys/extattr.h>
39 
40 #include <fcntl.h>
41 #include <semaphore.h>
42 #include <unistd.h>
43 }
44 
45 #include "mockfs.hh"
46 #include "utils.hh"
47 
48 using namespace testing;
49 
50 class DefaultPermissions: public FuseTest {
51 
52 virtual void SetUp() {
53 	m_default_permissions = true;
54 	FuseTest::SetUp();
55 	if (HasFatalFailure() || IsSkipped())
56 		return;
57 
58 	if (geteuid() == 0) {
59 		GTEST_SKIP() << "This test requires an unprivileged user";
60 	}
61 
62 	/* With -o default_permissions, FUSE_ACCESS should never be called */
63 	EXPECT_CALL(*m_mock, process(
64 		ResultOf([=](auto in) {
65 			return (in.header.opcode == FUSE_ACCESS);
66 		}, Eq(true)),
67 		_)
68 	).Times(0);
69 }
70 
71 public:
72 void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0)
73 {
74 	EXPECT_CALL(*m_mock, process(
75 		ResultOf([=](auto in) {
76 			return (in.header.opcode == FUSE_SETATTR &&
77 				in.header.nodeid == ino &&
78 				in.body.setattr.valid == FATTR_MODE &&
79 				in.body.setattr.mode == mode);
80 		}, Eq(true)),
81 		_)
82 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
83 		SET_OUT_HEADER_LEN(out, attr);
84 		out.body.attr.attr.ino = ino;	// Must match nodeid
85 		out.body.attr.attr.mode = S_IFREG | mode;
86 		out.body.attr.attr.size = size;
87 		out.body.attr.attr_valid = UINT64_MAX;
88 	})));
89 }
90 
91 void expect_create(const char *relpath, uint64_t ino)
92 {
93 	EXPECT_CALL(*m_mock, process(
94 		ResultOf([=](auto in) {
95 			const char *name = (const char*)in.body.bytes +
96 				sizeof(fuse_create_in);
97 			return (in.header.opcode == FUSE_CREATE &&
98 				(0 == strcmp(relpath, name)));
99 		}, Eq(true)),
100 		_)
101 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
102 		SET_OUT_HEADER_LEN(out, create);
103 		out.body.create.entry.attr.mode = S_IFREG | 0644;
104 		out.body.create.entry.nodeid = ino;
105 		out.body.create.entry.entry_valid = UINT64_MAX;
106 		out.body.create.entry.attr_valid = UINT64_MAX;
107 	})));
108 }
109 
110 void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
111     uint64_t off_out, uint64_t len)
112 {
113 	EXPECT_CALL(*m_mock, process(
114 		ResultOf([=](auto in) {
115 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
116 				in.header.nodeid == ino_in &&
117 				in.body.copy_file_range.off_in == off_in &&
118 				in.body.copy_file_range.nodeid_out == ino_out &&
119 				in.body.copy_file_range.off_out == off_out &&
120 				in.body.copy_file_range.len == len);
121 		}, Eq(true)),
122 		_)
123 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
124 		SET_OUT_HEADER_LEN(out, write);
125 		out.body.write.size = len;
126 	})));
127 }
128 
129 void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
130 	uid_t uid = 0, gid_t gid = 0)
131 {
132 	EXPECT_CALL(*m_mock, process(
133 		ResultOf([=](auto in) {
134 			return (in.header.opcode == FUSE_GETATTR &&
135 				in.header.nodeid == ino);
136 		}, Eq(true)),
137 		_)
138 	).Times(times)
139 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
140 		SET_OUT_HEADER_LEN(out, attr);
141 		out.body.attr.attr.ino = ino;	// Must match nodeid
142 		out.body.attr.attr.mode = mode;
143 		out.body.attr.attr.size = 0;
144 		out.body.attr.attr.uid = uid;
145 		out.body.attr.attr.gid = gid;
146 		out.body.attr.attr_valid = attr_valid;
147 	})));
148 }
149 
150 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
151 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
152 {
153 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
154 }
155 
156 };
157 
158 class Access: public DefaultPermissions {};
159 class Chown: public DefaultPermissions {};
160 class Chgrp: public DefaultPermissions {};
161 class CopyFileRange: public DefaultPermissions {};
162 class Fspacectl: public DefaultPermissions {};
163 class Lookup: public DefaultPermissions {};
164 class Open: public DefaultPermissions {};
165 class PosixFallocate: public DefaultPermissions {};
166 class Read: 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 write by a non-owner should clear a file's SGID bit */
839 TEST_F(Fspacectl, clear_sgid)
840 {
841 	const char FULLPATH[] = "mountpoint/file.txt";
842 	const char RELPATH[] = "file.txt";
843 	struct stat sb;
844 	struct spacectl_range rqsr;
845 	uint64_t ino = 42;
846 	mode_t oldmode = 02777;
847 	mode_t newmode = 0777;
848 	off_t fsize = 16;
849 	off_t off = 8;
850 	off_t len = 8;
851 	int fd;
852 
853 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
854 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
855 	    1, UINT64_MAX, 0, 0);
856 	expect_open(ino, 0, 1);
857 	expect_fallocate(ino, off, len,
858 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
859 	expect_chmod(ino, newmode, fsize);
860 
861 	fd = open(FULLPATH, O_WRONLY);
862 	ASSERT_LE(0, fd) << strerror(errno);
863 	rqsr.r_len = len;
864 	rqsr.r_offset = off;
865 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
866 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
867 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
868 
869 	leak(fd);
870 }
871 
872 /* A write by a non-owner should clear a file's SUID bit */
873 TEST_F(Fspacectl, clear_suid)
874 {
875 	const char FULLPATH[] = "mountpoint/file.txt";
876 	const char RELPATH[] = "file.txt";
877 	struct stat sb;
878 	struct spacectl_range rqsr;
879 	uint64_t ino = 42;
880 	mode_t oldmode = 04777;
881 	mode_t newmode = 0777;
882 	off_t fsize = 16;
883 	off_t off = 8;
884 	off_t len = 8;
885 	int fd;
886 
887 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
888 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
889 	    1, UINT64_MAX, 0, 0);
890 	expect_open(ino, 0, 1);
891 	expect_fallocate(ino, off, len,
892 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
893 	expect_chmod(ino, newmode, fsize);
894 
895 	fd = open(FULLPATH, O_WRONLY);
896 	ASSERT_LE(0, fd) << strerror(errno);
897 	rqsr.r_len = len;
898 	rqsr.r_offset = off;
899 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
900 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
901 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
902 
903 	leak(fd);
904 }
905 
906 /*
907  * fspacectl() of a file without writable permissions should succeed as
908  * long as the file descriptor is writable.  This is important when combined
909  * with O_CREAT
910  */
911 TEST_F(Fspacectl, posix_fallocate_of_newly_created_file)
912 {
913 	const char FULLPATH[] = "mountpoint/some_file.txt";
914 	const char RELPATH[] = "some_file.txt";
915 	struct spacectl_range rqsr;
916 	const uint64_t ino = 42;
917 	off_t off = 8;
918 	off_t len = 8;
919 	int fd;
920 
921 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
922 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
923 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
924 	expect_create(RELPATH, ino);
925 	expect_fallocate(ino, off, len,
926 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
927 
928 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
929 	ASSERT_LE(0, fd) << strerror(errno);
930 	rqsr.r_len = len;
931 	rqsr.r_offset = off;
932 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
933 	leak(fd);
934 }
935 
936 /* A component of the search path lacks execute permissions */
937 TEST_F(Lookup, eacces)
938 {
939 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
940 	const char RELDIRPATH[] = "some_dir";
941 	uint64_t dir_ino = 42;
942 
943 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
944 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
945 
946 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
947 	EXPECT_EQ(EACCES, errno);
948 }
949 
950 TEST_F(Open, eacces)
951 {
952 	const char FULLPATH[] = "mountpoint/some_file.txt";
953 	const char RELPATH[] = "some_file.txt";
954 	uint64_t ino = 42;
955 
956 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
957 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
958 
959 	EXPECT_EQ(-1, open(FULLPATH, O_RDWR));
960 	EXPECT_EQ(EACCES, errno);
961 }
962 
963 TEST_F(Open, ok)
964 {
965 	const char FULLPATH[] = "mountpoint/some_file.txt";
966 	const char RELPATH[] = "some_file.txt";
967 	uint64_t ino = 42;
968 	int fd;
969 
970 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
971 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
972 	expect_open(ino, 0, 1);
973 
974 	fd = open(FULLPATH, O_RDONLY);
975 	ASSERT_LE(0, fd) << strerror(errno);
976 	leak(fd);
977 }
978 
979 /* A write by a non-owner should clear a file's SGID bit */
980 TEST_F(PosixFallocate, clear_sgid)
981 {
982 	const char FULLPATH[] = "mountpoint/file.txt";
983 	const char RELPATH[] = "file.txt";
984 	struct stat sb;
985 	uint64_t ino = 42;
986 	mode_t oldmode = 02777;
987 	mode_t newmode = 0777;
988 	off_t fsize = 16;
989 	off_t off = 8;
990 	off_t len = 8;
991 	int fd;
992 
993 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
994 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
995 	    1, UINT64_MAX, 0, 0);
996 	expect_open(ino, 0, 1);
997 	expect_fallocate(ino, off, len, 0, 0);
998 	expect_chmod(ino, newmode, fsize);
999 
1000 	fd = open(FULLPATH, O_WRONLY);
1001 	ASSERT_LE(0, fd) << strerror(errno);
1002 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1003 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1004 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1005 
1006 	leak(fd);
1007 }
1008 
1009 /* A write by a non-owner should clear a file's SUID bit */
1010 TEST_F(PosixFallocate, clear_suid)
1011 {
1012 	const char FULLPATH[] = "mountpoint/file.txt";
1013 	const char RELPATH[] = "file.txt";
1014 	struct stat sb;
1015 	uint64_t ino = 42;
1016 	mode_t oldmode = 04777;
1017 	mode_t newmode = 0777;
1018 	off_t fsize = 16;
1019 	off_t off = 8;
1020 	off_t len = 8;
1021 	int fd;
1022 
1023 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1024 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
1025 	    1, UINT64_MAX, 0, 0);
1026 	expect_open(ino, 0, 1);
1027 	expect_fallocate(ino, off, len, 0, 0);
1028 	expect_chmod(ino, newmode, fsize);
1029 
1030 	fd = open(FULLPATH, O_WRONLY);
1031 	ASSERT_LE(0, fd) << strerror(errno);
1032 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1033 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1034 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1035 
1036 	leak(fd);
1037 }
1038 
1039 /*
1040  * posix_fallocate() of a file without writable permissions should succeed as
1041  * long as the file descriptor is writable.  This is important when combined
1042  * with O_CREAT
1043  */
1044 TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file)
1045 {
1046 	const char FULLPATH[] = "mountpoint/some_file.txt";
1047 	const char RELPATH[] = "some_file.txt";
1048 	const uint64_t ino = 42;
1049 	off_t off = 8;
1050 	off_t len = 8;
1051 	int fd;
1052 
1053 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1054 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1055 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1056 	expect_create(RELPATH, ino);
1057 	expect_fallocate(ino, off, len, 0, 0);
1058 
1059 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1060 	ASSERT_LE(0, fd) << strerror(errno);
1061 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1062 	leak(fd);
1063 }
1064 
1065 TEST_F(Rename, eacces_on_srcdir)
1066 {
1067 	const char FULLDST[] = "mountpoint/d/dst";
1068 	const char RELDST[] = "d/dst";
1069 	const char FULLSRC[] = "mountpoint/src";
1070 	const char RELSRC[] = "src";
1071 	uint64_t ino = 42;
1072 
1073 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
1074 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1075 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1076 		.Times(AnyNumber())
1077 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
1078 
1079 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1080 	ASSERT_EQ(EACCES, errno);
1081 }
1082 
1083 TEST_F(Rename, eacces_on_dstdir_for_creating)
1084 {
1085 	const char FULLDST[] = "mountpoint/d/dst";
1086 	const char RELDSTDIR[] = "d";
1087 	const char RELDST[] = "dst";
1088 	const char FULLSRC[] = "mountpoint/src";
1089 	const char RELSRC[] = "src";
1090 	uint64_t src_ino = 42;
1091 	uint64_t dstdir_ino = 43;
1092 
1093 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1094 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1095 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1096 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1097 
1098 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1099 	ASSERT_EQ(EACCES, errno);
1100 }
1101 
1102 TEST_F(Rename, eacces_on_dstdir_for_removing)
1103 {
1104 	const char FULLDST[] = "mountpoint/d/dst";
1105 	const char RELDSTDIR[] = "d";
1106 	const char RELDST[] = "dst";
1107 	const char FULLSRC[] = "mountpoint/src";
1108 	const char RELSRC[] = "src";
1109 	uint64_t src_ino = 42;
1110 	uint64_t dstdir_ino = 43;
1111 
1112 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1113 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1114 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1115 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1116 
1117 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1118 	ASSERT_EQ(EACCES, errno);
1119 }
1120 
1121 TEST_F(Rename, eperm_on_sticky_srcdir)
1122 {
1123 	const char FULLDST[] = "mountpoint/d/dst";
1124 	const char FULLSRC[] = "mountpoint/src";
1125 	const char RELSRC[] = "src";
1126 	uint64_t ino = 42;
1127 
1128 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1129 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1130 
1131 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1132 	ASSERT_EQ(EPERM, errno);
1133 }
1134 
1135 /*
1136  * A user cannot move out a subdirectory that he does not own, because that
1137  * would require changing the subdirectory's ".." dirent
1138  */
1139 TEST_F(Rename, eperm_for_subdirectory)
1140 {
1141 	const char FULLDST[] = "mountpoint/d/dst";
1142 	const char FULLSRC[] = "mountpoint/src";
1143 	const char RELDSTDIR[] = "d";
1144 	const char RELDST[] = "dst";
1145 	const char RELSRC[] = "src";
1146 	uint64_t ino = 42;
1147 	uint64_t dstdir_ino = 43;
1148 
1149 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1150 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
1151 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
1152 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1153 
1154 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1155 	ASSERT_EQ(EACCES, errno);
1156 }
1157 
1158 /*
1159  * A user _can_ rename a subdirectory to which he lacks write permissions, if
1160  * it will keep the same parent
1161  */
1162 TEST_F(Rename, subdirectory_to_same_dir)
1163 {
1164 	const char FULLDST[] = "mountpoint/dst";
1165 	const char FULLSRC[] = "mountpoint/src";
1166 	const char RELDST[] = "dst";
1167 	const char RELSRC[] = "src";
1168 	uint64_t ino = 42;
1169 
1170 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1171 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
1172 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1173 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1174 	expect_rename(0);
1175 
1176 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1177 }
1178 
1179 TEST_F(Rename, eperm_on_sticky_dstdir)
1180 {
1181 	const char FULLDST[] = "mountpoint/d/dst";
1182 	const char RELDSTDIR[] = "d";
1183 	const char RELDST[] = "dst";
1184 	const char FULLSRC[] = "mountpoint/src";
1185 	const char RELSRC[] = "src";
1186 	uint64_t src_ino = 42;
1187 	uint64_t dstdir_ino = 43;
1188 	uint64_t dst_ino = 44;
1189 
1190 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1191 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1192 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
1193 	EXPECT_LOOKUP(dstdir_ino, RELDST)
1194 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1195 		SET_OUT_HEADER_LEN(out, entry);
1196 		out.body.entry.attr.mode = S_IFREG | 0644;
1197 		out.body.entry.nodeid = dst_ino;
1198 		out.body.entry.attr_valid = UINT64_MAX;
1199 		out.body.entry.entry_valid = UINT64_MAX;
1200 		out.body.entry.attr.uid = 0;
1201 	})));
1202 
1203 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1204 	ASSERT_EQ(EPERM, errno);
1205 }
1206 
1207 /* Successfully rename a file, overwriting the destination */
1208 TEST_F(Rename, ok)
1209 {
1210 	const char FULLDST[] = "mountpoint/dst";
1211 	const char RELDST[] = "dst";
1212 	const char FULLSRC[] = "mountpoint/src";
1213 	const char RELSRC[] = "src";
1214 	// The inode of the already-existing destination file
1215 	uint64_t dst_ino = 2;
1216 	uint64_t ino = 42;
1217 
1218 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
1219 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1220 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
1221 	expect_rename(0);
1222 
1223 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1224 }
1225 
1226 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
1227 {
1228 	const char FULLDST[] = "mountpoint/dst";
1229 	const char RELDST[] = "dst";
1230 	const char FULLSRC[] = "mountpoint/src";
1231 	const char RELSRC[] = "src";
1232 	uint64_t ino = 42;
1233 
1234 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1235 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1236 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1237 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1238 	expect_rename(0);
1239 
1240 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1241 }
1242 
1243 // Don't update atime during close after read, if we lack permissions to write
1244 // that file.
1245 TEST_F(Read, atime_during_close)
1246 {
1247 	const char FULLPATH[] = "mountpoint/some_file.txt";
1248 	const char RELPATH[] = "some_file.txt";
1249 	uint64_t ino = 42;
1250 	int fd;
1251 	ssize_t bufsize = 100;
1252 	uint8_t buf[bufsize];
1253 	const char *CONTENTS = "abcdefgh";
1254 	ssize_t fsize = sizeof(CONTENTS);
1255 
1256 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1257 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0755, fsize,
1258 		1, UINT64_MAX, 0, 0);
1259 	expect_open(ino, 0, 1);
1260 	expect_read(ino, 0, fsize, fsize, CONTENTS);
1261 	EXPECT_CALL(*m_mock, process(
1262 		ResultOf([&](auto in) {
1263 			return (in.header.opcode == FUSE_SETATTR);
1264 		}, Eq(true)),
1265 		_)
1266 	).Times(0);
1267 	expect_flush(ino, 1, ReturnErrno(0));
1268 	expect_release(ino, FuseTest::FH);
1269 
1270 	fd = open(FULLPATH, O_RDONLY);
1271 	ASSERT_LE(0, fd) << strerror(errno);
1272 
1273 	/* Ensure atime will be different than during lookup */
1274 	nap();
1275 
1276 	ASSERT_EQ(fsize, read(fd, buf, bufsize)) << strerror(errno);
1277 
1278 	close(fd);
1279 }
1280 
1281 TEST_F(Setattr, ok)
1282 {
1283 	const char FULLPATH[] = "mountpoint/some_file.txt";
1284 	const char RELPATH[] = "some_file.txt";
1285 	const uint64_t ino = 42;
1286 	const mode_t oldmode = 0755;
1287 	const mode_t newmode = 0644;
1288 
1289 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1290 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1291 	EXPECT_CALL(*m_mock, process(
1292 		ResultOf([](auto in) {
1293 			return (in.header.opcode == FUSE_SETATTR &&
1294 				in.header.nodeid == ino &&
1295 				in.body.setattr.mode == newmode);
1296 		}, Eq(true)),
1297 		_)
1298 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1299 		SET_OUT_HEADER_LEN(out, attr);
1300 		out.body.attr.attr.mode = S_IFREG | newmode;
1301 	})));
1302 
1303 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1304 }
1305 
1306 TEST_F(Setattr, eacces)
1307 {
1308 	const char FULLPATH[] = "mountpoint/some_file.txt";
1309 	const char RELPATH[] = "some_file.txt";
1310 	const uint64_t ino = 42;
1311 	const mode_t oldmode = 0755;
1312 	const mode_t newmode = 0644;
1313 
1314 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1315 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
1316 	EXPECT_CALL(*m_mock, process(
1317 		ResultOf([](auto in) {
1318 			return (in.header.opcode == FUSE_SETATTR);
1319 		}, Eq(true)),
1320 		_)
1321 	).Times(0);
1322 
1323 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1324 	EXPECT_EQ(EPERM, errno);
1325 }
1326 
1327 /*
1328  * ftruncate() of a file without writable permissions should succeed as long as
1329  * the file descriptor is writable.  This is important when combined with
1330  * O_CREAT
1331  */
1332 TEST_F(Setattr, ftruncate_of_newly_created_file)
1333 {
1334 	const char FULLPATH[] = "mountpoint/some_file.txt";
1335 	const char RELPATH[] = "some_file.txt";
1336 	const uint64_t ino = 42;
1337 	const mode_t mode = 0000;
1338 	int fd;
1339 
1340 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1341 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1342 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1343 	expect_create(RELPATH, ino);
1344 	EXPECT_CALL(*m_mock, process(
1345 		ResultOf([](auto in) {
1346 			return (in.header.opcode == FUSE_SETATTR &&
1347 				in.header.nodeid == ino &&
1348 				(in.body.setattr.valid & FATTR_SIZE));
1349 		}, Eq(true)),
1350 		_)
1351 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1352 		SET_OUT_HEADER_LEN(out, attr);
1353 		out.body.attr.attr.ino = ino;
1354 		out.body.attr.attr.mode = S_IFREG | mode;
1355 		out.body.attr.attr_valid = UINT64_MAX;
1356 	})));
1357 
1358 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1359 	ASSERT_LE(0, fd) << strerror(errno);
1360 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
1361 	leak(fd);
1362 }
1363 
1364 /*
1365  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
1366  * to the file's group
1367  */
1368 TEST_F(Setattr, sgid_by_non_group_member)
1369 {
1370 	const char FULLPATH[] = "mountpoint/some_file.txt";
1371 	const char RELPATH[] = "some_file.txt";
1372 	const uint64_t ino = 42;
1373 	const mode_t oldmode = 0755;
1374 	const mode_t newmode = 02755;
1375 	uid_t uid = geteuid();
1376 	gid_t gid = excluded_group();
1377 
1378 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1379 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
1380 	EXPECT_CALL(*m_mock, process(
1381 		ResultOf([](auto in) {
1382 			return (in.header.opcode == FUSE_SETATTR);
1383 		}, Eq(true)),
1384 		_)
1385 	).Times(0);
1386 
1387 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1388 	EXPECT_EQ(EPERM, errno);
1389 }
1390 
1391 /* Only the superuser may set the sticky bit on a non-directory */
1392 TEST_F(Setattr, sticky_regular_file)
1393 {
1394 	const char FULLPATH[] = "mountpoint/some_file.txt";
1395 	const char RELPATH[] = "some_file.txt";
1396 	const uint64_t ino = 42;
1397 	const mode_t oldmode = 0644;
1398 	const mode_t newmode = 01644;
1399 
1400 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1401 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1402 	EXPECT_CALL(*m_mock, process(
1403 		ResultOf([](auto in) {
1404 			return (in.header.opcode == FUSE_SETATTR);
1405 		}, Eq(true)),
1406 		_)
1407 	).Times(0);
1408 
1409 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1410 	EXPECT_EQ(EFTYPE, errno);
1411 }
1412 
1413 TEST_F(Setextattr, ok)
1414 {
1415 	const char FULLPATH[] = "mountpoint/some_file.txt";
1416 	const char RELPATH[] = "some_file.txt";
1417 	uint64_t ino = 42;
1418 	const char value[] = "whatever";
1419 	ssize_t value_len = strlen(value) + 1;
1420 	int ns = EXTATTR_NAMESPACE_USER;
1421 	ssize_t r;
1422 
1423 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1424 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1425 	expect_setxattr(0);
1426 
1427 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1428 		value_len);
1429 	ASSERT_EQ(value_len, r) << strerror(errno);
1430 }
1431 
1432 TEST_F(Setextattr, eacces)
1433 {
1434 	const char FULLPATH[] = "mountpoint/some_file.txt";
1435 	const char RELPATH[] = "some_file.txt";
1436 	uint64_t ino = 42;
1437 	const char value[] = "whatever";
1438 	ssize_t value_len = strlen(value) + 1;
1439 	int ns = EXTATTR_NAMESPACE_USER;
1440 
1441 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1442 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1443 
1444 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1445 		value_len));
1446 	ASSERT_EQ(EACCES, errno);
1447 }
1448 
1449 // Setting system attributes requires superuser privileges
1450 TEST_F(Setextattr, system)
1451 {
1452 	const char FULLPATH[] = "mountpoint/some_file.txt";
1453 	const char RELPATH[] = "some_file.txt";
1454 	uint64_t ino = 42;
1455 	const char value[] = "whatever";
1456 	ssize_t value_len = strlen(value) + 1;
1457 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1458 
1459 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1460 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1461 
1462 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1463 		value_len));
1464 	ASSERT_EQ(EPERM, errno);
1465 }
1466 
1467 // Setting user attributes merely requires write privileges
1468 TEST_F(Setextattr, user)
1469 {
1470 	const char FULLPATH[] = "mountpoint/some_file.txt";
1471 	const char RELPATH[] = "some_file.txt";
1472 	uint64_t ino = 42;
1473 	const char value[] = "whatever";
1474 	ssize_t value_len = strlen(value) + 1;
1475 	int ns = EXTATTR_NAMESPACE_USER;
1476 	ssize_t r;
1477 
1478 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1479 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1480 	expect_setxattr(0);
1481 
1482 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
1483 		value_len);
1484 	ASSERT_EQ(value_len, r) << strerror(errno);
1485 }
1486 
1487 TEST_F(Unlink, ok)
1488 {
1489 	const char FULLPATH[] = "mountpoint/some_file.txt";
1490 	const char RELPATH[] = "some_file.txt";
1491 	uint64_t ino = 42;
1492 	sem_t sem;
1493 
1494 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
1495 
1496 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1497 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1498 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
1499 	expect_forget(ino, 1, &sem);
1500 
1501 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1502 
1503 	sem_wait(&sem);
1504 	sem_destroy(&sem);
1505 }
1506 
1507 /*
1508  * Ensure that a cached name doesn't cause unlink to bypass permission checks
1509  * in VOP_LOOKUP.
1510  *
1511  * This test should pass because lookup(9) purges the namecache entry by doing
1512  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
1513  */
1514 TEST_F(Unlink, cached_unwritable_directory)
1515 {
1516 	const char FULLPATH[] = "mountpoint/some_file.txt";
1517 	const char RELPATH[] = "some_file.txt";
1518 	uint64_t ino = 42;
1519 
1520 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1521 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1522 	.Times(AnyNumber())
1523 	.WillRepeatedly(Invoke(
1524 		ReturnImmediate([=](auto i __unused, auto& out) {
1525 			SET_OUT_HEADER_LEN(out, entry);
1526 			out.body.entry.attr.mode = S_IFREG | 0644;
1527 			out.body.entry.nodeid = ino;
1528 			out.body.entry.entry_valid = UINT64_MAX;
1529 		}))
1530 	);
1531 
1532 	/* Fill name cache */
1533 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
1534 	/* Despite cached name , unlink should fail */
1535 	ASSERT_EQ(-1, unlink(FULLPATH));
1536 	ASSERT_EQ(EACCES, errno);
1537 }
1538 
1539 TEST_F(Unlink, unwritable_directory)
1540 {
1541 	const char FULLPATH[] = "mountpoint/some_file.txt";
1542 	const char RELPATH[] = "some_file.txt";
1543 	uint64_t ino = 42;
1544 
1545 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1546 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1547 
1548 	ASSERT_EQ(-1, unlink(FULLPATH));
1549 	ASSERT_EQ(EACCES, errno);
1550 }
1551 
1552 TEST_F(Unlink, sticky_directory)
1553 {
1554 	const char FULLPATH[] = "mountpoint/some_file.txt";
1555 	const char RELPATH[] = "some_file.txt";
1556 	uint64_t ino = 42;
1557 
1558 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1559 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1560 
1561 	ASSERT_EQ(-1, unlink(FULLPATH));
1562 	ASSERT_EQ(EPERM, errno);
1563 }
1564 
1565 /* A write by a non-owner should clear a file's SUID bit */
1566 TEST_F(Write, clear_suid)
1567 {
1568 	const char FULLPATH[] = "mountpoint/some_file.txt";
1569 	const char RELPATH[] = "some_file.txt";
1570 	struct stat sb;
1571 	uint64_t ino = 42;
1572 	mode_t oldmode = 04777;
1573 	mode_t newmode = 0777;
1574 	char wbuf[1] = {'x'};
1575 	int fd;
1576 
1577 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1578 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1579 	expect_open(ino, 0, 1);
1580 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1581 	expect_chmod(ino, newmode, sizeof(wbuf));
1582 
1583 	fd = open(FULLPATH, O_WRONLY);
1584 	ASSERT_LE(0, fd) << strerror(errno);
1585 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1586 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1587 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1588 	leak(fd);
1589 }
1590 
1591 /* A write by a non-owner should clear a file's SGID bit */
1592 TEST_F(Write, clear_sgid)
1593 {
1594 	const char FULLPATH[] = "mountpoint/some_file.txt";
1595 	const char RELPATH[] = "some_file.txt";
1596 	struct stat sb;
1597 	uint64_t ino = 42;
1598 	mode_t oldmode = 02777;
1599 	mode_t newmode = 0777;
1600 	char wbuf[1] = {'x'};
1601 	int fd;
1602 
1603 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1604 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1605 	expect_open(ino, 0, 1);
1606 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1607 	expect_chmod(ino, newmode, sizeof(wbuf));
1608 
1609 	fd = open(FULLPATH, O_WRONLY);
1610 	ASSERT_LE(0, fd) << strerror(errno);
1611 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1612 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1613 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1614 	leak(fd);
1615 }
1616 
1617 /* Regression test for a specific recurse-of-nonrecursive-lock panic
1618  *
1619  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
1620  * may panic.  That happens if the FUSE_SETATTR response indicates that the
1621  * file's size has changed since the write.
1622  */
1623 TEST_F(Write, recursion_panic_while_clearing_suid)
1624 {
1625 	const char FULLPATH[] = "mountpoint/some_file.txt";
1626 	const char RELPATH[] = "some_file.txt";
1627 	uint64_t ino = 42;
1628 	mode_t oldmode = 04777;
1629 	mode_t newmode = 0777;
1630 	char wbuf[1] = {'x'};
1631 	int fd;
1632 
1633 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1634 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1635 	expect_open(ino, 0, 1);
1636 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1637 	/* XXX Return a smaller file size than what we just wrote! */
1638 	expect_chmod(ino, newmode, 0);
1639 
1640 	fd = open(FULLPATH, O_WRONLY);
1641 	ASSERT_LE(0, fd) << strerror(errno);
1642 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1643 	leak(fd);
1644 }
1645