xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision e5236d25f2c0709acf3547e6af45f4bb4eec4f02)
19821f1d3SAlan Somers /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
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.
299821f1d3SAlan Somers  */
309821f1d3SAlan Somers 
319821f1d3SAlan Somers /*
329821f1d3SAlan Somers  * Tests for the "default_permissions" mount option.  They must be in their own
33*fb619c94SAlan Somers  * file so they can be run as an unprivileged user.
349821f1d3SAlan Somers  */
359821f1d3SAlan Somers 
369821f1d3SAlan Somers extern "C" {
37ff4fbdf5SAlan Somers #include <sys/types.h>
38ff4fbdf5SAlan Somers #include <sys/extattr.h>
39ff4fbdf5SAlan Somers 
409821f1d3SAlan Somers #include <fcntl.h>
41331884f2SAlan Somers #include <semaphore.h>
429821f1d3SAlan Somers #include <unistd.h>
439821f1d3SAlan Somers }
449821f1d3SAlan Somers 
459821f1d3SAlan Somers #include "mockfs.hh"
469821f1d3SAlan Somers #include "utils.hh"
479821f1d3SAlan Somers 
489821f1d3SAlan Somers using namespace testing;
499821f1d3SAlan Somers 
509821f1d3SAlan Somers class DefaultPermissions: public FuseTest {
519821f1d3SAlan Somers 
SetUp()529821f1d3SAlan Somers virtual void SetUp() {
53ff4fbdf5SAlan Somers 	m_default_permissions = true;
549821f1d3SAlan Somers 	FuseTest::SetUp();
55ff4fbdf5SAlan Somers 	if (HasFatalFailure() || IsSkipped())
56ff4fbdf5SAlan Somers 		return;
579821f1d3SAlan Somers 
589821f1d3SAlan Somers 	if (geteuid() == 0) {
599821f1d3SAlan Somers 		GTEST_SKIP() << "This test requires an unprivileged user";
609821f1d3SAlan Somers 	}
61ff4fbdf5SAlan Somers 
62ff4fbdf5SAlan Somers 	/* With -o default_permissions, FUSE_ACCESS should never be called */
63ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
64ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
6529edc611SAlan Somers 			return (in.header.opcode == FUSE_ACCESS);
66ff4fbdf5SAlan Somers 		}, Eq(true)),
67ff4fbdf5SAlan Somers 		_)
68ff4fbdf5SAlan Somers 	).Times(0);
699821f1d3SAlan Somers }
709821f1d3SAlan Somers 
719821f1d3SAlan Somers public:
expect_chmod(uint64_t ino,mode_t mode,uint64_t size=0)7218a2264eSAlan Somers void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0)
73a90e32deSAlan Somers {
74a90e32deSAlan Somers 	EXPECT_CALL(*m_mock, process(
75a90e32deSAlan Somers 		ResultOf([=](auto in) {
7629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
7729edc611SAlan Somers 				in.header.nodeid == ino &&
7829edc611SAlan Somers 				in.body.setattr.valid == FATTR_MODE &&
7929edc611SAlan Somers 				in.body.setattr.mode == mode);
80a90e32deSAlan Somers 		}, Eq(true)),
81a90e32deSAlan Somers 		_)
8229edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
83a90e32deSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
8429edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
8529edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
8629edc611SAlan Somers 		out.body.attr.attr.size = size;
8729edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
88a90e32deSAlan Somers 	})));
89a90e32deSAlan Somers }
90a90e32deSAlan Somers 
expect_create(const char * relpath,uint64_t ino)913fa12789SAlan Somers void expect_create(const char *relpath, uint64_t ino)
923fa12789SAlan Somers {
933fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
943fa12789SAlan Somers 		ResultOf([=](auto in) {
9529edc611SAlan Somers 			const char *name = (const char*)in.body.bytes +
96a4856c96SAlan Somers 				sizeof(fuse_create_in);
9729edc611SAlan Somers 			return (in.header.opcode == FUSE_CREATE &&
983fa12789SAlan Somers 				(0 == strcmp(relpath, name)));
993fa12789SAlan Somers 		}, Eq(true)),
1003fa12789SAlan Somers 		_)
10129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1023fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
10329edc611SAlan Somers 		out.body.create.entry.attr.mode = S_IFREG | 0644;
10429edc611SAlan Somers 		out.body.create.entry.nodeid = ino;
10529edc611SAlan Somers 		out.body.create.entry.entry_valid = UINT64_MAX;
10629edc611SAlan Somers 		out.body.create.entry.attr_valid = UINT64_MAX;
1073fa12789SAlan Somers 	})));
1083fa12789SAlan Somers }
1093fa12789SAlan Somers 
expect_copy_file_range(uint64_t ino_in,uint64_t off_in,uint64_t ino_out,uint64_t off_out,uint64_t len)11092bbfe1fSAlan Somers void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
11192bbfe1fSAlan Somers     uint64_t off_out, uint64_t len)
11292bbfe1fSAlan Somers {
11392bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
11492bbfe1fSAlan Somers 		ResultOf([=](auto in) {
11592bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
11692bbfe1fSAlan Somers 				in.header.nodeid == ino_in &&
11792bbfe1fSAlan Somers 				in.body.copy_file_range.off_in == off_in &&
11892bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino_out &&
11992bbfe1fSAlan Somers 				in.body.copy_file_range.off_out == off_out &&
12092bbfe1fSAlan Somers 				in.body.copy_file_range.len == len);
12192bbfe1fSAlan Somers 		}, Eq(true)),
12292bbfe1fSAlan Somers 		_)
12392bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
12492bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
12592bbfe1fSAlan Somers 		out.body.write.size = len;
12692bbfe1fSAlan Somers 	})));
12792bbfe1fSAlan Somers }
12892bbfe1fSAlan Somers 
expect_getattr(uint64_t ino,mode_t mode,uint64_t attr_valid,int times,uid_t uid=0,gid_t gid=0)129ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
130474ba6faSAlan Somers 	uid_t uid = 0, gid_t gid = 0)
1319821f1d3SAlan Somers {
132ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
133ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
13429edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
13529edc611SAlan Somers 				in.header.nodeid == ino);
136ff4fbdf5SAlan Somers 		}, Eq(true)),
137ff4fbdf5SAlan Somers 		_)
138ff4fbdf5SAlan Somers 	).Times(times)
13929edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
140ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
14129edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
14229edc611SAlan Somers 		out.body.attr.attr.mode = mode;
14329edc611SAlan Somers 		out.body.attr.attr.size = 0;
14429edc611SAlan Somers 		out.body.attr.attr.uid = uid;
145a22a7807SAlan Somers 		out.body.attr.attr.gid = gid;
14629edc611SAlan Somers 		out.body.attr.attr_valid = attr_valid;
147ff4fbdf5SAlan Somers 	})));
148ff4fbdf5SAlan Somers }
149ff4fbdf5SAlan Somers 
expect_lookup(const char * relpath,uint64_t ino,mode_t mode,uint64_t attr_valid,uid_t uid=0,gid_t gid=0)150ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
151474ba6faSAlan Somers 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
152ff4fbdf5SAlan Somers {
153474ba6faSAlan Somers 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
1549821f1d3SAlan Somers }
1559821f1d3SAlan Somers 
1569821f1d3SAlan Somers };
1579821f1d3SAlan Somers 
1589821f1d3SAlan Somers class Access: public DefaultPermissions {};
159474ba6faSAlan Somers class Chown: public DefaultPermissions {};
160474ba6faSAlan Somers class Chgrp: public DefaultPermissions {};
16192bbfe1fSAlan Somers class CopyFileRange: public DefaultPermissions {};
16289d57b94SAlan Somers class Fspacectl: public DefaultPermissions {};
163ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1649821f1d3SAlan Somers class Open: public DefaultPermissions {};
165398c88c7SAlan Somers class PosixFallocate: public DefaultPermissions {};
166*fb619c94SAlan Somers class Read: 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:
expect_removexattr()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:
expect_getxattr(ProcessMockerT r)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:
expect_listxattr()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 	 */
expect_rename(int error)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:
expect_setxattr(int error)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 */
excluded_group()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 
TEST_F(Access,eacces)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 
TEST_F(Access,eacces_no_cached_attrs)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 
TEST_F(Access,ok)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 */
TEST_F(Chown,chown_to_self)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  */
TEST_F(Chown,clear_suid)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 */
TEST_F(Chown,eperm)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  */
TEST_F(Chgrp,clear_suid)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 */
TEST_F(Chgrp,eperm)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 
TEST_F(Chgrp,ok)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 */
TEST_F(CopyFileRange,clear_sgid)502398c88c7SAlan 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 */
TEST_F(CopyFileRange,clear_suid)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 
TEST_F(Create,ok)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 
TEST_F(Create,eacces)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 
TEST_F(Deleteextattr,eacces)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 
TEST_F(Deleteextattr,ok)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 */
TEST_F(Deleteextattr,system)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 */
TEST_F(Utimensat,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 */
TEST_F(Utimensat,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 */
TEST_F(Deleteextattr,user)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 
TEST_F(Getextattr,eacces)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 
TEST_F(Getextattr,ok)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 */
TEST_F(Getextattr,system)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 
TEST_F(Listextattr,eacces)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 
TEST_F(Listextattr,ok)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 */
TEST_F(Listextattr,system)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 
83889d57b94SAlan Somers /* A write by a non-owner should clear a file's SGID bit */
TEST_F(Fspacectl,clear_sgid)83989d57b94SAlan Somers TEST_F(Fspacectl, clear_sgid)
84089d57b94SAlan Somers {
84189d57b94SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
84289d57b94SAlan Somers 	const char RELPATH[] = "file.txt";
84389d57b94SAlan Somers 	struct stat sb;
84489d57b94SAlan Somers 	struct spacectl_range rqsr;
84589d57b94SAlan Somers 	uint64_t ino = 42;
84689d57b94SAlan Somers 	mode_t oldmode = 02777;
84789d57b94SAlan Somers 	mode_t newmode = 0777;
84889d57b94SAlan Somers 	off_t fsize = 16;
84989d57b94SAlan Somers 	off_t off = 8;
85089d57b94SAlan Somers 	off_t len = 8;
85189d57b94SAlan Somers 	int fd;
85289d57b94SAlan Somers 
85389d57b94SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
85489d57b94SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
85589d57b94SAlan Somers 	    1, UINT64_MAX, 0, 0);
85689d57b94SAlan Somers 	expect_open(ino, 0, 1);
85789d57b94SAlan Somers 	expect_fallocate(ino, off, len,
85889d57b94SAlan Somers 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
85989d57b94SAlan Somers 	expect_chmod(ino, newmode, fsize);
86089d57b94SAlan Somers 
86189d57b94SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
86289d57b94SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
86389d57b94SAlan Somers 	rqsr.r_len = len;
86489d57b94SAlan Somers 	rqsr.r_offset = off;
86589d57b94SAlan Somers 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
86689d57b94SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
86789d57b94SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
86889d57b94SAlan Somers 
86989d57b94SAlan Somers 	leak(fd);
87089d57b94SAlan Somers }
87189d57b94SAlan Somers 
87289d57b94SAlan Somers /* A write by a non-owner should clear a file's SUID bit */
TEST_F(Fspacectl,clear_suid)87389d57b94SAlan Somers TEST_F(Fspacectl, clear_suid)
87489d57b94SAlan Somers {
87589d57b94SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
87689d57b94SAlan Somers 	const char RELPATH[] = "file.txt";
87789d57b94SAlan Somers 	struct stat sb;
87889d57b94SAlan Somers 	struct spacectl_range rqsr;
87989d57b94SAlan Somers 	uint64_t ino = 42;
88089d57b94SAlan Somers 	mode_t oldmode = 04777;
88189d57b94SAlan Somers 	mode_t newmode = 0777;
88289d57b94SAlan Somers 	off_t fsize = 16;
88389d57b94SAlan Somers 	off_t off = 8;
88489d57b94SAlan Somers 	off_t len = 8;
88589d57b94SAlan Somers 	int fd;
88689d57b94SAlan Somers 
88789d57b94SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
88889d57b94SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
88989d57b94SAlan Somers 	    1, UINT64_MAX, 0, 0);
89089d57b94SAlan Somers 	expect_open(ino, 0, 1);
89189d57b94SAlan Somers 	expect_fallocate(ino, off, len,
89289d57b94SAlan Somers 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
89389d57b94SAlan Somers 	expect_chmod(ino, newmode, fsize);
89489d57b94SAlan Somers 
89589d57b94SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
89689d57b94SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
89789d57b94SAlan Somers 	rqsr.r_len = len;
89889d57b94SAlan Somers 	rqsr.r_offset = off;
89989d57b94SAlan Somers 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
90089d57b94SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
90189d57b94SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
90289d57b94SAlan Somers 
90389d57b94SAlan Somers 	leak(fd);
90489d57b94SAlan Somers }
90589d57b94SAlan Somers 
90689d57b94SAlan Somers /*
90789d57b94SAlan Somers  * fspacectl() of a file without writable permissions should succeed as
90889d57b94SAlan Somers  * long as the file descriptor is writable.  This is important when combined
90989d57b94SAlan Somers  * with O_CREAT
91089d57b94SAlan Somers  */
TEST_F(Fspacectl,posix_fallocate_of_newly_created_file)91189d57b94SAlan Somers TEST_F(Fspacectl, posix_fallocate_of_newly_created_file)
91289d57b94SAlan Somers {
91389d57b94SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
91489d57b94SAlan Somers 	const char RELPATH[] = "some_file.txt";
91589d57b94SAlan Somers 	struct spacectl_range rqsr;
91689d57b94SAlan Somers 	const uint64_t ino = 42;
91789d57b94SAlan Somers 	off_t off = 8;
91889d57b94SAlan Somers 	off_t len = 8;
91989d57b94SAlan Somers 	int fd;
92089d57b94SAlan Somers 
92189d57b94SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
92289d57b94SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
92389d57b94SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
92489d57b94SAlan Somers 	expect_create(RELPATH, ino);
92589d57b94SAlan Somers 	expect_fallocate(ino, off, len,
92689d57b94SAlan Somers 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
92789d57b94SAlan Somers 
92889d57b94SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
92989d57b94SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
93089d57b94SAlan Somers 	rqsr.r_len = len;
93189d57b94SAlan Somers 	rqsr.r_offset = off;
93289d57b94SAlan Somers 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
93389d57b94SAlan Somers 	leak(fd);
93489d57b94SAlan Somers }
93589d57b94SAlan Somers 
936ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
TEST_F(Lookup,eacces)937ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
938ff4fbdf5SAlan Somers {
939ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
940ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
941ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
942ff4fbdf5SAlan Somers 
943a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
944ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
945ff4fbdf5SAlan Somers 
946ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
947ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
948ff4fbdf5SAlan Somers }
949ff4fbdf5SAlan Somers 
TEST_F(Open,eacces)950ff4fbdf5SAlan Somers TEST_F(Open, eacces)
951ff4fbdf5SAlan Somers {
952ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
953ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
954ff4fbdf5SAlan Somers 	uint64_t ino = 42;
955ff4fbdf5SAlan Somers 
956a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
957ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
958ff4fbdf5SAlan Somers 
9594ca1c0b7SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_RDWR));
960ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
961ff4fbdf5SAlan Somers }
962ff4fbdf5SAlan Somers 
TEST_F(Open,ok)9639821f1d3SAlan Somers TEST_F(Open, ok)
9649821f1d3SAlan Somers {
9659821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
9669821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
9679821f1d3SAlan Somers 	uint64_t ino = 42;
9689821f1d3SAlan Somers 	int fd;
9699821f1d3SAlan Somers 
970a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
971ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
9729821f1d3SAlan Somers 	expect_open(ino, 0, 1);
9739821f1d3SAlan Somers 
9749821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
975d2621689SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
9767fc0921dSAlan Somers 	leak(fd);
9779821f1d3SAlan Somers }
9789821f1d3SAlan Somers 
979398c88c7SAlan Somers /* A write by a non-owner should clear a file's SGID bit */
TEST_F(PosixFallocate,clear_sgid)980398c88c7SAlan Somers TEST_F(PosixFallocate, clear_sgid)
981398c88c7SAlan Somers {
982398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
983398c88c7SAlan Somers 	const char RELPATH[] = "file.txt";
984398c88c7SAlan Somers 	struct stat sb;
985398c88c7SAlan Somers 	uint64_t ino = 42;
986398c88c7SAlan Somers 	mode_t oldmode = 02777;
987398c88c7SAlan Somers 	mode_t newmode = 0777;
988398c88c7SAlan Somers 	off_t fsize = 16;
989398c88c7SAlan Somers 	off_t off = 8;
990398c88c7SAlan Somers 	off_t len = 8;
991398c88c7SAlan Somers 	int fd;
992398c88c7SAlan Somers 
993398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
994398c88c7SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
995398c88c7SAlan Somers 	    1, UINT64_MAX, 0, 0);
996398c88c7SAlan Somers 	expect_open(ino, 0, 1);
997398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
998398c88c7SAlan Somers 	expect_chmod(ino, newmode, fsize);
999398c88c7SAlan Somers 
1000398c88c7SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1001398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1002398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1003398c88c7SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1004398c88c7SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1005398c88c7SAlan Somers 
1006398c88c7SAlan Somers 	leak(fd);
1007398c88c7SAlan Somers }
1008398c88c7SAlan Somers 
1009398c88c7SAlan Somers /* A write by a non-owner should clear a file's SUID bit */
TEST_F(PosixFallocate,clear_suid)1010398c88c7SAlan Somers TEST_F(PosixFallocate, clear_suid)
1011398c88c7SAlan Somers {
1012398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
1013398c88c7SAlan Somers 	const char RELPATH[] = "file.txt";
1014398c88c7SAlan Somers 	struct stat sb;
1015398c88c7SAlan Somers 	uint64_t ino = 42;
1016398c88c7SAlan Somers 	mode_t oldmode = 04777;
1017398c88c7SAlan Somers 	mode_t newmode = 0777;
1018398c88c7SAlan Somers 	off_t fsize = 16;
1019398c88c7SAlan Somers 	off_t off = 8;
1020398c88c7SAlan Somers 	off_t len = 8;
1021398c88c7SAlan Somers 	int fd;
1022398c88c7SAlan Somers 
1023398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1024398c88c7SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
1025398c88c7SAlan Somers 	    1, UINT64_MAX, 0, 0);
1026398c88c7SAlan Somers 	expect_open(ino, 0, 1);
1027398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
1028398c88c7SAlan Somers 	expect_chmod(ino, newmode, fsize);
1029398c88c7SAlan Somers 
1030398c88c7SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1031398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1032398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1033398c88c7SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1034398c88c7SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1035398c88c7SAlan Somers 
1036398c88c7SAlan Somers 	leak(fd);
1037398c88c7SAlan Somers }
1038398c88c7SAlan Somers 
1039398c88c7SAlan Somers /*
104089d57b94SAlan Somers  * posix_fallocate() of a file without writable permissions should succeed as
1041398c88c7SAlan Somers  * long as the file descriptor is writable.  This is important when combined
1042398c88c7SAlan Somers  * with O_CREAT
1043398c88c7SAlan Somers  */
TEST_F(PosixFallocate,posix_fallocate_of_newly_created_file)1044398c88c7SAlan Somers TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file)
1045398c88c7SAlan Somers {
1046398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1047398c88c7SAlan Somers 	const char RELPATH[] = "some_file.txt";
1048398c88c7SAlan Somers 	const uint64_t ino = 42;
1049398c88c7SAlan Somers 	off_t off = 8;
1050398c88c7SAlan Somers 	off_t len = 8;
1051398c88c7SAlan Somers 	int fd;
1052398c88c7SAlan Somers 
1053398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1054398c88c7SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1055398c88c7SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1056398c88c7SAlan Somers 	expect_create(RELPATH, ino);
1057398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
1058398c88c7SAlan Somers 
1059398c88c7SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1060398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1061398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1062398c88c7SAlan Somers 	leak(fd);
1063398c88c7SAlan Somers }
1064398c88c7SAlan Somers 
TEST_F(Rename,eacces_on_srcdir)1065ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
1066ff4fbdf5SAlan Somers {
1067ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1068ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
1069ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1070ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1071ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1072ff4fbdf5SAlan Somers 
1073a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
1074ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1075a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1076ff4fbdf5SAlan Somers 		.Times(AnyNumber())
1077ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
1078ff4fbdf5SAlan Somers 
1079ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1080ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1081ff4fbdf5SAlan Somers }
1082ff4fbdf5SAlan Somers 
TEST_F(Rename,eacces_on_dstdir_for_creating)1083ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
1084ff4fbdf5SAlan Somers {
1085ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1086ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
1087ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1088ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1089ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1090ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1091ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1092ff4fbdf5SAlan Somers 
1093a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1094ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1095ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1096ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1097ff4fbdf5SAlan Somers 
1098ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1099ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1100ff4fbdf5SAlan Somers }
1101ff4fbdf5SAlan Somers 
TEST_F(Rename,eacces_on_dstdir_for_removing)1102ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
1103ff4fbdf5SAlan Somers {
1104ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1105ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
1106ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1107ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1108ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1109ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1110ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1111ff4fbdf5SAlan Somers 
1112a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1113ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1114ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1115ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1116ff4fbdf5SAlan Somers 
1117ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1118ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1119ff4fbdf5SAlan Somers }
1120ff4fbdf5SAlan Somers 
TEST_F(Rename,eperm_on_sticky_srcdir)11216124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
1122ff4fbdf5SAlan Somers {
1123ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1124ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1125ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1126ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1127ff4fbdf5SAlan Somers 
1128a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1129ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1130ff4fbdf5SAlan Somers 
1131ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1132ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1133ff4fbdf5SAlan Somers }
1134ff4fbdf5SAlan Somers 
11358e45ec4eSAlan Somers /*
11368e45ec4eSAlan Somers  * A user cannot move out a subdirectory that he does not own, because that
11378e45ec4eSAlan Somers  * would require changing the subdirectory's ".." dirent
11388e45ec4eSAlan Somers  */
TEST_F(Rename,eperm_for_subdirectory)11398e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory)
11408e45ec4eSAlan Somers {
11418e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
11428e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
11438e45ec4eSAlan Somers 	const char RELDSTDIR[] = "d";
11448e45ec4eSAlan Somers 	const char RELDST[] = "dst";
11458e45ec4eSAlan Somers 	const char RELSRC[] = "src";
11468e45ec4eSAlan Somers 	uint64_t ino = 42;
11478e45ec4eSAlan Somers 	uint64_t dstdir_ino = 43;
11488e45ec4eSAlan Somers 
1149a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
11508e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
11518e45ec4eSAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
11528e45ec4eSAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
11538e45ec4eSAlan Somers 
11548e45ec4eSAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
11558e45ec4eSAlan Somers 	ASSERT_EQ(EACCES, errno);
11568e45ec4eSAlan Somers }
11578e45ec4eSAlan Somers 
11588e45ec4eSAlan Somers /*
11598e45ec4eSAlan Somers  * A user _can_ rename a subdirectory to which he lacks write permissions, if
11608e45ec4eSAlan Somers  * it will keep the same parent
11618e45ec4eSAlan Somers  */
TEST_F(Rename,subdirectory_to_same_dir)11628e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir)
11638e45ec4eSAlan Somers {
11648e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/dst";
11658e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
11668e45ec4eSAlan Somers 	const char RELDST[] = "dst";
11678e45ec4eSAlan Somers 	const char RELSRC[] = "src";
11688e45ec4eSAlan Somers 	uint64_t ino = 42;
11698e45ec4eSAlan Somers 
1170a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
11718e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
1172a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1173a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
11748e45ec4eSAlan Somers 	expect_rename(0);
11758e45ec4eSAlan Somers 
11768e45ec4eSAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
11778e45ec4eSAlan Somers }
11788e45ec4eSAlan Somers 
TEST_F(Rename,eperm_on_sticky_dstdir)11796124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
1180ff4fbdf5SAlan Somers {
1181ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1182ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
11836124fd71SAlan Somers 	const char RELDST[] = "dst";
1184ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1185ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1186ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1187ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1188ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
1189ff4fbdf5SAlan Somers 
1190a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1191ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1192ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
11936124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
119429edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
11956124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
119629edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
119729edc611SAlan Somers 		out.body.entry.nodeid = dst_ino;
119829edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
119929edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
120029edc611SAlan Somers 		out.body.entry.attr.uid = 0;
12016124fd71SAlan Somers 	})));
1202ff4fbdf5SAlan Somers 
1203ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1204ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1205ff4fbdf5SAlan Somers }
1206ff4fbdf5SAlan Somers 
1207ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
TEST_F(Rename,ok)1208ff4fbdf5SAlan Somers TEST_F(Rename, ok)
1209ff4fbdf5SAlan Somers {
1210ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1211ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1212ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1213ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1214ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
1215ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
1216ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1217ff4fbdf5SAlan Somers 
1218a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
1219ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1220ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
1221ff4fbdf5SAlan Somers 	expect_rename(0);
1222ff4fbdf5SAlan Somers 
1223ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1224ff4fbdf5SAlan Somers }
1225ff4fbdf5SAlan Somers 
TEST_F(Rename,ok_to_remove_src_because_of_stickiness)1226ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
1227ff4fbdf5SAlan Somers {
1228ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1229ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1230ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1231ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1232ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1233ff4fbdf5SAlan Somers 
1234a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1235ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1236a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1237a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1238ff4fbdf5SAlan Somers 	expect_rename(0);
1239ff4fbdf5SAlan Somers 
1240ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1241ff4fbdf5SAlan Somers }
1242ff4fbdf5SAlan Somers 
1243*fb619c94SAlan Somers // Don't update atime during close after read, if we lack permissions to write
1244*fb619c94SAlan Somers // that file.
TEST_F(Read,atime_during_close)1245*fb619c94SAlan Somers TEST_F(Read, atime_during_close)
1246*fb619c94SAlan Somers {
1247*fb619c94SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1248*fb619c94SAlan Somers 	const char RELPATH[] = "some_file.txt";
1249*fb619c94SAlan Somers 	uint64_t ino = 42;
1250*fb619c94SAlan Somers 	int fd;
1251*fb619c94SAlan Somers 	ssize_t bufsize = 100;
1252*fb619c94SAlan Somers 	uint8_t buf[bufsize];
1253*fb619c94SAlan Somers 	const char *CONTENTS = "abcdefgh";
1254*fb619c94SAlan Somers 	ssize_t fsize = sizeof(CONTENTS);
1255*fb619c94SAlan Somers 
1256*fb619c94SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1257*fb619c94SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0755, fsize,
1258*fb619c94SAlan Somers 		1, UINT64_MAX, 0, 0);
1259*fb619c94SAlan Somers 	expect_open(ino, 0, 1);
1260*fb619c94SAlan Somers 	expect_read(ino, 0, fsize, fsize, CONTENTS);
1261*fb619c94SAlan Somers 	EXPECT_CALL(*m_mock, process(
1262*fb619c94SAlan Somers 		ResultOf([&](auto in) {
1263*fb619c94SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1264*fb619c94SAlan Somers 		}, Eq(true)),
1265*fb619c94SAlan Somers 		_)
1266*fb619c94SAlan Somers 	).Times(0);
1267*fb619c94SAlan Somers 	expect_flush(ino, 1, ReturnErrno(0));
1268*fb619c94SAlan Somers 	expect_release(ino, FuseTest::FH);
1269*fb619c94SAlan Somers 
1270*fb619c94SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
1271*fb619c94SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1272*fb619c94SAlan Somers 
1273*fb619c94SAlan Somers 	/* Ensure atime will be different than during lookup */
1274*fb619c94SAlan Somers 	nap();
1275*fb619c94SAlan Somers 
1276*fb619c94SAlan Somers 	ASSERT_EQ(fsize, read(fd, buf, bufsize)) << strerror(errno);
1277*fb619c94SAlan Somers 
1278*fb619c94SAlan Somers 	close(fd);
1279*fb619c94SAlan Somers }
1280*fb619c94SAlan Somers 
TEST_F(Setattr,ok)1281ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
1282ff4fbdf5SAlan Somers {
1283ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1284ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1285ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
1286ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
1287ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
1288ff4fbdf5SAlan Somers 
1289a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1290ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1291ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
1292ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
129329edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
129429edc611SAlan Somers 				in.header.nodeid == ino &&
129529edc611SAlan Somers 				in.body.setattr.mode == newmode);
1296ff4fbdf5SAlan Somers 		}, Eq(true)),
1297ff4fbdf5SAlan Somers 		_)
129829edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1299ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
130029edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
1301ff4fbdf5SAlan Somers 	})));
1302ff4fbdf5SAlan Somers 
1303ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1304ff4fbdf5SAlan Somers }
1305ff4fbdf5SAlan Somers 
TEST_F(Setattr,eacces)1306ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
1307ff4fbdf5SAlan Somers {
1308ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1309ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1310ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
1311ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
1312ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
1313ff4fbdf5SAlan Somers 
1314a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1315ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
1316ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
1317ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
131829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1319ff4fbdf5SAlan Somers 		}, Eq(true)),
1320ff4fbdf5SAlan Somers 		_)
1321ff4fbdf5SAlan Somers 	).Times(0);
1322ff4fbdf5SAlan Somers 
1323ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1324ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
1325ff4fbdf5SAlan Somers }
1326ff4fbdf5SAlan Somers 
13278cfb4431SAlan Somers /*
13283fa12789SAlan Somers  * ftruncate() of a file without writable permissions should succeed as long as
13293fa12789SAlan Somers  * the file descriptor is writable.  This is important when combined with
13303fa12789SAlan Somers  * O_CREAT
13313fa12789SAlan Somers  */
TEST_F(Setattr,ftruncate_of_newly_created_file)13323fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file)
13333fa12789SAlan Somers {
13343fa12789SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
13353fa12789SAlan Somers 	const char RELPATH[] = "some_file.txt";
13363fa12789SAlan Somers 	const uint64_t ino = 42;
13373fa12789SAlan Somers 	const mode_t mode = 0000;
13383fa12789SAlan Somers 	int fd;
13393fa12789SAlan Somers 
1340a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1341a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1342a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
13433fa12789SAlan Somers 	expect_create(RELPATH, ino);
13443fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
13453fa12789SAlan Somers 		ResultOf([](auto in) {
134629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
134729edc611SAlan Somers 				in.header.nodeid == ino &&
134829edc611SAlan Somers 				(in.body.setattr.valid & FATTR_SIZE));
13493fa12789SAlan Somers 		}, Eq(true)),
13503fa12789SAlan Somers 		_)
135129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
13523fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
135329edc611SAlan Somers 		out.body.attr.attr.ino = ino;
135429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
135529edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
13563fa12789SAlan Somers 	})));
13573fa12789SAlan Somers 
13583fa12789SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
13593fa12789SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
13603fa12789SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
13617fc0921dSAlan Somers 	leak(fd);
13623fa12789SAlan Somers }
13633fa12789SAlan Somers 
13643fa12789SAlan Somers /*
13658cfb4431SAlan Somers  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
13668cfb4431SAlan Somers  * to the file's group
13678cfb4431SAlan Somers  */
TEST_F(Setattr,sgid_by_non_group_member)13688cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member)
13698cfb4431SAlan Somers {
13708cfb4431SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
13718cfb4431SAlan Somers 	const char RELPATH[] = "some_file.txt";
13728cfb4431SAlan Somers 	const uint64_t ino = 42;
13738cfb4431SAlan Somers 	const mode_t oldmode = 0755;
13748cfb4431SAlan Somers 	const mode_t newmode = 02755;
13758cfb4431SAlan Somers 	uid_t uid = geteuid();
13768cfb4431SAlan Somers 	gid_t gid = excluded_group();
13778cfb4431SAlan Somers 
1378a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
13798cfb4431SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
13808cfb4431SAlan Somers 	EXPECT_CALL(*m_mock, process(
13818cfb4431SAlan Somers 		ResultOf([](auto in) {
138229edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
13838cfb4431SAlan Somers 		}, Eq(true)),
13848cfb4431SAlan Somers 		_)
13858cfb4431SAlan Somers 	).Times(0);
13868cfb4431SAlan Somers 
13878cfb4431SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
13888cfb4431SAlan Somers 	EXPECT_EQ(EPERM, errno);
13898cfb4431SAlan Somers }
13908cfb4431SAlan Somers 
1391e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
TEST_F(Setattr,sticky_regular_file)1392e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
1393e5ff3a7eSAlan Somers {
1394e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1395e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
1396e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
1397e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
1398e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
1399e5ff3a7eSAlan Somers 
1400a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1401e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1402e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
1403e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
140429edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1405e5ff3a7eSAlan Somers 		}, Eq(true)),
1406e5ff3a7eSAlan Somers 		_)
1407e5ff3a7eSAlan Somers 	).Times(0);
1408e5ff3a7eSAlan Somers 
1409e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1410e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
1411e5ff3a7eSAlan Somers }
1412e5ff3a7eSAlan Somers 
TEST_F(Setextattr,ok)1413ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
1414ff4fbdf5SAlan Somers {
1415ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1416ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1417ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1418ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1419ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1420ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1421ff4fbdf5SAlan Somers 	ssize_t r;
1422ff4fbdf5SAlan Somers 
1423a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1424ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1425ff4fbdf5SAlan Somers 	expect_setxattr(0);
1426ff4fbdf5SAlan Somers 
14275a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
14285a0b9a27SAlan Somers 		value_len);
1429ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1430ff4fbdf5SAlan Somers }
1431ff4fbdf5SAlan Somers 
TEST_F(Setextattr,eacces)1432ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
1433ff4fbdf5SAlan Somers {
1434ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1435ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1436ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1437ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1438ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1439ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1440ff4fbdf5SAlan Somers 
1441a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1442ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1443ff4fbdf5SAlan Somers 
14445a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
14455a0b9a27SAlan Somers 		value_len));
1446ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1447ff4fbdf5SAlan Somers }
1448ff4fbdf5SAlan Somers 
1449ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
TEST_F(Setextattr,system)1450ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
1451ff4fbdf5SAlan Somers {
1452ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1453ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1454ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1455ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1456ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1457ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1458ff4fbdf5SAlan Somers 
1459a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1460ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1461ff4fbdf5SAlan Somers 
14625a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
14635a0b9a27SAlan Somers 		value_len));
1464ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1465ff4fbdf5SAlan Somers }
1466ff4fbdf5SAlan Somers 
1467ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
TEST_F(Setextattr,user)1468ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
1469ff4fbdf5SAlan Somers {
1470ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1471ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1472ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1473ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1474ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1475ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1476ff4fbdf5SAlan Somers 	ssize_t r;
1477ff4fbdf5SAlan Somers 
1478a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1479ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1480ff4fbdf5SAlan Somers 	expect_setxattr(0);
1481ff4fbdf5SAlan Somers 
14825a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
14835a0b9a27SAlan Somers 		value_len);
1484ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1485ff4fbdf5SAlan Somers }
1486ff4fbdf5SAlan Somers 
TEST_F(Unlink,ok)1487ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
14889821f1d3SAlan Somers {
14899821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
14909821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
14919821f1d3SAlan Somers 	uint64_t ino = 42;
1492331884f2SAlan Somers 	sem_t sem;
1493331884f2SAlan Somers 
1494331884f2SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
14959821f1d3SAlan Somers 
1496a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1497ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1498a34cdd26SAlan Somers 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
1499331884f2SAlan Somers 	expect_forget(ino, 1, &sem);
15009821f1d3SAlan Somers 
1501ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1502331884f2SAlan Somers 
1503331884f2SAlan Somers 	sem_wait(&sem);
1504331884f2SAlan Somers 	sem_destroy(&sem);
1505ff4fbdf5SAlan Somers }
1506ff4fbdf5SAlan Somers 
15076124fd71SAlan Somers /*
15086124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
15096124fd71SAlan Somers  * in VOP_LOOKUP.
15106124fd71SAlan Somers  *
15116124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
15126124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
15136124fd71SAlan Somers  */
TEST_F(Unlink,cached_unwritable_directory)15146124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
15156124fd71SAlan Somers {
15166124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
15176124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
15186124fd71SAlan Somers 	uint64_t ino = 42;
15196124fd71SAlan Somers 
1520a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1521a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
15226124fd71SAlan Somers 	.Times(AnyNumber())
15236124fd71SAlan Somers 	.WillRepeatedly(Invoke(
152429edc611SAlan Somers 		ReturnImmediate([=](auto i __unused, auto& out) {
15256124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
152629edc611SAlan Somers 			out.body.entry.attr.mode = S_IFREG | 0644;
152729edc611SAlan Somers 			out.body.entry.nodeid = ino;
152829edc611SAlan Somers 			out.body.entry.entry_valid = UINT64_MAX;
15296124fd71SAlan Somers 		}))
15306124fd71SAlan Somers 	);
15316124fd71SAlan Somers 
15326124fd71SAlan Somers 	/* Fill name cache */
15336124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
15346124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
15356124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
15366124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
15376124fd71SAlan Somers }
15386124fd71SAlan Somers 
TEST_F(Unlink,unwritable_directory)1539ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
1540ff4fbdf5SAlan Somers {
1541ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1542ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1543ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1544ff4fbdf5SAlan Somers 
1545a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1546ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1547ff4fbdf5SAlan Somers 
1548ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1549ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1550ff4fbdf5SAlan Somers }
1551ff4fbdf5SAlan Somers 
TEST_F(Unlink,sticky_directory)15526124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
1553ff4fbdf5SAlan Somers {
1554ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1555ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1556ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1557ff4fbdf5SAlan Somers 
1558a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1559ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1560ff4fbdf5SAlan Somers 
1561ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1562ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
15639821f1d3SAlan Somers }
1564a90e32deSAlan Somers 
1565a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
TEST_F(Write,clear_suid)1566a90e32deSAlan Somers TEST_F(Write, clear_suid)
1567a90e32deSAlan Somers {
1568a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1569a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1570a90e32deSAlan Somers 	struct stat sb;
1571a90e32deSAlan Somers 	uint64_t ino = 42;
1572a90e32deSAlan Somers 	mode_t oldmode = 04777;
1573a90e32deSAlan Somers 	mode_t newmode = 0777;
1574a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1575a90e32deSAlan Somers 	int fd;
1576a90e32deSAlan Somers 
1577a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1578a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1579a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1580bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
158118a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1582a90e32deSAlan Somers 
1583a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1584a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1585a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1586a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1587a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
15887fc0921dSAlan Somers 	leak(fd);
1589a90e32deSAlan Somers }
1590a90e32deSAlan Somers 
1591a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
TEST_F(Write,clear_sgid)1592a90e32deSAlan Somers TEST_F(Write, clear_sgid)
1593a90e32deSAlan Somers {
1594a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1595a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1596a90e32deSAlan Somers 	struct stat sb;
1597a90e32deSAlan Somers 	uint64_t ino = 42;
1598a90e32deSAlan Somers 	mode_t oldmode = 02777;
1599a90e32deSAlan Somers 	mode_t newmode = 0777;
1600a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1601a90e32deSAlan Somers 	int fd;
1602a90e32deSAlan Somers 
1603a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1604a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1605a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1606bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
160718a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1608a90e32deSAlan Somers 
1609a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1610a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1611a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1612a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1613a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
16147fc0921dSAlan Somers 	leak(fd);
1615a90e32deSAlan Somers }
161618a2264eSAlan Somers 
161718a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic
161818a2264eSAlan Somers  *
161918a2264eSAlan Somers  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
162018a2264eSAlan Somers  * may panic.  That happens if the FUSE_SETATTR response indicates that the
162118a2264eSAlan Somers  * file's size has changed since the write.
162218a2264eSAlan Somers  */
TEST_F(Write,recursion_panic_while_clearing_suid)162318a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid)
162418a2264eSAlan Somers {
162518a2264eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
162618a2264eSAlan Somers 	const char RELPATH[] = "some_file.txt";
162718a2264eSAlan Somers 	uint64_t ino = 42;
162818a2264eSAlan Somers 	mode_t oldmode = 04777;
162918a2264eSAlan Somers 	mode_t newmode = 0777;
163018a2264eSAlan Somers 	char wbuf[1] = {'x'};
163118a2264eSAlan Somers 	int fd;
163218a2264eSAlan Somers 
1633a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
163418a2264eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
163518a2264eSAlan Somers 	expect_open(ino, 0, 1);
1636bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
163718a2264eSAlan Somers 	/* XXX Return a smaller file size than what we just wrote! */
163818a2264eSAlan Somers 	expect_chmod(ino, newmode, 0);
163918a2264eSAlan Somers 
164018a2264eSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
164118a2264eSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
164218a2264eSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
16437fc0921dSAlan Somers 	leak(fd);
164418a2264eSAlan Somers }
1645