xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 398c88c7582a195cbfeb689ceff1400cc717673f)
19821f1d3SAlan Somers /*-
29821f1d3SAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
39821f1d3SAlan Somers  *
49821f1d3SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers  *
69821f1d3SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers  * from the FreeBSD Foundation.
89821f1d3SAlan Somers  *
99821f1d3SAlan Somers  * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers  * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers  * are met:
129821f1d3SAlan Somers  * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers  *    documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers  *
189821f1d3SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers  * SUCH DAMAGE.
291fa8ebfbSAlan Somers  *
301fa8ebfbSAlan Somers  * $FreeBSD$
319821f1d3SAlan Somers  */
329821f1d3SAlan Somers 
339821f1d3SAlan Somers /*
349821f1d3SAlan Somers  * Tests for the "default_permissions" mount option.  They must be in their own
359821f1d3SAlan Somers  * file so they can be run as an unprivileged user
369821f1d3SAlan Somers  */
379821f1d3SAlan Somers 
389821f1d3SAlan Somers extern "C" {
39ff4fbdf5SAlan Somers #include <sys/types.h>
40ff4fbdf5SAlan Somers #include <sys/extattr.h>
41ff4fbdf5SAlan Somers 
429821f1d3SAlan Somers #include <fcntl.h>
43331884f2SAlan Somers #include <semaphore.h>
449821f1d3SAlan Somers #include <unistd.h>
459821f1d3SAlan Somers }
469821f1d3SAlan Somers 
479821f1d3SAlan Somers #include "mockfs.hh"
489821f1d3SAlan Somers #include "utils.hh"
499821f1d3SAlan Somers 
509821f1d3SAlan Somers using namespace testing;
519821f1d3SAlan Somers 
529821f1d3SAlan Somers class DefaultPermissions: public FuseTest {
539821f1d3SAlan Somers 
549821f1d3SAlan Somers virtual void SetUp() {
55ff4fbdf5SAlan Somers 	m_default_permissions = true;
569821f1d3SAlan Somers 	FuseTest::SetUp();
57ff4fbdf5SAlan Somers 	if (HasFatalFailure() || IsSkipped())
58ff4fbdf5SAlan Somers 		return;
599821f1d3SAlan Somers 
609821f1d3SAlan Somers 	if (geteuid() == 0) {
619821f1d3SAlan Somers 		GTEST_SKIP() << "This test requires an unprivileged user";
629821f1d3SAlan Somers 	}
63ff4fbdf5SAlan Somers 
64ff4fbdf5SAlan Somers 	/* With -o default_permissions, FUSE_ACCESS should never be called */
65ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
66ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
6729edc611SAlan Somers 			return (in.header.opcode == FUSE_ACCESS);
68ff4fbdf5SAlan Somers 		}, Eq(true)),
69ff4fbdf5SAlan Somers 		_)
70ff4fbdf5SAlan Somers 	).Times(0);
719821f1d3SAlan Somers }
729821f1d3SAlan Somers 
739821f1d3SAlan Somers public:
7418a2264eSAlan Somers void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0)
75a90e32deSAlan Somers {
76a90e32deSAlan Somers 	EXPECT_CALL(*m_mock, process(
77a90e32deSAlan Somers 		ResultOf([=](auto in) {
7829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
7929edc611SAlan Somers 				in.header.nodeid == ino &&
8029edc611SAlan Somers 				in.body.setattr.valid == FATTR_MODE &&
8129edc611SAlan Somers 				in.body.setattr.mode == mode);
82a90e32deSAlan Somers 		}, Eq(true)),
83a90e32deSAlan Somers 		_)
8429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
85a90e32deSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
8629edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
8729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
8829edc611SAlan Somers 		out.body.attr.attr.size = size;
8929edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
90a90e32deSAlan Somers 	})));
91a90e32deSAlan Somers }
92a90e32deSAlan Somers 
933fa12789SAlan Somers void expect_create(const char *relpath, uint64_t ino)
943fa12789SAlan Somers {
953fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
963fa12789SAlan Somers 		ResultOf([=](auto in) {
9729edc611SAlan Somers 			const char *name = (const char*)in.body.bytes +
98a4856c96SAlan Somers 				sizeof(fuse_create_in);
9929edc611SAlan Somers 			return (in.header.opcode == FUSE_CREATE &&
1003fa12789SAlan Somers 				(0 == strcmp(relpath, name)));
1013fa12789SAlan Somers 		}, Eq(true)),
1023fa12789SAlan Somers 		_)
10329edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1043fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
10529edc611SAlan Somers 		out.body.create.entry.attr.mode = S_IFREG | 0644;
10629edc611SAlan Somers 		out.body.create.entry.nodeid = ino;
10729edc611SAlan Somers 		out.body.create.entry.entry_valid = UINT64_MAX;
10829edc611SAlan Somers 		out.body.create.entry.attr_valid = UINT64_MAX;
1093fa12789SAlan Somers 	})));
1103fa12789SAlan Somers }
1113fa12789SAlan Somers 
11292bbfe1fSAlan Somers void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
11392bbfe1fSAlan Somers     uint64_t off_out, uint64_t len)
11492bbfe1fSAlan Somers {
11592bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
11692bbfe1fSAlan Somers 		ResultOf([=](auto in) {
11792bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
11892bbfe1fSAlan Somers 				in.header.nodeid == ino_in &&
11992bbfe1fSAlan Somers 				in.body.copy_file_range.off_in == off_in &&
12092bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino_out &&
12192bbfe1fSAlan Somers 				in.body.copy_file_range.off_out == off_out &&
12292bbfe1fSAlan Somers 				in.body.copy_file_range.len == len);
12392bbfe1fSAlan Somers 		}, Eq(true)),
12492bbfe1fSAlan Somers 		_)
12592bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
12692bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
12792bbfe1fSAlan Somers 		out.body.write.size = len;
12892bbfe1fSAlan Somers 	})));
12992bbfe1fSAlan Somers }
13092bbfe1fSAlan Somers 
131ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
132474ba6faSAlan Somers 	uid_t uid = 0, gid_t gid = 0)
1339821f1d3SAlan Somers {
134ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
135ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
13629edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
13729edc611SAlan Somers 				in.header.nodeid == ino);
138ff4fbdf5SAlan Somers 		}, Eq(true)),
139ff4fbdf5SAlan Somers 		_)
140ff4fbdf5SAlan Somers 	).Times(times)
14129edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
142ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
14329edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
14429edc611SAlan Somers 		out.body.attr.attr.mode = mode;
14529edc611SAlan Somers 		out.body.attr.attr.size = 0;
14629edc611SAlan Somers 		out.body.attr.attr.uid = uid;
147a22a7807SAlan Somers 		out.body.attr.attr.gid = gid;
14829edc611SAlan Somers 		out.body.attr.attr_valid = attr_valid;
149ff4fbdf5SAlan Somers 	})));
150ff4fbdf5SAlan Somers }
151ff4fbdf5SAlan Somers 
152ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
153474ba6faSAlan Somers 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
154ff4fbdf5SAlan Somers {
155474ba6faSAlan Somers 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
1569821f1d3SAlan Somers }
1579821f1d3SAlan Somers 
1589821f1d3SAlan Somers };
1599821f1d3SAlan Somers 
1609821f1d3SAlan Somers class Access: public DefaultPermissions {};
161474ba6faSAlan Somers class Chown: public DefaultPermissions {};
162474ba6faSAlan Somers class Chgrp: public DefaultPermissions {};
16392bbfe1fSAlan Somers class CopyFileRange: public DefaultPermissions {};
164ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1659821f1d3SAlan Somers class Open: public DefaultPermissions {};
166*398c88c7SAlan Somers class PosixFallocate: public DefaultPermissions {};
167ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {};
168ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {};
169d943c93eSAlan Somers class Utimensat: public DefaultPermissions {};
170a90e32deSAlan Somers class Write: public DefaultPermissions {};
1719821f1d3SAlan Somers 
172ff4fbdf5SAlan Somers /*
173ff4fbdf5SAlan Somers  * Test permission handling during create, mkdir, mknod, link, symlink, and
174ff4fbdf5SAlan Somers  * rename vops (they all share a common path for permission checks in
175ff4fbdf5SAlan Somers  * VOP_LOOKUP)
176ff4fbdf5SAlan Somers  */
1773fa12789SAlan Somers class Create: public DefaultPermissions {};
178ff4fbdf5SAlan Somers 
179ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions {
180ff4fbdf5SAlan Somers public:
181ff4fbdf5SAlan Somers void expect_removexattr()
182ff4fbdf5SAlan Somers {
183ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
184ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
18529edc611SAlan Somers 			return (in.header.opcode == FUSE_REMOVEXATTR);
186ff4fbdf5SAlan Somers 		}, Eq(true)),
187ff4fbdf5SAlan Somers 		_)
188ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(0)));
189ff4fbdf5SAlan Somers }
190ff4fbdf5SAlan Somers };
191ff4fbdf5SAlan Somers 
192ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions {
193ff4fbdf5SAlan Somers public:
194ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r)
195ff4fbdf5SAlan Somers {
196ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
197ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
19829edc611SAlan Somers 			return (in.header.opcode == FUSE_GETXATTR);
199ff4fbdf5SAlan Somers 		}, Eq(true)),
200ff4fbdf5SAlan Somers 		_)
201ff4fbdf5SAlan Somers 	).WillOnce(Invoke(r));
202ff4fbdf5SAlan Somers }
203ff4fbdf5SAlan Somers };
204ff4fbdf5SAlan Somers 
205ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions {
206ff4fbdf5SAlan Somers public:
207ff4fbdf5SAlan Somers void expect_listxattr()
208ff4fbdf5SAlan Somers {
209ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
210ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
21129edc611SAlan Somers 			return (in.header.opcode == FUSE_LISTXATTR);
212ff4fbdf5SAlan Somers 		}, Eq(true)),
213ff4fbdf5SAlan Somers 		_)
21429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
21529edc611SAlan Somers 		out.body.listxattr.size = 0;
216ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
217ff4fbdf5SAlan Somers 	})));
218ff4fbdf5SAlan Somers }
219ff4fbdf5SAlan Somers };
220ff4fbdf5SAlan Somers 
221ff4fbdf5SAlan Somers class Rename: public DefaultPermissions {
222ff4fbdf5SAlan Somers public:
223ff4fbdf5SAlan Somers 	/*
224ff4fbdf5SAlan Somers 	 * Expect a rename and respond with the given error.  Don't both to
225ff4fbdf5SAlan Somers 	 * validate arguments; the tests in rename.cc do that.
226ff4fbdf5SAlan Somers 	 */
227ff4fbdf5SAlan Somers 	void expect_rename(int error)
228ff4fbdf5SAlan Somers 	{
229ff4fbdf5SAlan Somers 		EXPECT_CALL(*m_mock, process(
230ff4fbdf5SAlan Somers 			ResultOf([=](auto in) {
23129edc611SAlan Somers 				return (in.header.opcode == FUSE_RENAME);
232ff4fbdf5SAlan Somers 			}, Eq(true)),
233ff4fbdf5SAlan Somers 			_)
234ff4fbdf5SAlan Somers 		).WillOnce(Invoke(ReturnErrno(error)));
235ff4fbdf5SAlan Somers 	}
236ff4fbdf5SAlan Somers };
237ff4fbdf5SAlan Somers 
238ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions {
239ff4fbdf5SAlan Somers public:
240ff4fbdf5SAlan Somers void expect_setxattr(int error)
241ff4fbdf5SAlan Somers {
242ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
243ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
24429edc611SAlan Somers 			return (in.header.opcode == FUSE_SETXATTR);
245ff4fbdf5SAlan Somers 		}, Eq(true)),
246ff4fbdf5SAlan Somers 		_)
247ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
248ff4fbdf5SAlan Somers }
249ff4fbdf5SAlan Somers };
250ff4fbdf5SAlan Somers 
2518cfb4431SAlan Somers /* Return a group to which this user does not belong */
2528cfb4431SAlan Somers static gid_t excluded_group()
2538cfb4431SAlan Somers {
2548cfb4431SAlan Somers 	int i, ngroups = 64;
2558cfb4431SAlan Somers 	gid_t newgid, groups[ngroups];
2568cfb4431SAlan Somers 
2578cfb4431SAlan Somers 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
2585a0b9a27SAlan Somers 	for (newgid = 0; ; newgid++) {
2598cfb4431SAlan Somers 		bool belongs = false;
2608cfb4431SAlan Somers 
2618cfb4431SAlan Somers 		for (i = 0; i < ngroups; i++) {
2628cfb4431SAlan Somers 			if (groups[i] == newgid)
2638cfb4431SAlan Somers 				belongs = true;
2648cfb4431SAlan Somers 		}
2658cfb4431SAlan Somers 		if (!belongs)
2668cfb4431SAlan Somers 			break;
2678cfb4431SAlan Somers 	}
2688cfb4431SAlan Somers 	/* newgid is now a group to which the current user does not belong */
2698cfb4431SAlan Somers 	return newgid;
2708cfb4431SAlan Somers }
2718cfb4431SAlan Somers 
272ff4fbdf5SAlan Somers TEST_F(Access, eacces)
2739821f1d3SAlan Somers {
2749821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2759821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2769821f1d3SAlan Somers 	uint64_t ino = 42;
2779821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
2789821f1d3SAlan Somers 
279a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
280ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
281ff4fbdf5SAlan Somers 
282ff4fbdf5SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
283ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
284ff4fbdf5SAlan Somers }
285ff4fbdf5SAlan Somers 
286ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs)
287ff4fbdf5SAlan Somers {
288ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
289ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
290ff4fbdf5SAlan Somers 	uint64_t ino = 42;
291ff4fbdf5SAlan Somers 	mode_t	access_mode = X_OK;
292ff4fbdf5SAlan Somers 
293a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1);
294ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
295ff4fbdf5SAlan Somers 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
2969821f1d3SAlan Somers 	/*
2979821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
2989821f1d3SAlan Somers 	 * another FUSE_GETATTR or something in here.  But there should not be
2999821f1d3SAlan Somers 	 * a FUSE_ACCESS
3009821f1d3SAlan Somers 	 */
3019821f1d3SAlan Somers 
3029821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
3039821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
3049821f1d3SAlan Somers }
3059821f1d3SAlan Somers 
306ff4fbdf5SAlan Somers TEST_F(Access, ok)
3079821f1d3SAlan Somers {
3089821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3099821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
3109821f1d3SAlan Somers 	uint64_t ino = 42;
3119821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
3129821f1d3SAlan Somers 
313a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
314ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
3159821f1d3SAlan Somers 	/*
3169821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
31791ff3a0dSAlan Somers 	 * another FUSE_GETATTR or something in here.
3189821f1d3SAlan Somers 	 */
3199821f1d3SAlan Somers 
3209821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
3219821f1d3SAlan Somers }
3229821f1d3SAlan Somers 
3234e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */
3244e83d655SAlan Somers TEST_F(Chown, chown_to_self)
3254e83d655SAlan Somers {
3264e83d655SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3274e83d655SAlan Somers 	const char RELPATH[] = "some_file.txt";
3284e83d655SAlan Somers 	const uint64_t ino = 42;
3294e83d655SAlan Somers 	const mode_t mode = 0755;
3304e83d655SAlan Somers 	uid_t uid;
3314e83d655SAlan Somers 
3324e83d655SAlan Somers 	uid = geteuid();
3334e83d655SAlan Somers 
334a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
3354e83d655SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
3364e83d655SAlan Somers 	/* The OS may optimize chown by omitting the redundant setattr */
3374e83d655SAlan Somers 	EXPECT_CALL(*m_mock, process(
3384e83d655SAlan Somers 		ResultOf([](auto in) {
33929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
3404e83d655SAlan Somers 		}, Eq(true)),
3414e83d655SAlan Somers 		_)
34229edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
3434e83d655SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
34429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
34529edc611SAlan Somers 		out.body.attr.attr.uid = uid;
3464e83d655SAlan Somers 	})));
3474e83d655SAlan Somers 
3484e83d655SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
3494e83d655SAlan Somers }
3504e83d655SAlan Somers 
351a2bdd737SAlan Somers /*
352a2bdd737SAlan Somers  * A successful chown by a non-privileged non-owner should clear a file's SUID
353a2bdd737SAlan Somers  * bit
354a2bdd737SAlan Somers  */
355a2bdd737SAlan Somers TEST_F(Chown, clear_suid)
356a2bdd737SAlan Somers {
357a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
358a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
359a2bdd737SAlan Somers 	uint64_t ino = 42;
360a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
361a2bdd737SAlan Somers 	const mode_t newmode = 0755;
362a2bdd737SAlan Somers 	uid_t uid = geteuid();
363a2bdd737SAlan Somers 	uint32_t valid = FATTR_UID | FATTR_MODE;
364a2bdd737SAlan Somers 
365a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
366a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
367a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
368a2bdd737SAlan Somers 		ResultOf([=](auto in) {
36929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
37029edc611SAlan Somers 				in.header.nodeid == ino &&
37129edc611SAlan Somers 				in.body.setattr.valid == valid &&
37229edc611SAlan Somers 				in.body.setattr.mode == newmode);
373a2bdd737SAlan Somers 		}, Eq(true)),
374a2bdd737SAlan Somers 		_)
37529edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
376a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
37729edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
37829edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
37929edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
380a2bdd737SAlan Somers 	})));
381a2bdd737SAlan Somers 
382a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
383a2bdd737SAlan Somers }
384a2bdd737SAlan Somers 
385a2bdd737SAlan Somers 
386474ba6faSAlan Somers /* Only root may change a file's owner */
387474ba6faSAlan Somers TEST_F(Chown, eperm)
388474ba6faSAlan Somers {
389474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
390474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
391474ba6faSAlan Somers 	const uint64_t ino = 42;
392474ba6faSAlan Somers 	const mode_t mode = 0755;
393474ba6faSAlan Somers 
394a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
395474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
396474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
397474ba6faSAlan Somers 		ResultOf([](auto in) {
39829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
399474ba6faSAlan Somers 		}, Eq(true)),
400474ba6faSAlan Somers 		_)
401474ba6faSAlan Somers 	).Times(0);
402474ba6faSAlan Somers 
403474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
404474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
405474ba6faSAlan Somers }
406474ba6faSAlan Somers 
407a2bdd737SAlan Somers /*
408a2bdd737SAlan Somers  * A successful chgrp by a non-privileged non-owner should clear a file's SUID
409a2bdd737SAlan Somers  * bit
410a2bdd737SAlan Somers  */
411a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid)
412a2bdd737SAlan Somers {
413a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
414a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
415a2bdd737SAlan Somers 	uint64_t ino = 42;
416a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
417a2bdd737SAlan Somers 	const mode_t newmode = 0755;
418a2bdd737SAlan Somers 	uid_t uid = geteuid();
419a2bdd737SAlan Somers 	gid_t gid = getegid();
420a2bdd737SAlan Somers 	uint32_t valid = FATTR_GID | FATTR_MODE;
421a2bdd737SAlan Somers 
422a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
423a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
424a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
425a2bdd737SAlan Somers 		ResultOf([=](auto in) {
42629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
42729edc611SAlan Somers 				in.header.nodeid == ino &&
42829edc611SAlan Somers 				in.body.setattr.valid == valid &&
42929edc611SAlan Somers 				in.body.setattr.mode == newmode);
430a2bdd737SAlan Somers 		}, Eq(true)),
431a2bdd737SAlan Somers 		_)
43229edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
433a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
43429edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
43529edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
43629edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
437a2bdd737SAlan Somers 	})));
438a2bdd737SAlan Somers 
439a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
440a2bdd737SAlan Somers }
441a2bdd737SAlan Somers 
442474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */
443474ba6faSAlan Somers TEST_F(Chgrp, eperm)
444474ba6faSAlan Somers {
445474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
446474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
447474ba6faSAlan Somers 	const uint64_t ino = 42;
448474ba6faSAlan Somers 	const mode_t mode = 0755;
449474ba6faSAlan Somers 	uid_t uid;
450474ba6faSAlan Somers 	gid_t gid, newgid;
451474ba6faSAlan Somers 
452474ba6faSAlan Somers 	uid = geteuid();
453474ba6faSAlan Somers 	gid = getegid();
4548cfb4431SAlan Somers 	newgid = excluded_group();
455474ba6faSAlan Somers 
456a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
457474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
458474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
459474ba6faSAlan Somers 		ResultOf([](auto in) {
46029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
461474ba6faSAlan Somers 		}, Eq(true)),
462474ba6faSAlan Somers 		_)
463474ba6faSAlan Somers 	).Times(0);
464474ba6faSAlan Somers 
465474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
466474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
467474ba6faSAlan Somers }
468474ba6faSAlan Somers 
469474ba6faSAlan Somers TEST_F(Chgrp, ok)
470474ba6faSAlan Somers {
471474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
472474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
473474ba6faSAlan Somers 	const uint64_t ino = 42;
474474ba6faSAlan Somers 	const mode_t mode = 0755;
475474ba6faSAlan Somers 	uid_t uid;
476474ba6faSAlan Somers 	gid_t gid, newgid;
477474ba6faSAlan Somers 
478474ba6faSAlan Somers 	uid = geteuid();
479474ba6faSAlan Somers 	gid = 0;
480474ba6faSAlan Somers 	newgid = getegid();
481474ba6faSAlan Somers 
482a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
483474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
4844e83d655SAlan Somers 	/* The OS may optimize chgrp by omitting the redundant setattr */
485474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
486474ba6faSAlan Somers 		ResultOf([](auto in) {
48729edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
48829edc611SAlan Somers 				in.header.nodeid == ino);
489474ba6faSAlan Somers 		}, Eq(true)),
490474ba6faSAlan Somers 		_)
49129edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
492474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
49329edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
49429edc611SAlan Somers 		out.body.attr.attr.uid = uid;
49529edc611SAlan Somers 		out.body.attr.attr.gid = newgid;
496474ba6faSAlan Somers 	})));
497474ba6faSAlan Somers 
498474ba6faSAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
499474ba6faSAlan Somers }
500474ba6faSAlan Somers 
50192bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
502*398c88c7SAlan Somers TEST_F(CopyFileRange, clear_sgid)
50392bbfe1fSAlan Somers {
50492bbfe1fSAlan Somers 	const char FULLPATH_IN[] = "mountpoint/in.txt";
50592bbfe1fSAlan Somers 	const char RELPATH_IN[] = "in.txt";
50692bbfe1fSAlan Somers 	const char FULLPATH_OUT[] = "mountpoint/out.txt";
50792bbfe1fSAlan Somers 	const char RELPATH_OUT[] = "out.txt";
50892bbfe1fSAlan Somers 	struct stat sb;
50992bbfe1fSAlan Somers 	uint64_t ino_in = 42;
51092bbfe1fSAlan Somers 	uint64_t ino_out = 43;
51192bbfe1fSAlan Somers 	mode_t oldmode = 02777;
51292bbfe1fSAlan Somers 	mode_t newmode = 0777;
51392bbfe1fSAlan Somers 	off_t fsize = 16;
51492bbfe1fSAlan Somers 	off_t off_in = 0;
51592bbfe1fSAlan Somers 	off_t off_out = 8;
51692bbfe1fSAlan Somers 	off_t len = 8;
51792bbfe1fSAlan Somers 	int fd_in, fd_out;
51892bbfe1fSAlan Somers 
51992bbfe1fSAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
52092bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
52192bbfe1fSAlan Somers 	    UINT64_MAX, 0, 0);
52292bbfe1fSAlan Somers 	expect_open(ino_in, 0, 1);
52392bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
52492bbfe1fSAlan Somers 	    1, UINT64_MAX, 0, 0);
52592bbfe1fSAlan Somers 	expect_open(ino_out, 0, 1);
52692bbfe1fSAlan Somers 	expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
52792bbfe1fSAlan Somers 	expect_chmod(ino_out, newmode, fsize);
52892bbfe1fSAlan Somers 
52992bbfe1fSAlan Somers 	fd_in = open(FULLPATH_IN, O_RDONLY);
53092bbfe1fSAlan Somers 	ASSERT_LE(0, fd_in) << strerror(errno);
53192bbfe1fSAlan Somers 	fd_out = open(FULLPATH_OUT, O_WRONLY);
53292bbfe1fSAlan Somers 	ASSERT_LE(0, fd_out) << strerror(errno);
53392bbfe1fSAlan Somers 	ASSERT_EQ(len,
53492bbfe1fSAlan Somers 	    copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
53592bbfe1fSAlan Somers 	    << strerror(errno);
53692bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
53792bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
53892bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
53992bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
54092bbfe1fSAlan Somers 
54192bbfe1fSAlan Somers 	leak(fd_in);
54292bbfe1fSAlan Somers 	leak(fd_out);
54392bbfe1fSAlan Somers }
54492bbfe1fSAlan Somers 
54592bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
54692bbfe1fSAlan Somers TEST_F(CopyFileRange, clear_suid)
54792bbfe1fSAlan Somers {
54892bbfe1fSAlan Somers 	const char FULLPATH_IN[] = "mountpoint/in.txt";
54992bbfe1fSAlan Somers 	const char RELPATH_IN[] = "in.txt";
55092bbfe1fSAlan Somers 	const char FULLPATH_OUT[] = "mountpoint/out.txt";
55192bbfe1fSAlan Somers 	const char RELPATH_OUT[] = "out.txt";
55292bbfe1fSAlan Somers 	struct stat sb;
55392bbfe1fSAlan Somers 	uint64_t ino_in = 42;
55492bbfe1fSAlan Somers 	uint64_t ino_out = 43;
55592bbfe1fSAlan Somers 	mode_t oldmode = 04777;
55692bbfe1fSAlan Somers 	mode_t newmode = 0777;
55792bbfe1fSAlan Somers 	off_t fsize = 16;
55892bbfe1fSAlan Somers 	off_t off_in = 0;
55992bbfe1fSAlan Somers 	off_t off_out = 8;
56092bbfe1fSAlan Somers 	off_t len = 8;
56192bbfe1fSAlan Somers 	int fd_in, fd_out;
56292bbfe1fSAlan Somers 
56392bbfe1fSAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
56492bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
56592bbfe1fSAlan Somers 	    UINT64_MAX, 0, 0);
56692bbfe1fSAlan Somers 	expect_open(ino_in, 0, 1);
56792bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
56892bbfe1fSAlan Somers 	    1, UINT64_MAX, 0, 0);
56992bbfe1fSAlan Somers 	expect_open(ino_out, 0, 1);
57092bbfe1fSAlan Somers 	expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
57192bbfe1fSAlan Somers 	expect_chmod(ino_out, newmode, fsize);
57292bbfe1fSAlan Somers 
57392bbfe1fSAlan Somers 	fd_in = open(FULLPATH_IN, O_RDONLY);
57492bbfe1fSAlan Somers 	ASSERT_LE(0, fd_in) << strerror(errno);
57592bbfe1fSAlan Somers 	fd_out = open(FULLPATH_OUT, O_WRONLY);
57692bbfe1fSAlan Somers 	ASSERT_LE(0, fd_out) << strerror(errno);
57792bbfe1fSAlan Somers 	ASSERT_EQ(len,
57892bbfe1fSAlan Somers 	    copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
57992bbfe1fSAlan Somers 	    << strerror(errno);
58092bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
58192bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
58292bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
58392bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
58492bbfe1fSAlan Somers 
58592bbfe1fSAlan Somers 	leak(fd_in);
58692bbfe1fSAlan Somers 	leak(fd_out);
58792bbfe1fSAlan Somers }
58892bbfe1fSAlan Somers 
589ff4fbdf5SAlan Somers TEST_F(Create, ok)
590ff4fbdf5SAlan Somers {
591ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
592ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
593ff4fbdf5SAlan Somers 	uint64_t ino = 42;
594ff4fbdf5SAlan Somers 	int fd;
595ff4fbdf5SAlan Somers 
596a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
597a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
598a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
599ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
600ff4fbdf5SAlan Somers 
601ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
602d2621689SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
6037fc0921dSAlan Somers 	leak(fd);
604ff4fbdf5SAlan Somers }
605ff4fbdf5SAlan Somers 
606ff4fbdf5SAlan Somers TEST_F(Create, eacces)
607ff4fbdf5SAlan Somers {
608ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
609ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
610ff4fbdf5SAlan Somers 
611a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
612a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
613a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
614ff4fbdf5SAlan Somers 
6158e765737SAlan Somers 	ASSERT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
616ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
617ff4fbdf5SAlan Somers }
618ff4fbdf5SAlan Somers 
619ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
620ff4fbdf5SAlan Somers {
621ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
622ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
623ff4fbdf5SAlan Somers 	uint64_t ino = 42;
624ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
625ff4fbdf5SAlan Somers 
626a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
627ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
628ff4fbdf5SAlan Somers 
629ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
630ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
631ff4fbdf5SAlan Somers }
632ff4fbdf5SAlan Somers 
633ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
634ff4fbdf5SAlan Somers {
635ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
636ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
637ff4fbdf5SAlan Somers 	uint64_t ino = 42;
638ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
639ff4fbdf5SAlan Somers 
640a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
641ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
642ff4fbdf5SAlan Somers 	expect_removexattr();
643ff4fbdf5SAlan Somers 
644ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
645ff4fbdf5SAlan Somers 		<< strerror(errno);
646ff4fbdf5SAlan Somers }
647ff4fbdf5SAlan Somers 
648ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
649ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
650ff4fbdf5SAlan Somers {
651ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
652ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
653ff4fbdf5SAlan Somers 	uint64_t ino = 42;
654ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
655ff4fbdf5SAlan Somers 
656a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
657ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
658ff4fbdf5SAlan Somers 
659ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
660ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
661ff4fbdf5SAlan Somers }
662ff4fbdf5SAlan Somers 
663d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */
664d943c93eSAlan Somers TEST_F(Utimensat, utime_now)
665d943c93eSAlan Somers {
666d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
667d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
668d943c93eSAlan Somers 	const uint64_t ino = 42;
669d943c93eSAlan Somers 	/* Write permissions for everybody */
670d943c93eSAlan Somers 	const mode_t mode = 0666;
671d943c93eSAlan Somers 	uid_t owner = 0;
672d943c93eSAlan Somers 	const timespec times[2] = {
673d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
674d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
675d943c93eSAlan Somers 	};
676d943c93eSAlan Somers 
677a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
678d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
679d943c93eSAlan Somers 	EXPECT_CALL(*m_mock, process(
680d943c93eSAlan Somers 		ResultOf([](auto in) {
68129edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
68229edc611SAlan Somers 				in.header.nodeid == ino &&
68329edc611SAlan Somers 				in.body.setattr.valid & FATTR_ATIME &&
68429edc611SAlan Somers 				in.body.setattr.valid & FATTR_MTIME);
685d943c93eSAlan Somers 		}, Eq(true)),
686d943c93eSAlan Somers 		_)
68729edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
688d943c93eSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
68929edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
690d943c93eSAlan Somers 	})));
691d943c93eSAlan Somers 
692d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
693d943c93eSAlan Somers 		<< strerror(errno);
694d943c93eSAlan Somers }
695d943c93eSAlan Somers 
696d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */
697d943c93eSAlan Somers TEST_F(Utimensat, utime_omit)
698d943c93eSAlan Somers {
699d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
700d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
701d943c93eSAlan Somers 	const uint64_t ino = 42;
702d943c93eSAlan Somers 	/* Write permissions for no one */
703d943c93eSAlan Somers 	const mode_t mode = 0444;
704d943c93eSAlan Somers 	uid_t owner = 0;
705d943c93eSAlan Somers 	const timespec times[2] = {
706d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
707d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
708d943c93eSAlan Somers 	};
709d943c93eSAlan Somers 
710a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
711d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
712d943c93eSAlan Somers 
713d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
714d943c93eSAlan Somers 		<< strerror(errno);
715d943c93eSAlan Somers }
716d943c93eSAlan Somers 
717ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
718ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
719ff4fbdf5SAlan Somers {
720ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
721ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
722ff4fbdf5SAlan Somers 	uint64_t ino = 42;
723ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
724ff4fbdf5SAlan Somers 
725a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
726ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
727ff4fbdf5SAlan Somers 	expect_removexattr();
728ff4fbdf5SAlan Somers 
729ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
730ff4fbdf5SAlan Somers 		<< strerror(errno);
731ff4fbdf5SAlan Somers }
732ff4fbdf5SAlan Somers 
733ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
734ff4fbdf5SAlan Somers {
735ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
736ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
737ff4fbdf5SAlan Somers 	uint64_t ino = 42;
738ff4fbdf5SAlan Somers 	char data[80];
739ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
740ff4fbdf5SAlan Somers 
741a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
742ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
743ff4fbdf5SAlan Somers 
744ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
745ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
746ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
747ff4fbdf5SAlan Somers }
748ff4fbdf5SAlan Somers 
749ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
750ff4fbdf5SAlan Somers {
751ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
752ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
753ff4fbdf5SAlan Somers 	uint64_t ino = 42;
754ff4fbdf5SAlan Somers 	char data[80];
755ff4fbdf5SAlan Somers 	const char value[] = "whatever";
756ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
757ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
758ff4fbdf5SAlan Somers 	ssize_t r;
759ff4fbdf5SAlan Somers 
760a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
761ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
762ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
763ff4fbdf5SAlan Somers 	expect_getxattr(
76429edc611SAlan Somers 		ReturnImmediate([&](auto in __unused, auto& out) {
76529edc611SAlan Somers 			memcpy((void*)out.body.bytes, value, value_len);
76629edc611SAlan Somers 			out.header.len = sizeof(out.header) + value_len;
767ff4fbdf5SAlan Somers 		})
768ff4fbdf5SAlan Somers 	);
769ff4fbdf5SAlan Somers 
770ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
771ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
772ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
773ff4fbdf5SAlan Somers }
774ff4fbdf5SAlan Somers 
775ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
776ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
777ff4fbdf5SAlan Somers {
778ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
779ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
780ff4fbdf5SAlan Somers 	uint64_t ino = 42;
781ff4fbdf5SAlan Somers 	char data[80];
782ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
783ff4fbdf5SAlan Somers 
784a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
785ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
786ff4fbdf5SAlan Somers 
787ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
788ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
789ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
790ff4fbdf5SAlan Somers }
791ff4fbdf5SAlan Somers 
792ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
793ff4fbdf5SAlan Somers {
794ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
795ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
796ff4fbdf5SAlan Somers 	uint64_t ino = 42;
797ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
798ff4fbdf5SAlan Somers 
799a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
800ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
801ff4fbdf5SAlan Somers 
802ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
803ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
804ff4fbdf5SAlan Somers }
805ff4fbdf5SAlan Somers 
806ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
807ff4fbdf5SAlan Somers {
808ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
809ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
810ff4fbdf5SAlan Somers 	uint64_t ino = 42;
811ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
812ff4fbdf5SAlan Somers 
813a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
814ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
815ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
816ff4fbdf5SAlan Somers 	expect_listxattr();
817ff4fbdf5SAlan Somers 
818ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
819ff4fbdf5SAlan Somers 		<< strerror(errno);
820ff4fbdf5SAlan Somers }
821ff4fbdf5SAlan Somers 
822ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
823ff4fbdf5SAlan Somers TEST_F(Listextattr, system)
824ff4fbdf5SAlan Somers {
825ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
826ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
827ff4fbdf5SAlan Somers 	uint64_t ino = 42;
828ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
829ff4fbdf5SAlan Somers 
830a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
831ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
832ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
833ff4fbdf5SAlan Somers 
834ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
835ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
836ff4fbdf5SAlan Somers }
837ff4fbdf5SAlan Somers 
838ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
839ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
840ff4fbdf5SAlan Somers {
841ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
842ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
843ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
844ff4fbdf5SAlan Somers 
845a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
846ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
847ff4fbdf5SAlan Somers 
848ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
849ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
850ff4fbdf5SAlan Somers }
851ff4fbdf5SAlan Somers 
852ff4fbdf5SAlan Somers TEST_F(Open, eacces)
853ff4fbdf5SAlan Somers {
854ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
855ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
856ff4fbdf5SAlan Somers 	uint64_t ino = 42;
857ff4fbdf5SAlan Somers 
858a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
859ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
860ff4fbdf5SAlan Somers 
8614ca1c0b7SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_RDWR));
862ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
863ff4fbdf5SAlan Somers }
864ff4fbdf5SAlan Somers 
8659821f1d3SAlan Somers TEST_F(Open, ok)
8669821f1d3SAlan Somers {
8679821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
8689821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
8699821f1d3SAlan Somers 	uint64_t ino = 42;
8709821f1d3SAlan Somers 	int fd;
8719821f1d3SAlan Somers 
872a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
873ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
8749821f1d3SAlan Somers 	expect_open(ino, 0, 1);
8759821f1d3SAlan Somers 
8769821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
877d2621689SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
8787fc0921dSAlan Somers 	leak(fd);
8799821f1d3SAlan Somers }
8809821f1d3SAlan Somers 
881*398c88c7SAlan Somers /* A write by a non-owner should clear a file's SGID bit */
882*398c88c7SAlan Somers TEST_F(PosixFallocate, clear_sgid)
883*398c88c7SAlan Somers {
884*398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
885*398c88c7SAlan Somers 	const char RELPATH[] = "file.txt";
886*398c88c7SAlan Somers 	struct stat sb;
887*398c88c7SAlan Somers 	uint64_t ino = 42;
888*398c88c7SAlan Somers 	mode_t oldmode = 02777;
889*398c88c7SAlan Somers 	mode_t newmode = 0777;
890*398c88c7SAlan Somers 	off_t fsize = 16;
891*398c88c7SAlan Somers 	off_t off = 8;
892*398c88c7SAlan Somers 	off_t len = 8;
893*398c88c7SAlan Somers 	int fd;
894*398c88c7SAlan Somers 
895*398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
896*398c88c7SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
897*398c88c7SAlan Somers 	    1, UINT64_MAX, 0, 0);
898*398c88c7SAlan Somers 	expect_open(ino, 0, 1);
899*398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
900*398c88c7SAlan Somers 	expect_chmod(ino, newmode, fsize);
901*398c88c7SAlan Somers 
902*398c88c7SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
903*398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
904*398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
905*398c88c7SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
906*398c88c7SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
907*398c88c7SAlan Somers 
908*398c88c7SAlan Somers 	leak(fd);
909*398c88c7SAlan Somers }
910*398c88c7SAlan Somers 
911*398c88c7SAlan Somers /* A write by a non-owner should clear a file's SUID bit */
912*398c88c7SAlan Somers TEST_F(PosixFallocate, clear_suid)
913*398c88c7SAlan Somers {
914*398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
915*398c88c7SAlan Somers 	const char RELPATH[] = "file.txt";
916*398c88c7SAlan Somers 	struct stat sb;
917*398c88c7SAlan Somers 	uint64_t ino = 42;
918*398c88c7SAlan Somers 	mode_t oldmode = 04777;
919*398c88c7SAlan Somers 	mode_t newmode = 0777;
920*398c88c7SAlan Somers 	off_t fsize = 16;
921*398c88c7SAlan Somers 	off_t off = 8;
922*398c88c7SAlan Somers 	off_t len = 8;
923*398c88c7SAlan Somers 	int fd;
924*398c88c7SAlan Somers 
925*398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
926*398c88c7SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
927*398c88c7SAlan Somers 	    1, UINT64_MAX, 0, 0);
928*398c88c7SAlan Somers 	expect_open(ino, 0, 1);
929*398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
930*398c88c7SAlan Somers 	expect_chmod(ino, newmode, fsize);
931*398c88c7SAlan Somers 
932*398c88c7SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
933*398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
934*398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
935*398c88c7SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
936*398c88c7SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
937*398c88c7SAlan Somers 
938*398c88c7SAlan Somers 	leak(fd);
939*398c88c7SAlan Somers }
940*398c88c7SAlan Somers 
941*398c88c7SAlan Somers /*
942*398c88c7SAlan Somers  * posix_fallcoate() of a file without writable permissions should succeed as
943*398c88c7SAlan Somers  * long as the file descriptor is writable.  This is important when combined
944*398c88c7SAlan Somers  * with O_CREAT
945*398c88c7SAlan Somers  */
946*398c88c7SAlan Somers TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file)
947*398c88c7SAlan Somers {
948*398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
949*398c88c7SAlan Somers 	const char RELPATH[] = "some_file.txt";
950*398c88c7SAlan Somers 	const uint64_t ino = 42;
951*398c88c7SAlan Somers 	off_t off = 8;
952*398c88c7SAlan Somers 	off_t len = 8;
953*398c88c7SAlan Somers 	int fd;
954*398c88c7SAlan Somers 
955*398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
956*398c88c7SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
957*398c88c7SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
958*398c88c7SAlan Somers 	expect_create(RELPATH, ino);
959*398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
960*398c88c7SAlan Somers 
961*398c88c7SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
962*398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
963*398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
964*398c88c7SAlan Somers 	leak(fd);
965*398c88c7SAlan Somers }
966*398c88c7SAlan Somers 
967ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
968ff4fbdf5SAlan Somers {
969ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
970ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
971ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
972ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
973ff4fbdf5SAlan Somers 	uint64_t ino = 42;
974ff4fbdf5SAlan Somers 
975a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
976ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
977a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
978ff4fbdf5SAlan Somers 		.Times(AnyNumber())
979ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
980ff4fbdf5SAlan Somers 
981ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
982ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
983ff4fbdf5SAlan Somers }
984ff4fbdf5SAlan Somers 
985ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
986ff4fbdf5SAlan Somers {
987ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
988ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
989ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
990ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
991ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
992ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
993ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
994ff4fbdf5SAlan Somers 
995a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
996ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
997ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
998ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
999ff4fbdf5SAlan Somers 
1000ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1001ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1002ff4fbdf5SAlan Somers }
1003ff4fbdf5SAlan Somers 
1004ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
1005ff4fbdf5SAlan Somers {
1006ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1007ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
1008ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1009ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1010ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1011ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1012ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1013ff4fbdf5SAlan Somers 
1014a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1015ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1016ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1017ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1018ff4fbdf5SAlan Somers 
1019ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1020ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1021ff4fbdf5SAlan Somers }
1022ff4fbdf5SAlan Somers 
10236124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
1024ff4fbdf5SAlan Somers {
1025ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1026ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1027ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1028ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1029ff4fbdf5SAlan Somers 
1030a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1031ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1032ff4fbdf5SAlan Somers 
1033ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1034ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1035ff4fbdf5SAlan Somers }
1036ff4fbdf5SAlan Somers 
10378e45ec4eSAlan Somers /*
10388e45ec4eSAlan Somers  * A user cannot move out a subdirectory that he does not own, because that
10398e45ec4eSAlan Somers  * would require changing the subdirectory's ".." dirent
10408e45ec4eSAlan Somers  */
10418e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory)
10428e45ec4eSAlan Somers {
10438e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
10448e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
10458e45ec4eSAlan Somers 	const char RELDSTDIR[] = "d";
10468e45ec4eSAlan Somers 	const char RELDST[] = "dst";
10478e45ec4eSAlan Somers 	const char RELSRC[] = "src";
10488e45ec4eSAlan Somers 	uint64_t ino = 42;
10498e45ec4eSAlan Somers 	uint64_t dstdir_ino = 43;
10508e45ec4eSAlan Somers 
1051a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
10528e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
10538e45ec4eSAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
10548e45ec4eSAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
10558e45ec4eSAlan Somers 
10568e45ec4eSAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
10578e45ec4eSAlan Somers 	ASSERT_EQ(EACCES, errno);
10588e45ec4eSAlan Somers }
10598e45ec4eSAlan Somers 
10608e45ec4eSAlan Somers /*
10618e45ec4eSAlan Somers  * A user _can_ rename a subdirectory to which he lacks write permissions, if
10628e45ec4eSAlan Somers  * it will keep the same parent
10638e45ec4eSAlan Somers  */
10648e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir)
10658e45ec4eSAlan Somers {
10668e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/dst";
10678e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
10688e45ec4eSAlan Somers 	const char RELDST[] = "dst";
10698e45ec4eSAlan Somers 	const char RELSRC[] = "src";
10708e45ec4eSAlan Somers 	uint64_t ino = 42;
10718e45ec4eSAlan Somers 
1072a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
10738e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
1074a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1075a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
10768e45ec4eSAlan Somers 	expect_rename(0);
10778e45ec4eSAlan Somers 
10788e45ec4eSAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
10798e45ec4eSAlan Somers }
10808e45ec4eSAlan Somers 
10816124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
1082ff4fbdf5SAlan Somers {
1083ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1084ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
10856124fd71SAlan Somers 	const char RELDST[] = "dst";
1086ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1087ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1088ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1089ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1090ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
1091ff4fbdf5SAlan Somers 
1092a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1093ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1094ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
10956124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
109629edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
10976124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
109829edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
109929edc611SAlan Somers 		out.body.entry.nodeid = dst_ino;
110029edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
110129edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
110229edc611SAlan Somers 		out.body.entry.attr.uid = 0;
11036124fd71SAlan Somers 	})));
1104ff4fbdf5SAlan Somers 
1105ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1106ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1107ff4fbdf5SAlan Somers }
1108ff4fbdf5SAlan Somers 
1109ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
1110ff4fbdf5SAlan Somers TEST_F(Rename, ok)
1111ff4fbdf5SAlan Somers {
1112ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1113ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1114ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1115ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1116ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
1117ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
1118ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1119ff4fbdf5SAlan Somers 
1120a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
1121ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1122ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
1123ff4fbdf5SAlan Somers 	expect_rename(0);
1124ff4fbdf5SAlan Somers 
1125ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1126ff4fbdf5SAlan Somers }
1127ff4fbdf5SAlan Somers 
1128ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
1129ff4fbdf5SAlan Somers {
1130ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1131ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1132ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1133ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1134ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1135ff4fbdf5SAlan Somers 
1136a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1137ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1138a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1139a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1140ff4fbdf5SAlan Somers 	expect_rename(0);
1141ff4fbdf5SAlan Somers 
1142ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1143ff4fbdf5SAlan Somers }
1144ff4fbdf5SAlan Somers 
1145ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
1146ff4fbdf5SAlan Somers {
1147ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1148ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1149ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
1150ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
1151ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
1152ff4fbdf5SAlan Somers 
1153a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1154ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1155ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
1156ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
115729edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
115829edc611SAlan Somers 				in.header.nodeid == ino &&
115929edc611SAlan Somers 				in.body.setattr.mode == newmode);
1160ff4fbdf5SAlan Somers 		}, Eq(true)),
1161ff4fbdf5SAlan Somers 		_)
116229edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1163ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
116429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
1165ff4fbdf5SAlan Somers 	})));
1166ff4fbdf5SAlan Somers 
1167ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1168ff4fbdf5SAlan Somers }
1169ff4fbdf5SAlan Somers 
1170ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
1171ff4fbdf5SAlan Somers {
1172ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1173ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1174ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
1175ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
1176ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
1177ff4fbdf5SAlan Somers 
1178a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1179ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
1180ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
1181ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
118229edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1183ff4fbdf5SAlan Somers 		}, Eq(true)),
1184ff4fbdf5SAlan Somers 		_)
1185ff4fbdf5SAlan Somers 	).Times(0);
1186ff4fbdf5SAlan Somers 
1187ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1188ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
1189ff4fbdf5SAlan Somers }
1190ff4fbdf5SAlan Somers 
11918cfb4431SAlan Somers /*
11923fa12789SAlan Somers  * ftruncate() of a file without writable permissions should succeed as long as
11933fa12789SAlan Somers  * the file descriptor is writable.  This is important when combined with
11943fa12789SAlan Somers  * O_CREAT
11953fa12789SAlan Somers  */
11963fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file)
11973fa12789SAlan Somers {
11983fa12789SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11993fa12789SAlan Somers 	const char RELPATH[] = "some_file.txt";
12003fa12789SAlan Somers 	const uint64_t ino = 42;
12013fa12789SAlan Somers 	const mode_t mode = 0000;
12023fa12789SAlan Somers 	int fd;
12033fa12789SAlan Somers 
1204a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1205a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1206a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
12073fa12789SAlan Somers 	expect_create(RELPATH, ino);
12083fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
12093fa12789SAlan Somers 		ResultOf([](auto in) {
121029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
121129edc611SAlan Somers 				in.header.nodeid == ino &&
121229edc611SAlan Somers 				(in.body.setattr.valid & FATTR_SIZE));
12133fa12789SAlan Somers 		}, Eq(true)),
12143fa12789SAlan Somers 		_)
121529edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
12163fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
121729edc611SAlan Somers 		out.body.attr.attr.ino = ino;
121829edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
121929edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
12203fa12789SAlan Somers 	})));
12213fa12789SAlan Somers 
12223fa12789SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
12233fa12789SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
12243fa12789SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
12257fc0921dSAlan Somers 	leak(fd);
12263fa12789SAlan Somers }
12273fa12789SAlan Somers 
12283fa12789SAlan Somers /*
12298cfb4431SAlan Somers  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
12308cfb4431SAlan Somers  * to the file's group
12318cfb4431SAlan Somers  */
12328cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member)
12338cfb4431SAlan Somers {
12348cfb4431SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
12358cfb4431SAlan Somers 	const char RELPATH[] = "some_file.txt";
12368cfb4431SAlan Somers 	const uint64_t ino = 42;
12378cfb4431SAlan Somers 	const mode_t oldmode = 0755;
12388cfb4431SAlan Somers 	const mode_t newmode = 02755;
12398cfb4431SAlan Somers 	uid_t uid = geteuid();
12408cfb4431SAlan Somers 	gid_t gid = excluded_group();
12418cfb4431SAlan Somers 
1242a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
12438cfb4431SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
12448cfb4431SAlan Somers 	EXPECT_CALL(*m_mock, process(
12458cfb4431SAlan Somers 		ResultOf([](auto in) {
124629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
12478cfb4431SAlan Somers 		}, Eq(true)),
12488cfb4431SAlan Somers 		_)
12498cfb4431SAlan Somers 	).Times(0);
12508cfb4431SAlan Somers 
12518cfb4431SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
12528cfb4431SAlan Somers 	EXPECT_EQ(EPERM, errno);
12538cfb4431SAlan Somers }
12548cfb4431SAlan Somers 
1255e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
1256e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
1257e5ff3a7eSAlan Somers {
1258e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1259e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
1260e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
1261e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
1262e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
1263e5ff3a7eSAlan Somers 
1264a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1265e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1266e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
1267e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
126829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1269e5ff3a7eSAlan Somers 		}, Eq(true)),
1270e5ff3a7eSAlan Somers 		_)
1271e5ff3a7eSAlan Somers 	).Times(0);
1272e5ff3a7eSAlan Somers 
1273e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1274e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
1275e5ff3a7eSAlan Somers }
1276e5ff3a7eSAlan Somers 
1277ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
1278ff4fbdf5SAlan Somers {
1279ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1280ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1281ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1282ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1283ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1284ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1285ff4fbdf5SAlan Somers 	ssize_t r;
1286ff4fbdf5SAlan Somers 
1287a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1288ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1289ff4fbdf5SAlan Somers 	expect_setxattr(0);
1290ff4fbdf5SAlan Somers 
12915a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
12925a0b9a27SAlan Somers 		value_len);
1293ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1294ff4fbdf5SAlan Somers }
1295ff4fbdf5SAlan Somers 
1296ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
1297ff4fbdf5SAlan Somers {
1298ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1299ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1300ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1301ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1302ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1303ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1304ff4fbdf5SAlan Somers 
1305a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1306ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1307ff4fbdf5SAlan Somers 
13085a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
13095a0b9a27SAlan Somers 		value_len));
1310ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1311ff4fbdf5SAlan Somers }
1312ff4fbdf5SAlan Somers 
1313ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
1314ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
1315ff4fbdf5SAlan Somers {
1316ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1317ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1318ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1319ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1320ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1321ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1322ff4fbdf5SAlan Somers 
1323a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1324ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1325ff4fbdf5SAlan Somers 
13265a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
13275a0b9a27SAlan Somers 		value_len));
1328ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1329ff4fbdf5SAlan Somers }
1330ff4fbdf5SAlan Somers 
1331ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
1332ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
1333ff4fbdf5SAlan Somers {
1334ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1335ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1336ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1337ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1338ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1339ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1340ff4fbdf5SAlan Somers 	ssize_t r;
1341ff4fbdf5SAlan Somers 
1342a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1343ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1344ff4fbdf5SAlan Somers 	expect_setxattr(0);
1345ff4fbdf5SAlan Somers 
13465a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
13475a0b9a27SAlan Somers 		value_len);
1348ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1349ff4fbdf5SAlan Somers }
1350ff4fbdf5SAlan Somers 
1351ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
13529821f1d3SAlan Somers {
13539821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
13549821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
13559821f1d3SAlan Somers 	uint64_t ino = 42;
1356331884f2SAlan Somers 	sem_t sem;
1357331884f2SAlan Somers 
1358331884f2SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
13599821f1d3SAlan Somers 
1360a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1361ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1362a34cdd26SAlan Somers 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
1363331884f2SAlan Somers 	expect_forget(ino, 1, &sem);
13649821f1d3SAlan Somers 
1365ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1366331884f2SAlan Somers 
1367331884f2SAlan Somers 	sem_wait(&sem);
1368331884f2SAlan Somers 	sem_destroy(&sem);
1369ff4fbdf5SAlan Somers }
1370ff4fbdf5SAlan Somers 
13716124fd71SAlan Somers /*
13726124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
13736124fd71SAlan Somers  * in VOP_LOOKUP.
13746124fd71SAlan Somers  *
13756124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
13766124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
13776124fd71SAlan Somers  */
13786124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
13796124fd71SAlan Somers {
13806124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
13816124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
13826124fd71SAlan Somers 	uint64_t ino = 42;
13836124fd71SAlan Somers 
1384a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1385a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
13866124fd71SAlan Somers 	.Times(AnyNumber())
13876124fd71SAlan Somers 	.WillRepeatedly(Invoke(
138829edc611SAlan Somers 		ReturnImmediate([=](auto i __unused, auto& out) {
13896124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
139029edc611SAlan Somers 			out.body.entry.attr.mode = S_IFREG | 0644;
139129edc611SAlan Somers 			out.body.entry.nodeid = ino;
139229edc611SAlan Somers 			out.body.entry.entry_valid = UINT64_MAX;
13936124fd71SAlan Somers 		}))
13946124fd71SAlan Somers 	);
13956124fd71SAlan Somers 
13966124fd71SAlan Somers 	/* Fill name cache */
13976124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
13986124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
13996124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
14006124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
14016124fd71SAlan Somers }
14026124fd71SAlan Somers 
1403ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
1404ff4fbdf5SAlan Somers {
1405ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1406ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1407ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1408ff4fbdf5SAlan Somers 
1409a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1410ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1411ff4fbdf5SAlan Somers 
1412ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1413ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1414ff4fbdf5SAlan Somers }
1415ff4fbdf5SAlan Somers 
14166124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
1417ff4fbdf5SAlan Somers {
1418ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1419ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1420ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1421ff4fbdf5SAlan Somers 
1422a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1423ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1424ff4fbdf5SAlan Somers 
1425ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1426ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
14279821f1d3SAlan Somers }
1428a90e32deSAlan Somers 
1429a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
1430a90e32deSAlan Somers TEST_F(Write, clear_suid)
1431a90e32deSAlan Somers {
1432a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1433a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1434a90e32deSAlan Somers 	struct stat sb;
1435a90e32deSAlan Somers 	uint64_t ino = 42;
1436a90e32deSAlan Somers 	mode_t oldmode = 04777;
1437a90e32deSAlan Somers 	mode_t newmode = 0777;
1438a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1439a90e32deSAlan Somers 	int fd;
1440a90e32deSAlan Somers 
1441a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1442a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1443a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1444bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
144518a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1446a90e32deSAlan Somers 
1447a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1448a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1449a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1450a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1451a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
14527fc0921dSAlan Somers 	leak(fd);
1453a90e32deSAlan Somers }
1454a90e32deSAlan Somers 
1455a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
1456a90e32deSAlan Somers TEST_F(Write, clear_sgid)
1457a90e32deSAlan Somers {
1458a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1459a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1460a90e32deSAlan Somers 	struct stat sb;
1461a90e32deSAlan Somers 	uint64_t ino = 42;
1462a90e32deSAlan Somers 	mode_t oldmode = 02777;
1463a90e32deSAlan Somers 	mode_t newmode = 0777;
1464a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1465a90e32deSAlan Somers 	int fd;
1466a90e32deSAlan Somers 
1467a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1468a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1469a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1470bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
147118a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1472a90e32deSAlan Somers 
1473a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1474a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1475a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1476a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1477a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
14787fc0921dSAlan Somers 	leak(fd);
1479a90e32deSAlan Somers }
148018a2264eSAlan Somers 
148118a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic
148218a2264eSAlan Somers  *
148318a2264eSAlan Somers  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
148418a2264eSAlan Somers  * may panic.  That happens if the FUSE_SETATTR response indicates that the
148518a2264eSAlan Somers  * file's size has changed since the write.
148618a2264eSAlan Somers  */
148718a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid)
148818a2264eSAlan Somers {
148918a2264eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
149018a2264eSAlan Somers 	const char RELPATH[] = "some_file.txt";
149118a2264eSAlan Somers 	uint64_t ino = 42;
149218a2264eSAlan Somers 	mode_t oldmode = 04777;
149318a2264eSAlan Somers 	mode_t newmode = 0777;
149418a2264eSAlan Somers 	char wbuf[1] = {'x'};
149518a2264eSAlan Somers 	int fd;
149618a2264eSAlan Somers 
1497a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
149818a2264eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
149918a2264eSAlan Somers 	expect_open(ino, 0, 1);
1500bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
150118a2264eSAlan Somers 	/* XXX Return a smaller file size than what we just wrote! */
150218a2264eSAlan Somers 	expect_chmod(ino, newmode, 0);
150318a2264eSAlan Somers 
150418a2264eSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
150518a2264eSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
150618a2264eSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
15077fc0921dSAlan Somers 	leak(fd);
150818a2264eSAlan Somers }
1509