xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 92bbfe1f0d1f1c4436d1f064a16e5aaf682526ba)
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 
112*92bbfe1fSAlan Somers void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
113*92bbfe1fSAlan Somers     uint64_t off_out, uint64_t len)
114*92bbfe1fSAlan Somers {
115*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
116*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
117*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
118*92bbfe1fSAlan Somers 				in.header.nodeid == ino_in &&
119*92bbfe1fSAlan Somers 				in.body.copy_file_range.off_in == off_in &&
120*92bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino_out &&
121*92bbfe1fSAlan Somers 				in.body.copy_file_range.off_out == off_out &&
122*92bbfe1fSAlan Somers 				in.body.copy_file_range.len == len);
123*92bbfe1fSAlan Somers 		}, Eq(true)),
124*92bbfe1fSAlan Somers 		_)
125*92bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
126*92bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
127*92bbfe1fSAlan Somers 		out.body.write.size = len;
128*92bbfe1fSAlan Somers 	})));
129*92bbfe1fSAlan Somers }
130*92bbfe1fSAlan 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 {};
163*92bbfe1fSAlan Somers class CopyFileRange: public DefaultPermissions {};
164ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1659821f1d3SAlan Somers class Open: public DefaultPermissions {};
166ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {};
167ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {};
168d943c93eSAlan Somers class Utimensat: public DefaultPermissions {};
169a90e32deSAlan Somers class Write: public DefaultPermissions {};
1709821f1d3SAlan Somers 
171ff4fbdf5SAlan Somers /*
172ff4fbdf5SAlan Somers  * Test permission handling during create, mkdir, mknod, link, symlink, and
173ff4fbdf5SAlan Somers  * rename vops (they all share a common path for permission checks in
174ff4fbdf5SAlan Somers  * VOP_LOOKUP)
175ff4fbdf5SAlan Somers  */
1763fa12789SAlan Somers class Create: public DefaultPermissions {};
177ff4fbdf5SAlan Somers 
178ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions {
179ff4fbdf5SAlan Somers public:
180ff4fbdf5SAlan Somers void expect_removexattr()
181ff4fbdf5SAlan Somers {
182ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
183ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
18429edc611SAlan Somers 			return (in.header.opcode == FUSE_REMOVEXATTR);
185ff4fbdf5SAlan Somers 		}, Eq(true)),
186ff4fbdf5SAlan Somers 		_)
187ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(0)));
188ff4fbdf5SAlan Somers }
189ff4fbdf5SAlan Somers };
190ff4fbdf5SAlan Somers 
191ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions {
192ff4fbdf5SAlan Somers public:
193ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r)
194ff4fbdf5SAlan Somers {
195ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
196ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
19729edc611SAlan Somers 			return (in.header.opcode == FUSE_GETXATTR);
198ff4fbdf5SAlan Somers 		}, Eq(true)),
199ff4fbdf5SAlan Somers 		_)
200ff4fbdf5SAlan Somers 	).WillOnce(Invoke(r));
201ff4fbdf5SAlan Somers }
202ff4fbdf5SAlan Somers };
203ff4fbdf5SAlan Somers 
204ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions {
205ff4fbdf5SAlan Somers public:
206ff4fbdf5SAlan Somers void expect_listxattr()
207ff4fbdf5SAlan Somers {
208ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
209ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
21029edc611SAlan Somers 			return (in.header.opcode == FUSE_LISTXATTR);
211ff4fbdf5SAlan Somers 		}, Eq(true)),
212ff4fbdf5SAlan Somers 		_)
21329edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
21429edc611SAlan Somers 		out.body.listxattr.size = 0;
215ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
216ff4fbdf5SAlan Somers 	})));
217ff4fbdf5SAlan Somers }
218ff4fbdf5SAlan Somers };
219ff4fbdf5SAlan Somers 
220ff4fbdf5SAlan Somers class Rename: public DefaultPermissions {
221ff4fbdf5SAlan Somers public:
222ff4fbdf5SAlan Somers 	/*
223ff4fbdf5SAlan Somers 	 * Expect a rename and respond with the given error.  Don't both to
224ff4fbdf5SAlan Somers 	 * validate arguments; the tests in rename.cc do that.
225ff4fbdf5SAlan Somers 	 */
226ff4fbdf5SAlan Somers 	void expect_rename(int error)
227ff4fbdf5SAlan Somers 	{
228ff4fbdf5SAlan Somers 		EXPECT_CALL(*m_mock, process(
229ff4fbdf5SAlan Somers 			ResultOf([=](auto in) {
23029edc611SAlan Somers 				return (in.header.opcode == FUSE_RENAME);
231ff4fbdf5SAlan Somers 			}, Eq(true)),
232ff4fbdf5SAlan Somers 			_)
233ff4fbdf5SAlan Somers 		).WillOnce(Invoke(ReturnErrno(error)));
234ff4fbdf5SAlan Somers 	}
235ff4fbdf5SAlan Somers };
236ff4fbdf5SAlan Somers 
237ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions {
238ff4fbdf5SAlan Somers public:
239ff4fbdf5SAlan Somers void expect_setxattr(int error)
240ff4fbdf5SAlan Somers {
241ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
242ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
24329edc611SAlan Somers 			return (in.header.opcode == FUSE_SETXATTR);
244ff4fbdf5SAlan Somers 		}, Eq(true)),
245ff4fbdf5SAlan Somers 		_)
246ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
247ff4fbdf5SAlan Somers }
248ff4fbdf5SAlan Somers };
249ff4fbdf5SAlan Somers 
2508cfb4431SAlan Somers /* Return a group to which this user does not belong */
2518cfb4431SAlan Somers static gid_t excluded_group()
2528cfb4431SAlan Somers {
2538cfb4431SAlan Somers 	int i, ngroups = 64;
2548cfb4431SAlan Somers 	gid_t newgid, groups[ngroups];
2558cfb4431SAlan Somers 
2568cfb4431SAlan Somers 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
2575a0b9a27SAlan Somers 	for (newgid = 0; ; newgid++) {
2588cfb4431SAlan Somers 		bool belongs = false;
2598cfb4431SAlan Somers 
2608cfb4431SAlan Somers 		for (i = 0; i < ngroups; i++) {
2618cfb4431SAlan Somers 			if (groups[i] == newgid)
2628cfb4431SAlan Somers 				belongs = true;
2638cfb4431SAlan Somers 		}
2648cfb4431SAlan Somers 		if (!belongs)
2658cfb4431SAlan Somers 			break;
2668cfb4431SAlan Somers 	}
2678cfb4431SAlan Somers 	/* newgid is now a group to which the current user does not belong */
2688cfb4431SAlan Somers 	return newgid;
2698cfb4431SAlan Somers }
2708cfb4431SAlan Somers 
271ff4fbdf5SAlan Somers TEST_F(Access, eacces)
2729821f1d3SAlan Somers {
2739821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2749821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2759821f1d3SAlan Somers 	uint64_t ino = 42;
2769821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
2779821f1d3SAlan Somers 
278a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
279ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
280ff4fbdf5SAlan Somers 
281ff4fbdf5SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
282ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
283ff4fbdf5SAlan Somers }
284ff4fbdf5SAlan Somers 
285ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs)
286ff4fbdf5SAlan Somers {
287ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
288ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
289ff4fbdf5SAlan Somers 	uint64_t ino = 42;
290ff4fbdf5SAlan Somers 	mode_t	access_mode = X_OK;
291ff4fbdf5SAlan Somers 
292a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1);
293ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
294ff4fbdf5SAlan Somers 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
2959821f1d3SAlan Somers 	/*
2969821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
2979821f1d3SAlan Somers 	 * another FUSE_GETATTR or something in here.  But there should not be
2989821f1d3SAlan Somers 	 * a FUSE_ACCESS
2999821f1d3SAlan Somers 	 */
3009821f1d3SAlan Somers 
3019821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
3029821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
3039821f1d3SAlan Somers }
3049821f1d3SAlan Somers 
305ff4fbdf5SAlan Somers TEST_F(Access, ok)
3069821f1d3SAlan Somers {
3079821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3089821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
3099821f1d3SAlan Somers 	uint64_t ino = 42;
3109821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
3119821f1d3SAlan Somers 
312a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
313ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
3149821f1d3SAlan Somers 	/*
3159821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
31691ff3a0dSAlan Somers 	 * another FUSE_GETATTR or something in here.
3179821f1d3SAlan Somers 	 */
3189821f1d3SAlan Somers 
3199821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
3209821f1d3SAlan Somers }
3219821f1d3SAlan Somers 
3224e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */
3234e83d655SAlan Somers TEST_F(Chown, chown_to_self)
3244e83d655SAlan Somers {
3254e83d655SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3264e83d655SAlan Somers 	const char RELPATH[] = "some_file.txt";
3274e83d655SAlan Somers 	const uint64_t ino = 42;
3284e83d655SAlan Somers 	const mode_t mode = 0755;
3294e83d655SAlan Somers 	uid_t uid;
3304e83d655SAlan Somers 
3314e83d655SAlan Somers 	uid = geteuid();
3324e83d655SAlan Somers 
333a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
3344e83d655SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
3354e83d655SAlan Somers 	/* The OS may optimize chown by omitting the redundant setattr */
3364e83d655SAlan Somers 	EXPECT_CALL(*m_mock, process(
3374e83d655SAlan Somers 		ResultOf([](auto in) {
33829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
3394e83d655SAlan Somers 		}, Eq(true)),
3404e83d655SAlan Somers 		_)
34129edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
3424e83d655SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
34329edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
34429edc611SAlan Somers 		out.body.attr.attr.uid = uid;
3454e83d655SAlan Somers 	})));
3464e83d655SAlan Somers 
3474e83d655SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
3484e83d655SAlan Somers }
3494e83d655SAlan Somers 
350a2bdd737SAlan Somers /*
351a2bdd737SAlan Somers  * A successful chown by a non-privileged non-owner should clear a file's SUID
352a2bdd737SAlan Somers  * bit
353a2bdd737SAlan Somers  */
354a2bdd737SAlan Somers TEST_F(Chown, clear_suid)
355a2bdd737SAlan Somers {
356a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
357a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
358a2bdd737SAlan Somers 	uint64_t ino = 42;
359a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
360a2bdd737SAlan Somers 	const mode_t newmode = 0755;
361a2bdd737SAlan Somers 	uid_t uid = geteuid();
362a2bdd737SAlan Somers 	uint32_t valid = FATTR_UID | FATTR_MODE;
363a2bdd737SAlan Somers 
364a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
365a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
366a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
367a2bdd737SAlan Somers 		ResultOf([=](auto in) {
36829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
36929edc611SAlan Somers 				in.header.nodeid == ino &&
37029edc611SAlan Somers 				in.body.setattr.valid == valid &&
37129edc611SAlan Somers 				in.body.setattr.mode == newmode);
372a2bdd737SAlan Somers 		}, Eq(true)),
373a2bdd737SAlan Somers 		_)
37429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
375a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
37629edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
37729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
37829edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
379a2bdd737SAlan Somers 	})));
380a2bdd737SAlan Somers 
381a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
382a2bdd737SAlan Somers }
383a2bdd737SAlan Somers 
384a2bdd737SAlan Somers 
385474ba6faSAlan Somers /* Only root may change a file's owner */
386474ba6faSAlan Somers TEST_F(Chown, eperm)
387474ba6faSAlan Somers {
388474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
389474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
390474ba6faSAlan Somers 	const uint64_t ino = 42;
391474ba6faSAlan Somers 	const mode_t mode = 0755;
392474ba6faSAlan Somers 
393a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
394474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
395474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
396474ba6faSAlan Somers 		ResultOf([](auto in) {
39729edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
398474ba6faSAlan Somers 		}, Eq(true)),
399474ba6faSAlan Somers 		_)
400474ba6faSAlan Somers 	).Times(0);
401474ba6faSAlan Somers 
402474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
403474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
404474ba6faSAlan Somers }
405474ba6faSAlan Somers 
406a2bdd737SAlan Somers /*
407a2bdd737SAlan Somers  * A successful chgrp by a non-privileged non-owner should clear a file's SUID
408a2bdd737SAlan Somers  * bit
409a2bdd737SAlan Somers  */
410a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid)
411a2bdd737SAlan Somers {
412a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
413a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
414a2bdd737SAlan Somers 	uint64_t ino = 42;
415a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
416a2bdd737SAlan Somers 	const mode_t newmode = 0755;
417a2bdd737SAlan Somers 	uid_t uid = geteuid();
418a2bdd737SAlan Somers 	gid_t gid = getegid();
419a2bdd737SAlan Somers 	uint32_t valid = FATTR_GID | FATTR_MODE;
420a2bdd737SAlan Somers 
421a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
422a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
423a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
424a2bdd737SAlan Somers 		ResultOf([=](auto in) {
42529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
42629edc611SAlan Somers 				in.header.nodeid == ino &&
42729edc611SAlan Somers 				in.body.setattr.valid == valid &&
42829edc611SAlan Somers 				in.body.setattr.mode == newmode);
429a2bdd737SAlan Somers 		}, Eq(true)),
430a2bdd737SAlan Somers 		_)
43129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
432a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
43329edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
43429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
43529edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
436a2bdd737SAlan Somers 	})));
437a2bdd737SAlan Somers 
438a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
439a2bdd737SAlan Somers }
440a2bdd737SAlan Somers 
441474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */
442474ba6faSAlan Somers TEST_F(Chgrp, eperm)
443474ba6faSAlan Somers {
444474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
445474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
446474ba6faSAlan Somers 	const uint64_t ino = 42;
447474ba6faSAlan Somers 	const mode_t mode = 0755;
448474ba6faSAlan Somers 	uid_t uid;
449474ba6faSAlan Somers 	gid_t gid, newgid;
450474ba6faSAlan Somers 
451474ba6faSAlan Somers 	uid = geteuid();
452474ba6faSAlan Somers 	gid = getegid();
4538cfb4431SAlan Somers 	newgid = excluded_group();
454474ba6faSAlan Somers 
455a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
456474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
457474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
458474ba6faSAlan Somers 		ResultOf([](auto in) {
45929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
460474ba6faSAlan Somers 		}, Eq(true)),
461474ba6faSAlan Somers 		_)
462474ba6faSAlan Somers 	).Times(0);
463474ba6faSAlan Somers 
464474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
465474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
466474ba6faSAlan Somers }
467474ba6faSAlan Somers 
468474ba6faSAlan Somers TEST_F(Chgrp, ok)
469474ba6faSAlan Somers {
470474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
471474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
472474ba6faSAlan Somers 	const uint64_t ino = 42;
473474ba6faSAlan Somers 	const mode_t mode = 0755;
474474ba6faSAlan Somers 	uid_t uid;
475474ba6faSAlan Somers 	gid_t gid, newgid;
476474ba6faSAlan Somers 
477474ba6faSAlan Somers 	uid = geteuid();
478474ba6faSAlan Somers 	gid = 0;
479474ba6faSAlan Somers 	newgid = getegid();
480474ba6faSAlan Somers 
481a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
482474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
4834e83d655SAlan Somers 	/* The OS may optimize chgrp by omitting the redundant setattr */
484474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
485474ba6faSAlan Somers 		ResultOf([](auto in) {
48629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
48729edc611SAlan Somers 				in.header.nodeid == ino);
488474ba6faSAlan Somers 		}, Eq(true)),
489474ba6faSAlan Somers 		_)
49029edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
491474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
49229edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
49329edc611SAlan Somers 		out.body.attr.attr.uid = uid;
49429edc611SAlan Somers 		out.body.attr.attr.gid = newgid;
495474ba6faSAlan Somers 	})));
496474ba6faSAlan Somers 
497474ba6faSAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
498474ba6faSAlan Somers }
499474ba6faSAlan Somers 
500*92bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
501*92bbfe1fSAlan Somers TEST_F(CopyFileRange, clear_guid)
502*92bbfe1fSAlan Somers {
503*92bbfe1fSAlan Somers 	const char FULLPATH_IN[] = "mountpoint/in.txt";
504*92bbfe1fSAlan Somers 	const char RELPATH_IN[] = "in.txt";
505*92bbfe1fSAlan Somers 	const char FULLPATH_OUT[] = "mountpoint/out.txt";
506*92bbfe1fSAlan Somers 	const char RELPATH_OUT[] = "out.txt";
507*92bbfe1fSAlan Somers 	struct stat sb;
508*92bbfe1fSAlan Somers 	uint64_t ino_in = 42;
509*92bbfe1fSAlan Somers 	uint64_t ino_out = 43;
510*92bbfe1fSAlan Somers 	mode_t oldmode = 02777;
511*92bbfe1fSAlan Somers 	mode_t newmode = 0777;
512*92bbfe1fSAlan Somers 	off_t fsize = 16;
513*92bbfe1fSAlan Somers 	off_t off_in = 0;
514*92bbfe1fSAlan Somers 	off_t off_out = 8;
515*92bbfe1fSAlan Somers 	off_t len = 8;
516*92bbfe1fSAlan Somers 	int fd_in, fd_out;
517*92bbfe1fSAlan Somers 
518*92bbfe1fSAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
519*92bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
520*92bbfe1fSAlan Somers 	    UINT64_MAX, 0, 0);
521*92bbfe1fSAlan Somers 	expect_open(ino_in, 0, 1);
522*92bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
523*92bbfe1fSAlan Somers 	    1, UINT64_MAX, 0, 0);
524*92bbfe1fSAlan Somers 	expect_open(ino_out, 0, 1);
525*92bbfe1fSAlan Somers 	expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
526*92bbfe1fSAlan Somers 	expect_chmod(ino_out, newmode, fsize);
527*92bbfe1fSAlan Somers 
528*92bbfe1fSAlan Somers 	fd_in = open(FULLPATH_IN, O_RDONLY);
529*92bbfe1fSAlan Somers 	ASSERT_LE(0, fd_in) << strerror(errno);
530*92bbfe1fSAlan Somers 	fd_out = open(FULLPATH_OUT, O_WRONLY);
531*92bbfe1fSAlan Somers 	ASSERT_LE(0, fd_out) << strerror(errno);
532*92bbfe1fSAlan Somers 	ASSERT_EQ(len,
533*92bbfe1fSAlan Somers 	    copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
534*92bbfe1fSAlan Somers 	    << strerror(errno);
535*92bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
536*92bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
537*92bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
538*92bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
539*92bbfe1fSAlan Somers 
540*92bbfe1fSAlan Somers 	leak(fd_in);
541*92bbfe1fSAlan Somers 	leak(fd_out);
542*92bbfe1fSAlan Somers }
543*92bbfe1fSAlan Somers 
544*92bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
545*92bbfe1fSAlan Somers TEST_F(CopyFileRange, clear_suid)
546*92bbfe1fSAlan Somers {
547*92bbfe1fSAlan Somers 	const char FULLPATH_IN[] = "mountpoint/in.txt";
548*92bbfe1fSAlan Somers 	const char RELPATH_IN[] = "in.txt";
549*92bbfe1fSAlan Somers 	const char FULLPATH_OUT[] = "mountpoint/out.txt";
550*92bbfe1fSAlan Somers 	const char RELPATH_OUT[] = "out.txt";
551*92bbfe1fSAlan Somers 	struct stat sb;
552*92bbfe1fSAlan Somers 	uint64_t ino_in = 42;
553*92bbfe1fSAlan Somers 	uint64_t ino_out = 43;
554*92bbfe1fSAlan Somers 	mode_t oldmode = 04777;
555*92bbfe1fSAlan Somers 	mode_t newmode = 0777;
556*92bbfe1fSAlan Somers 	off_t fsize = 16;
557*92bbfe1fSAlan Somers 	off_t off_in = 0;
558*92bbfe1fSAlan Somers 	off_t off_out = 8;
559*92bbfe1fSAlan Somers 	off_t len = 8;
560*92bbfe1fSAlan Somers 	int fd_in, fd_out;
561*92bbfe1fSAlan Somers 
562*92bbfe1fSAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
563*92bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
564*92bbfe1fSAlan Somers 	    UINT64_MAX, 0, 0);
565*92bbfe1fSAlan Somers 	expect_open(ino_in, 0, 1);
566*92bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
567*92bbfe1fSAlan Somers 	    1, UINT64_MAX, 0, 0);
568*92bbfe1fSAlan Somers 	expect_open(ino_out, 0, 1);
569*92bbfe1fSAlan Somers 	expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
570*92bbfe1fSAlan Somers 	expect_chmod(ino_out, newmode, fsize);
571*92bbfe1fSAlan Somers 
572*92bbfe1fSAlan Somers 	fd_in = open(FULLPATH_IN, O_RDONLY);
573*92bbfe1fSAlan Somers 	ASSERT_LE(0, fd_in) << strerror(errno);
574*92bbfe1fSAlan Somers 	fd_out = open(FULLPATH_OUT, O_WRONLY);
575*92bbfe1fSAlan Somers 	ASSERT_LE(0, fd_out) << strerror(errno);
576*92bbfe1fSAlan Somers 	ASSERT_EQ(len,
577*92bbfe1fSAlan Somers 	    copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
578*92bbfe1fSAlan Somers 	    << strerror(errno);
579*92bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
580*92bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
581*92bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
582*92bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
583*92bbfe1fSAlan Somers 
584*92bbfe1fSAlan Somers 	leak(fd_in);
585*92bbfe1fSAlan Somers 	leak(fd_out);
586*92bbfe1fSAlan Somers }
587*92bbfe1fSAlan Somers 
588ff4fbdf5SAlan Somers TEST_F(Create, ok)
589ff4fbdf5SAlan Somers {
590ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
591ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
592ff4fbdf5SAlan Somers 	uint64_t ino = 42;
593ff4fbdf5SAlan Somers 	int fd;
594ff4fbdf5SAlan Somers 
595a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
596a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
597a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
598ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
599ff4fbdf5SAlan Somers 
600ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
601d2621689SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
6027fc0921dSAlan Somers 	leak(fd);
603ff4fbdf5SAlan Somers }
604ff4fbdf5SAlan Somers 
605ff4fbdf5SAlan Somers TEST_F(Create, eacces)
606ff4fbdf5SAlan Somers {
607ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
608ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
609ff4fbdf5SAlan Somers 
610a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
611a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
612a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
613ff4fbdf5SAlan Somers 
6148e765737SAlan Somers 	ASSERT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
615ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
616ff4fbdf5SAlan Somers }
617ff4fbdf5SAlan Somers 
618ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
619ff4fbdf5SAlan Somers {
620ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
621ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
622ff4fbdf5SAlan Somers 	uint64_t ino = 42;
623ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
624ff4fbdf5SAlan Somers 
625a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
626ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
627ff4fbdf5SAlan Somers 
628ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
629ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
630ff4fbdf5SAlan Somers }
631ff4fbdf5SAlan Somers 
632ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
633ff4fbdf5SAlan Somers {
634ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
635ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
636ff4fbdf5SAlan Somers 	uint64_t ino = 42;
637ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
638ff4fbdf5SAlan Somers 
639a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
640ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
641ff4fbdf5SAlan Somers 	expect_removexattr();
642ff4fbdf5SAlan Somers 
643ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
644ff4fbdf5SAlan Somers 		<< strerror(errno);
645ff4fbdf5SAlan Somers }
646ff4fbdf5SAlan Somers 
647ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
648ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
649ff4fbdf5SAlan Somers {
650ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
651ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
652ff4fbdf5SAlan Somers 	uint64_t ino = 42;
653ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
654ff4fbdf5SAlan Somers 
655a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
656ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
657ff4fbdf5SAlan Somers 
658ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
659ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
660ff4fbdf5SAlan Somers }
661ff4fbdf5SAlan Somers 
662d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */
663d943c93eSAlan Somers TEST_F(Utimensat, utime_now)
664d943c93eSAlan Somers {
665d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
666d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
667d943c93eSAlan Somers 	const uint64_t ino = 42;
668d943c93eSAlan Somers 	/* Write permissions for everybody */
669d943c93eSAlan Somers 	const mode_t mode = 0666;
670d943c93eSAlan Somers 	uid_t owner = 0;
671d943c93eSAlan Somers 	const timespec times[2] = {
672d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
673d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
674d943c93eSAlan Somers 	};
675d943c93eSAlan Somers 
676a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
677d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
678d943c93eSAlan Somers 	EXPECT_CALL(*m_mock, process(
679d943c93eSAlan Somers 		ResultOf([](auto in) {
68029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
68129edc611SAlan Somers 				in.header.nodeid == ino &&
68229edc611SAlan Somers 				in.body.setattr.valid & FATTR_ATIME &&
68329edc611SAlan Somers 				in.body.setattr.valid & FATTR_MTIME);
684d943c93eSAlan Somers 		}, Eq(true)),
685d943c93eSAlan Somers 		_)
68629edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
687d943c93eSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
68829edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
689d943c93eSAlan Somers 	})));
690d943c93eSAlan Somers 
691d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
692d943c93eSAlan Somers 		<< strerror(errno);
693d943c93eSAlan Somers }
694d943c93eSAlan Somers 
695d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */
696d943c93eSAlan Somers TEST_F(Utimensat, utime_omit)
697d943c93eSAlan Somers {
698d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
699d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
700d943c93eSAlan Somers 	const uint64_t ino = 42;
701d943c93eSAlan Somers 	/* Write permissions for no one */
702d943c93eSAlan Somers 	const mode_t mode = 0444;
703d943c93eSAlan Somers 	uid_t owner = 0;
704d943c93eSAlan Somers 	const timespec times[2] = {
705d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
706d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
707d943c93eSAlan Somers 	};
708d943c93eSAlan Somers 
709a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
710d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
711d943c93eSAlan Somers 
712d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
713d943c93eSAlan Somers 		<< strerror(errno);
714d943c93eSAlan Somers }
715d943c93eSAlan Somers 
716ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
717ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
718ff4fbdf5SAlan Somers {
719ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
720ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
721ff4fbdf5SAlan Somers 	uint64_t ino = 42;
722ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
723ff4fbdf5SAlan Somers 
724a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
725ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
726ff4fbdf5SAlan Somers 	expect_removexattr();
727ff4fbdf5SAlan Somers 
728ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
729ff4fbdf5SAlan Somers 		<< strerror(errno);
730ff4fbdf5SAlan Somers }
731ff4fbdf5SAlan Somers 
732ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
733ff4fbdf5SAlan Somers {
734ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
735ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
736ff4fbdf5SAlan Somers 	uint64_t ino = 42;
737ff4fbdf5SAlan Somers 	char data[80];
738ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
739ff4fbdf5SAlan Somers 
740a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
741ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
742ff4fbdf5SAlan Somers 
743ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
744ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
745ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
746ff4fbdf5SAlan Somers }
747ff4fbdf5SAlan Somers 
748ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
749ff4fbdf5SAlan Somers {
750ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
751ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
752ff4fbdf5SAlan Somers 	uint64_t ino = 42;
753ff4fbdf5SAlan Somers 	char data[80];
754ff4fbdf5SAlan Somers 	const char value[] = "whatever";
755ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
756ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
757ff4fbdf5SAlan Somers 	ssize_t r;
758ff4fbdf5SAlan Somers 
759a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
760ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
761ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
762ff4fbdf5SAlan Somers 	expect_getxattr(
76329edc611SAlan Somers 		ReturnImmediate([&](auto in __unused, auto& out) {
76429edc611SAlan Somers 			memcpy((void*)out.body.bytes, value, value_len);
76529edc611SAlan Somers 			out.header.len = sizeof(out.header) + value_len;
766ff4fbdf5SAlan Somers 		})
767ff4fbdf5SAlan Somers 	);
768ff4fbdf5SAlan Somers 
769ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
770ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
771ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
772ff4fbdf5SAlan Somers }
773ff4fbdf5SAlan Somers 
774ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
775ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
776ff4fbdf5SAlan Somers {
777ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
778ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
779ff4fbdf5SAlan Somers 	uint64_t ino = 42;
780ff4fbdf5SAlan Somers 	char data[80];
781ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
782ff4fbdf5SAlan Somers 
783a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
784ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
785ff4fbdf5SAlan Somers 
786ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
787ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
788ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
789ff4fbdf5SAlan Somers }
790ff4fbdf5SAlan Somers 
791ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
792ff4fbdf5SAlan Somers {
793ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
794ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
795ff4fbdf5SAlan Somers 	uint64_t ino = 42;
796ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
797ff4fbdf5SAlan Somers 
798a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
799ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
800ff4fbdf5SAlan Somers 
801ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
802ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
803ff4fbdf5SAlan Somers }
804ff4fbdf5SAlan Somers 
805ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
806ff4fbdf5SAlan Somers {
807ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
808ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
809ff4fbdf5SAlan Somers 	uint64_t ino = 42;
810ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
811ff4fbdf5SAlan Somers 
812a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
813ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
814ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
815ff4fbdf5SAlan Somers 	expect_listxattr();
816ff4fbdf5SAlan Somers 
817ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
818ff4fbdf5SAlan Somers 		<< strerror(errno);
819ff4fbdf5SAlan Somers }
820ff4fbdf5SAlan Somers 
821ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
822ff4fbdf5SAlan Somers TEST_F(Listextattr, system)
823ff4fbdf5SAlan Somers {
824ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
825ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
826ff4fbdf5SAlan Somers 	uint64_t ino = 42;
827ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
828ff4fbdf5SAlan Somers 
829a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
830ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
831ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
832ff4fbdf5SAlan Somers 
833ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
834ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
835ff4fbdf5SAlan Somers }
836ff4fbdf5SAlan Somers 
837ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
838ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
839ff4fbdf5SAlan Somers {
840ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
841ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
842ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
843ff4fbdf5SAlan Somers 
844a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
845ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
846ff4fbdf5SAlan Somers 
847ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
848ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
849ff4fbdf5SAlan Somers }
850ff4fbdf5SAlan Somers 
851ff4fbdf5SAlan Somers TEST_F(Open, eacces)
852ff4fbdf5SAlan Somers {
853ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
854ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
855ff4fbdf5SAlan Somers 	uint64_t ino = 42;
856ff4fbdf5SAlan Somers 
857a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
858ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
859ff4fbdf5SAlan Somers 
8604ca1c0b7SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_RDWR));
861ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
862ff4fbdf5SAlan Somers }
863ff4fbdf5SAlan Somers 
8649821f1d3SAlan Somers TEST_F(Open, ok)
8659821f1d3SAlan Somers {
8669821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
8679821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
8689821f1d3SAlan Somers 	uint64_t ino = 42;
8699821f1d3SAlan Somers 	int fd;
8709821f1d3SAlan Somers 
871a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
872ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
8739821f1d3SAlan Somers 	expect_open(ino, 0, 1);
8749821f1d3SAlan Somers 
8759821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
876d2621689SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
8777fc0921dSAlan Somers 	leak(fd);
8789821f1d3SAlan Somers }
8799821f1d3SAlan Somers 
880ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
881ff4fbdf5SAlan Somers {
882ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
883ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
884ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
885ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
886ff4fbdf5SAlan Somers 	uint64_t ino = 42;
887ff4fbdf5SAlan Somers 
888a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
889ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
890a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
891ff4fbdf5SAlan Somers 		.Times(AnyNumber())
892ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
893ff4fbdf5SAlan Somers 
894ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
895ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
896ff4fbdf5SAlan Somers }
897ff4fbdf5SAlan Somers 
898ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
899ff4fbdf5SAlan Somers {
900ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
901ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
902ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
903ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
904ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
905ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
906ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
907ff4fbdf5SAlan Somers 
908a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
909ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
910ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
911ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
912ff4fbdf5SAlan Somers 
913ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
914ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
915ff4fbdf5SAlan Somers }
916ff4fbdf5SAlan Somers 
917ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
918ff4fbdf5SAlan Somers {
919ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
920ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
921ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
922ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
923ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
924ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
925ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
926ff4fbdf5SAlan Somers 
927a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
928ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
929ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
930ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
931ff4fbdf5SAlan Somers 
932ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
933ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
934ff4fbdf5SAlan Somers }
935ff4fbdf5SAlan Somers 
9366124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
937ff4fbdf5SAlan Somers {
938ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
939ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
940ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
941ff4fbdf5SAlan Somers 	uint64_t ino = 42;
942ff4fbdf5SAlan Somers 
943a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
944ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
945ff4fbdf5SAlan Somers 
946ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
947ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
948ff4fbdf5SAlan Somers }
949ff4fbdf5SAlan Somers 
9508e45ec4eSAlan Somers /*
9518e45ec4eSAlan Somers  * A user cannot move out a subdirectory that he does not own, because that
9528e45ec4eSAlan Somers  * would require changing the subdirectory's ".." dirent
9538e45ec4eSAlan Somers  */
9548e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory)
9558e45ec4eSAlan Somers {
9568e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
9578e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
9588e45ec4eSAlan Somers 	const char RELDSTDIR[] = "d";
9598e45ec4eSAlan Somers 	const char RELDST[] = "dst";
9608e45ec4eSAlan Somers 	const char RELSRC[] = "src";
9618e45ec4eSAlan Somers 	uint64_t ino = 42;
9628e45ec4eSAlan Somers 	uint64_t dstdir_ino = 43;
9638e45ec4eSAlan Somers 
964a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
9658e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
9668e45ec4eSAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
9678e45ec4eSAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
9688e45ec4eSAlan Somers 
9698e45ec4eSAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
9708e45ec4eSAlan Somers 	ASSERT_EQ(EACCES, errno);
9718e45ec4eSAlan Somers }
9728e45ec4eSAlan Somers 
9738e45ec4eSAlan Somers /*
9748e45ec4eSAlan Somers  * A user _can_ rename a subdirectory to which he lacks write permissions, if
9758e45ec4eSAlan Somers  * it will keep the same parent
9768e45ec4eSAlan Somers  */
9778e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir)
9788e45ec4eSAlan Somers {
9798e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/dst";
9808e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
9818e45ec4eSAlan Somers 	const char RELDST[] = "dst";
9828e45ec4eSAlan Somers 	const char RELSRC[] = "src";
9838e45ec4eSAlan Somers 	uint64_t ino = 42;
9848e45ec4eSAlan Somers 
985a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
9868e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
987a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
988a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
9898e45ec4eSAlan Somers 	expect_rename(0);
9908e45ec4eSAlan Somers 
9918e45ec4eSAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
9928e45ec4eSAlan Somers }
9938e45ec4eSAlan Somers 
9946124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
995ff4fbdf5SAlan Somers {
996ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
997ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
9986124fd71SAlan Somers 	const char RELDST[] = "dst";
999ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1000ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1001ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1002ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1003ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
1004ff4fbdf5SAlan Somers 
1005a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1006ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1007ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
10086124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
100929edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
10106124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
101129edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
101229edc611SAlan Somers 		out.body.entry.nodeid = dst_ino;
101329edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
101429edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
101529edc611SAlan Somers 		out.body.entry.attr.uid = 0;
10166124fd71SAlan Somers 	})));
1017ff4fbdf5SAlan Somers 
1018ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1019ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1020ff4fbdf5SAlan Somers }
1021ff4fbdf5SAlan Somers 
1022ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
1023ff4fbdf5SAlan Somers TEST_F(Rename, ok)
1024ff4fbdf5SAlan Somers {
1025ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1026ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1027ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1028ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1029ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
1030ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
1031ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1032ff4fbdf5SAlan Somers 
1033a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
1034ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1035ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
1036ff4fbdf5SAlan Somers 	expect_rename(0);
1037ff4fbdf5SAlan Somers 
1038ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1039ff4fbdf5SAlan Somers }
1040ff4fbdf5SAlan Somers 
1041ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
1042ff4fbdf5SAlan Somers {
1043ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1044ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1045ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1046ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1047ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1048ff4fbdf5SAlan Somers 
1049a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1050ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1051a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1052a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1053ff4fbdf5SAlan Somers 	expect_rename(0);
1054ff4fbdf5SAlan Somers 
1055ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1056ff4fbdf5SAlan Somers }
1057ff4fbdf5SAlan Somers 
1058ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
1059ff4fbdf5SAlan Somers {
1060ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1061ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1062ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
1063ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
1064ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
1065ff4fbdf5SAlan Somers 
1066a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1067ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1068ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
1069ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
107029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
107129edc611SAlan Somers 				in.header.nodeid == ino &&
107229edc611SAlan Somers 				in.body.setattr.mode == newmode);
1073ff4fbdf5SAlan Somers 		}, Eq(true)),
1074ff4fbdf5SAlan Somers 		_)
107529edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1076ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
107729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
1078ff4fbdf5SAlan Somers 	})));
1079ff4fbdf5SAlan Somers 
1080ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1081ff4fbdf5SAlan Somers }
1082ff4fbdf5SAlan Somers 
1083ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
1084ff4fbdf5SAlan Somers {
1085ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1086ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1087ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
1088ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
1089ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
1090ff4fbdf5SAlan Somers 
1091a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1092ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
1093ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
1094ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
109529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1096ff4fbdf5SAlan Somers 		}, Eq(true)),
1097ff4fbdf5SAlan Somers 		_)
1098ff4fbdf5SAlan Somers 	).Times(0);
1099ff4fbdf5SAlan Somers 
1100ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1101ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
1102ff4fbdf5SAlan Somers }
1103ff4fbdf5SAlan Somers 
11048cfb4431SAlan Somers /*
11053fa12789SAlan Somers  * ftruncate() of a file without writable permissions should succeed as long as
11063fa12789SAlan Somers  * the file descriptor is writable.  This is important when combined with
11073fa12789SAlan Somers  * O_CREAT
11083fa12789SAlan Somers  */
11093fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file)
11103fa12789SAlan Somers {
11113fa12789SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11123fa12789SAlan Somers 	const char RELPATH[] = "some_file.txt";
11133fa12789SAlan Somers 	const uint64_t ino = 42;
11143fa12789SAlan Somers 	const mode_t mode = 0000;
11153fa12789SAlan Somers 	int fd;
11163fa12789SAlan Somers 
1117a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1118a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1119a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
11203fa12789SAlan Somers 	expect_create(RELPATH, ino);
11213fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
11223fa12789SAlan Somers 		ResultOf([](auto in) {
112329edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
112429edc611SAlan Somers 				in.header.nodeid == ino &&
112529edc611SAlan Somers 				(in.body.setattr.valid & FATTR_SIZE));
11263fa12789SAlan Somers 		}, Eq(true)),
11273fa12789SAlan Somers 		_)
112829edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
11293fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
113029edc611SAlan Somers 		out.body.attr.attr.ino = ino;
113129edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
113229edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
11333fa12789SAlan Somers 	})));
11343fa12789SAlan Somers 
11353fa12789SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
11363fa12789SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
11373fa12789SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
11387fc0921dSAlan Somers 	leak(fd);
11393fa12789SAlan Somers }
11403fa12789SAlan Somers 
11413fa12789SAlan Somers /*
11428cfb4431SAlan Somers  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
11438cfb4431SAlan Somers  * to the file's group
11448cfb4431SAlan Somers  */
11458cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member)
11468cfb4431SAlan Somers {
11478cfb4431SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11488cfb4431SAlan Somers 	const char RELPATH[] = "some_file.txt";
11498cfb4431SAlan Somers 	const uint64_t ino = 42;
11508cfb4431SAlan Somers 	const mode_t oldmode = 0755;
11518cfb4431SAlan Somers 	const mode_t newmode = 02755;
11528cfb4431SAlan Somers 	uid_t uid = geteuid();
11538cfb4431SAlan Somers 	gid_t gid = excluded_group();
11548cfb4431SAlan Somers 
1155a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
11568cfb4431SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
11578cfb4431SAlan Somers 	EXPECT_CALL(*m_mock, process(
11588cfb4431SAlan Somers 		ResultOf([](auto in) {
115929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
11608cfb4431SAlan Somers 		}, Eq(true)),
11618cfb4431SAlan Somers 		_)
11628cfb4431SAlan Somers 	).Times(0);
11638cfb4431SAlan Somers 
11648cfb4431SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
11658cfb4431SAlan Somers 	EXPECT_EQ(EPERM, errno);
11668cfb4431SAlan Somers }
11678cfb4431SAlan Somers 
1168e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
1169e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
1170e5ff3a7eSAlan Somers {
1171e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1172e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
1173e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
1174e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
1175e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
1176e5ff3a7eSAlan Somers 
1177a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1178e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1179e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
1180e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
118129edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1182e5ff3a7eSAlan Somers 		}, Eq(true)),
1183e5ff3a7eSAlan Somers 		_)
1184e5ff3a7eSAlan Somers 	).Times(0);
1185e5ff3a7eSAlan Somers 
1186e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1187e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
1188e5ff3a7eSAlan Somers }
1189e5ff3a7eSAlan Somers 
1190ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
1191ff4fbdf5SAlan Somers {
1192ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1193ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1194ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1195ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1196ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1197ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1198ff4fbdf5SAlan Somers 	ssize_t r;
1199ff4fbdf5SAlan Somers 
1200a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1201ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1202ff4fbdf5SAlan Somers 	expect_setxattr(0);
1203ff4fbdf5SAlan Somers 
12045a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
12055a0b9a27SAlan Somers 		value_len);
1206ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1207ff4fbdf5SAlan Somers }
1208ff4fbdf5SAlan Somers 
1209ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
1210ff4fbdf5SAlan Somers {
1211ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1212ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1213ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1214ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1215ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1216ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1217ff4fbdf5SAlan Somers 
1218a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1219ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1220ff4fbdf5SAlan Somers 
12215a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
12225a0b9a27SAlan Somers 		value_len));
1223ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1224ff4fbdf5SAlan Somers }
1225ff4fbdf5SAlan Somers 
1226ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
1227ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
1228ff4fbdf5SAlan Somers {
1229ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1230ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1231ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1232ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1233ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1234ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1235ff4fbdf5SAlan Somers 
1236a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1237ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1238ff4fbdf5SAlan Somers 
12395a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
12405a0b9a27SAlan Somers 		value_len));
1241ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1242ff4fbdf5SAlan Somers }
1243ff4fbdf5SAlan Somers 
1244ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
1245ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
1246ff4fbdf5SAlan Somers {
1247ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1248ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1249ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1250ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1251ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1252ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1253ff4fbdf5SAlan Somers 	ssize_t r;
1254ff4fbdf5SAlan Somers 
1255a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1256ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1257ff4fbdf5SAlan Somers 	expect_setxattr(0);
1258ff4fbdf5SAlan Somers 
12595a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
12605a0b9a27SAlan Somers 		value_len);
1261ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1262ff4fbdf5SAlan Somers }
1263ff4fbdf5SAlan Somers 
1264ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
12659821f1d3SAlan Somers {
12669821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
12679821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
12689821f1d3SAlan Somers 	uint64_t ino = 42;
1269331884f2SAlan Somers 	sem_t sem;
1270331884f2SAlan Somers 
1271331884f2SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
12729821f1d3SAlan Somers 
1273a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1274ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1275a34cdd26SAlan Somers 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
1276331884f2SAlan Somers 	expect_forget(ino, 1, &sem);
12779821f1d3SAlan Somers 
1278ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1279331884f2SAlan Somers 
1280331884f2SAlan Somers 	sem_wait(&sem);
1281331884f2SAlan Somers 	sem_destroy(&sem);
1282ff4fbdf5SAlan Somers }
1283ff4fbdf5SAlan Somers 
12846124fd71SAlan Somers /*
12856124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
12866124fd71SAlan Somers  * in VOP_LOOKUP.
12876124fd71SAlan Somers  *
12886124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
12896124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
12906124fd71SAlan Somers  */
12916124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
12926124fd71SAlan Somers {
12936124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
12946124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
12956124fd71SAlan Somers 	uint64_t ino = 42;
12966124fd71SAlan Somers 
1297a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1298a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
12996124fd71SAlan Somers 	.Times(AnyNumber())
13006124fd71SAlan Somers 	.WillRepeatedly(Invoke(
130129edc611SAlan Somers 		ReturnImmediate([=](auto i __unused, auto& out) {
13026124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
130329edc611SAlan Somers 			out.body.entry.attr.mode = S_IFREG | 0644;
130429edc611SAlan Somers 			out.body.entry.nodeid = ino;
130529edc611SAlan Somers 			out.body.entry.entry_valid = UINT64_MAX;
13066124fd71SAlan Somers 		}))
13076124fd71SAlan Somers 	);
13086124fd71SAlan Somers 
13096124fd71SAlan Somers 	/* Fill name cache */
13106124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
13116124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
13126124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
13136124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
13146124fd71SAlan Somers }
13156124fd71SAlan Somers 
1316ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
1317ff4fbdf5SAlan Somers {
1318ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1319ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1320ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1321ff4fbdf5SAlan Somers 
1322a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1323ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1324ff4fbdf5SAlan Somers 
1325ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1326ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1327ff4fbdf5SAlan Somers }
1328ff4fbdf5SAlan Somers 
13296124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
1330ff4fbdf5SAlan Somers {
1331ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1332ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1333ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1334ff4fbdf5SAlan Somers 
1335a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1336ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1337ff4fbdf5SAlan Somers 
1338ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1339ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
13409821f1d3SAlan Somers }
1341a90e32deSAlan Somers 
1342a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
1343a90e32deSAlan Somers TEST_F(Write, clear_suid)
1344a90e32deSAlan Somers {
1345a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1346a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1347a90e32deSAlan Somers 	struct stat sb;
1348a90e32deSAlan Somers 	uint64_t ino = 42;
1349a90e32deSAlan Somers 	mode_t oldmode = 04777;
1350a90e32deSAlan Somers 	mode_t newmode = 0777;
1351a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1352a90e32deSAlan Somers 	int fd;
1353a90e32deSAlan Somers 
1354a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1355a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1356a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1357bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
135818a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1359a90e32deSAlan Somers 
1360a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1361a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1362a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1363a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1364a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
13657fc0921dSAlan Somers 	leak(fd);
1366a90e32deSAlan Somers }
1367a90e32deSAlan Somers 
1368a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
1369a90e32deSAlan Somers TEST_F(Write, clear_sgid)
1370a90e32deSAlan Somers {
1371a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1372a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1373a90e32deSAlan Somers 	struct stat sb;
1374a90e32deSAlan Somers 	uint64_t ino = 42;
1375a90e32deSAlan Somers 	mode_t oldmode = 02777;
1376a90e32deSAlan Somers 	mode_t newmode = 0777;
1377a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1378a90e32deSAlan Somers 	int fd;
1379a90e32deSAlan Somers 
1380a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1381a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1382a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1383bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
138418a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1385a90e32deSAlan Somers 
1386a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1387a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1388a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1389a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1390a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
13917fc0921dSAlan Somers 	leak(fd);
1392a90e32deSAlan Somers }
139318a2264eSAlan Somers 
139418a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic
139518a2264eSAlan Somers  *
139618a2264eSAlan Somers  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
139718a2264eSAlan Somers  * may panic.  That happens if the FUSE_SETATTR response indicates that the
139818a2264eSAlan Somers  * file's size has changed since the write.
139918a2264eSAlan Somers  */
140018a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid)
140118a2264eSAlan Somers {
140218a2264eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
140318a2264eSAlan Somers 	const char RELPATH[] = "some_file.txt";
140418a2264eSAlan Somers 	uint64_t ino = 42;
140518a2264eSAlan Somers 	mode_t oldmode = 04777;
140618a2264eSAlan Somers 	mode_t newmode = 0777;
140718a2264eSAlan Somers 	char wbuf[1] = {'x'};
140818a2264eSAlan Somers 	int fd;
140918a2264eSAlan Somers 
1410a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
141118a2264eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
141218a2264eSAlan Somers 	expect_open(ino, 0, 1);
1413bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
141418a2264eSAlan Somers 	/* XXX Return a smaller file size than what we just wrote! */
141518a2264eSAlan Somers 	expect_chmod(ino, newmode, 0);
141618a2264eSAlan Somers 
141718a2264eSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
141818a2264eSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
141918a2264eSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
14207fc0921dSAlan Somers 	leak(fd);
142118a2264eSAlan Somers }
1422