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
36extern "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
48using namespace testing;
49
50class DefaultPermissions: public FuseTest {
51
52virtual 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
71public:
72void 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
91void 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
110void 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
129void 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
150void 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
158class Access: public DefaultPermissions {};
159class Chown: public DefaultPermissions {};
160class Chgrp: public DefaultPermissions {};
161class CopyFileRange: public DefaultPermissions {};
162class Fspacectl: public DefaultPermissions {};
163class Lookup: public DefaultPermissions {};
164class Open: public DefaultPermissions {};
165class PosixFallocate: public DefaultPermissions {};
166class Read: public DefaultPermissions {};
167class Setattr: public DefaultPermissions {};
168class Unlink: public DefaultPermissions {};
169class Utimensat: public DefaultPermissions {};
170class 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 */
177class Create: public DefaultPermissions {};
178
179class Deleteextattr: public DefaultPermissions {
180public:
181void 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
192class Getextattr: public DefaultPermissions {
193public:
194void 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
205class Listextattr: public DefaultPermissions {
206public:
207void 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
221class Rename: public DefaultPermissions {
222public:
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
238class Setextattr: public DefaultPermissions {
239public:
240void 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 */
252static 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
272TEST_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
286TEST_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
306TEST_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 */
324TEST_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 */
355TEST_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 */
387TEST_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 */
411TEST_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 */
443TEST_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
469TEST_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 */
502TEST_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 */
546TEST_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
589TEST_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
606TEST_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
619TEST_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
633TEST_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 */
649TEST_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 */
664TEST_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 */
697TEST_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 */
718TEST_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
733TEST_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
749TEST_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 */
776TEST_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
792TEST_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
806TEST_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 */
823TEST_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 */
839TEST_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 */
873TEST_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 */
911TEST_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 */
937TEST_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
950TEST_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
963TEST_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 */
980TEST_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 */
1010TEST_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 */
1044TEST_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
1065TEST_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
1083TEST_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
1102TEST_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
1121TEST_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 */
1139TEST_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 */
1162TEST_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
1179TEST_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 */
1208TEST_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
1226TEST_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.
1245TEST_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
1281TEST_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
1306TEST_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 */
1332TEST_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 */
1368TEST_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 */
1392TEST_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
1413TEST_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
1432TEST_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
1450TEST_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
1468TEST_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
1487TEST_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 */
1514TEST_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
1539TEST_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
1552TEST_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 */
1566TEST_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 */
1592TEST_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 */
1623TEST_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