xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 331884f291f1de426e2e7a47f315eda85a6ba22f)
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>
43*331884f2SAlan 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 
112ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
113474ba6faSAlan Somers 	uid_t uid = 0, gid_t gid = 0)
1149821f1d3SAlan Somers {
115ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
116ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
11729edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
11829edc611SAlan Somers 				in.header.nodeid == ino);
119ff4fbdf5SAlan Somers 		}, Eq(true)),
120ff4fbdf5SAlan Somers 		_)
121ff4fbdf5SAlan Somers 	).Times(times)
12229edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
123ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
12429edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
12529edc611SAlan Somers 		out.body.attr.attr.mode = mode;
12629edc611SAlan Somers 		out.body.attr.attr.size = 0;
12729edc611SAlan Somers 		out.body.attr.attr.uid = uid;
12829edc611SAlan Somers 		out.body.attr.attr.uid = gid;
12929edc611SAlan Somers 		out.body.attr.attr_valid = attr_valid;
130ff4fbdf5SAlan Somers 	})));
131ff4fbdf5SAlan Somers }
132ff4fbdf5SAlan Somers 
133ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
134474ba6faSAlan Somers 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
135ff4fbdf5SAlan Somers {
136474ba6faSAlan Somers 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
1379821f1d3SAlan Somers }
1389821f1d3SAlan Somers 
1399821f1d3SAlan Somers };
1409821f1d3SAlan Somers 
1419821f1d3SAlan Somers class Access: public DefaultPermissions {};
142474ba6faSAlan Somers class Chown: public DefaultPermissions {};
143474ba6faSAlan Somers class Chgrp: public DefaultPermissions {};
144ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1459821f1d3SAlan Somers class Open: public DefaultPermissions {};
146ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {};
147ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {};
148d943c93eSAlan Somers class Utimensat: public DefaultPermissions {};
149a90e32deSAlan Somers class Write: public DefaultPermissions {};
1509821f1d3SAlan Somers 
151ff4fbdf5SAlan Somers /*
152ff4fbdf5SAlan Somers  * Test permission handling during create, mkdir, mknod, link, symlink, and
153ff4fbdf5SAlan Somers  * rename vops (they all share a common path for permission checks in
154ff4fbdf5SAlan Somers  * VOP_LOOKUP)
155ff4fbdf5SAlan Somers  */
1563fa12789SAlan Somers class Create: public DefaultPermissions {};
157ff4fbdf5SAlan Somers 
158ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions {
159ff4fbdf5SAlan Somers public:
160ff4fbdf5SAlan Somers void expect_removexattr()
161ff4fbdf5SAlan Somers {
162ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
163ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
16429edc611SAlan Somers 			return (in.header.opcode == FUSE_REMOVEXATTR);
165ff4fbdf5SAlan Somers 		}, Eq(true)),
166ff4fbdf5SAlan Somers 		_)
167ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(0)));
168ff4fbdf5SAlan Somers }
169ff4fbdf5SAlan Somers };
170ff4fbdf5SAlan Somers 
171ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions {
172ff4fbdf5SAlan Somers public:
173ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r)
174ff4fbdf5SAlan Somers {
175ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
176ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
17729edc611SAlan Somers 			return (in.header.opcode == FUSE_GETXATTR);
178ff4fbdf5SAlan Somers 		}, Eq(true)),
179ff4fbdf5SAlan Somers 		_)
180ff4fbdf5SAlan Somers 	).WillOnce(Invoke(r));
181ff4fbdf5SAlan Somers }
182ff4fbdf5SAlan Somers };
183ff4fbdf5SAlan Somers 
184ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions {
185ff4fbdf5SAlan Somers public:
186ff4fbdf5SAlan Somers void expect_listxattr()
187ff4fbdf5SAlan Somers {
188ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
189ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
19029edc611SAlan Somers 			return (in.header.opcode == FUSE_LISTXATTR);
191ff4fbdf5SAlan Somers 		}, Eq(true)),
192ff4fbdf5SAlan Somers 		_)
19329edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
19429edc611SAlan Somers 		out.body.listxattr.size = 0;
195ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
196ff4fbdf5SAlan Somers 	})));
197ff4fbdf5SAlan Somers }
198ff4fbdf5SAlan Somers };
199ff4fbdf5SAlan Somers 
200ff4fbdf5SAlan Somers class Rename: public DefaultPermissions {
201ff4fbdf5SAlan Somers public:
202ff4fbdf5SAlan Somers 	/*
203ff4fbdf5SAlan Somers 	 * Expect a rename and respond with the given error.  Don't both to
204ff4fbdf5SAlan Somers 	 * validate arguments; the tests in rename.cc do that.
205ff4fbdf5SAlan Somers 	 */
206ff4fbdf5SAlan Somers 	void expect_rename(int error)
207ff4fbdf5SAlan Somers 	{
208ff4fbdf5SAlan Somers 		EXPECT_CALL(*m_mock, process(
209ff4fbdf5SAlan Somers 			ResultOf([=](auto in) {
21029edc611SAlan Somers 				return (in.header.opcode == FUSE_RENAME);
211ff4fbdf5SAlan Somers 			}, Eq(true)),
212ff4fbdf5SAlan Somers 			_)
213ff4fbdf5SAlan Somers 		).WillOnce(Invoke(ReturnErrno(error)));
214ff4fbdf5SAlan Somers 	}
215ff4fbdf5SAlan Somers };
216ff4fbdf5SAlan Somers 
217ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions {
218ff4fbdf5SAlan Somers public:
219ff4fbdf5SAlan Somers void expect_setxattr(int error)
220ff4fbdf5SAlan Somers {
221ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
222ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
22329edc611SAlan Somers 			return (in.header.opcode == FUSE_SETXATTR);
224ff4fbdf5SAlan Somers 		}, Eq(true)),
225ff4fbdf5SAlan Somers 		_)
226ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
227ff4fbdf5SAlan Somers }
228ff4fbdf5SAlan Somers };
229ff4fbdf5SAlan Somers 
2308cfb4431SAlan Somers /* Return a group to which this user does not belong */
2318cfb4431SAlan Somers static gid_t excluded_group()
2328cfb4431SAlan Somers {
2338cfb4431SAlan Somers 	int i, ngroups = 64;
2348cfb4431SAlan Somers 	gid_t newgid, groups[ngroups];
2358cfb4431SAlan Somers 
2368cfb4431SAlan Somers 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
2375a0b9a27SAlan Somers 	for (newgid = 0; ; newgid++) {
2388cfb4431SAlan Somers 		bool belongs = false;
2398cfb4431SAlan Somers 
2408cfb4431SAlan Somers 		for (i = 0; i < ngroups; i++) {
2418cfb4431SAlan Somers 			if (groups[i] == newgid)
2428cfb4431SAlan Somers 				belongs = true;
2438cfb4431SAlan Somers 		}
2448cfb4431SAlan Somers 		if (!belongs)
2458cfb4431SAlan Somers 			break;
2468cfb4431SAlan Somers 	}
2478cfb4431SAlan Somers 	/* newgid is now a group to which the current user does not belong */
2488cfb4431SAlan Somers 	return newgid;
2498cfb4431SAlan Somers }
2508cfb4431SAlan Somers 
251ff4fbdf5SAlan Somers TEST_F(Access, eacces)
2529821f1d3SAlan Somers {
2539821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2549821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2559821f1d3SAlan Somers 	uint64_t ino = 42;
2569821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
2579821f1d3SAlan Somers 
258a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
259ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
260ff4fbdf5SAlan Somers 
261ff4fbdf5SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
262ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
263ff4fbdf5SAlan Somers }
264ff4fbdf5SAlan Somers 
265ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs)
266ff4fbdf5SAlan Somers {
267ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
268ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
269ff4fbdf5SAlan Somers 	uint64_t ino = 42;
270ff4fbdf5SAlan Somers 	mode_t	access_mode = X_OK;
271ff4fbdf5SAlan Somers 
272a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1);
273ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
274ff4fbdf5SAlan Somers 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
2759821f1d3SAlan Somers 	/*
2769821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
2779821f1d3SAlan Somers 	 * another FUSE_GETATTR or something in here.  But there should not be
2789821f1d3SAlan Somers 	 * a FUSE_ACCESS
2799821f1d3SAlan Somers 	 */
2809821f1d3SAlan Somers 
2819821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
2829821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
2839821f1d3SAlan Somers }
2849821f1d3SAlan Somers 
285ff4fbdf5SAlan Somers TEST_F(Access, ok)
2869821f1d3SAlan Somers {
2879821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2889821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2899821f1d3SAlan Somers 	uint64_t ino = 42;
2909821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
2919821f1d3SAlan Somers 
292a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
293ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
2949821f1d3SAlan Somers 	/*
2959821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
29691ff3a0dSAlan Somers 	 * another FUSE_GETATTR or something in here.
2979821f1d3SAlan Somers 	 */
2989821f1d3SAlan Somers 
2999821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
3009821f1d3SAlan Somers }
3019821f1d3SAlan Somers 
3024e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */
3034e83d655SAlan Somers TEST_F(Chown, chown_to_self)
3044e83d655SAlan Somers {
3054e83d655SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3064e83d655SAlan Somers 	const char RELPATH[] = "some_file.txt";
3074e83d655SAlan Somers 	const uint64_t ino = 42;
3084e83d655SAlan Somers 	const mode_t mode = 0755;
3094e83d655SAlan Somers 	uid_t uid;
3104e83d655SAlan Somers 
3114e83d655SAlan Somers 	uid = geteuid();
3124e83d655SAlan Somers 
313a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
3144e83d655SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
3154e83d655SAlan Somers 	/* The OS may optimize chown by omitting the redundant setattr */
3164e83d655SAlan Somers 	EXPECT_CALL(*m_mock, process(
3174e83d655SAlan Somers 		ResultOf([](auto in) {
31829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
3194e83d655SAlan Somers 		}, Eq(true)),
3204e83d655SAlan Somers 		_)
32129edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
3224e83d655SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
32329edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
32429edc611SAlan Somers 		out.body.attr.attr.uid = uid;
3254e83d655SAlan Somers 	})));
3264e83d655SAlan Somers 
3274e83d655SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
3284e83d655SAlan Somers }
3294e83d655SAlan Somers 
330a2bdd737SAlan Somers /*
331a2bdd737SAlan Somers  * A successful chown by a non-privileged non-owner should clear a file's SUID
332a2bdd737SAlan Somers  * bit
333a2bdd737SAlan Somers  */
334a2bdd737SAlan Somers TEST_F(Chown, clear_suid)
335a2bdd737SAlan Somers {
336a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
337a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
338a2bdd737SAlan Somers 	uint64_t ino = 42;
339a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
340a2bdd737SAlan Somers 	const mode_t newmode = 0755;
341a2bdd737SAlan Somers 	uid_t uid = geteuid();
342a2bdd737SAlan Somers 	uint32_t valid = FATTR_UID | FATTR_MODE;
343a2bdd737SAlan Somers 
344a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
345a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
346a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
347a2bdd737SAlan Somers 		ResultOf([=](auto in) {
34829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
34929edc611SAlan Somers 				in.header.nodeid == ino &&
35029edc611SAlan Somers 				in.body.setattr.valid == valid &&
35129edc611SAlan Somers 				in.body.setattr.mode == newmode);
352a2bdd737SAlan Somers 		}, Eq(true)),
353a2bdd737SAlan Somers 		_)
35429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
355a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
35629edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
35729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
35829edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
359a2bdd737SAlan Somers 	})));
360a2bdd737SAlan Somers 
361a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
362a2bdd737SAlan Somers }
363a2bdd737SAlan Somers 
364a2bdd737SAlan Somers 
365474ba6faSAlan Somers /* Only root may change a file's owner */
366474ba6faSAlan Somers TEST_F(Chown, eperm)
367474ba6faSAlan Somers {
368474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
369474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
370474ba6faSAlan Somers 	const uint64_t ino = 42;
371474ba6faSAlan Somers 	const mode_t mode = 0755;
372474ba6faSAlan Somers 
373a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
374474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
375474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
376474ba6faSAlan Somers 		ResultOf([](auto in) {
37729edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
378474ba6faSAlan Somers 		}, Eq(true)),
379474ba6faSAlan Somers 		_)
380474ba6faSAlan Somers 	).Times(0);
381474ba6faSAlan Somers 
382474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
383474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
384474ba6faSAlan Somers }
385474ba6faSAlan Somers 
386a2bdd737SAlan Somers /*
387a2bdd737SAlan Somers  * A successful chgrp by a non-privileged non-owner should clear a file's SUID
388a2bdd737SAlan Somers  * bit
389a2bdd737SAlan Somers  */
390a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid)
391a2bdd737SAlan Somers {
392a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
393a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
394a2bdd737SAlan Somers 	uint64_t ino = 42;
395a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
396a2bdd737SAlan Somers 	const mode_t newmode = 0755;
397a2bdd737SAlan Somers 	uid_t uid = geteuid();
398a2bdd737SAlan Somers 	gid_t gid = getegid();
399a2bdd737SAlan Somers 	uint32_t valid = FATTR_GID | FATTR_MODE;
400a2bdd737SAlan Somers 
401a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
402a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
403a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
404a2bdd737SAlan Somers 		ResultOf([=](auto in) {
40529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
40629edc611SAlan Somers 				in.header.nodeid == ino &&
40729edc611SAlan Somers 				in.body.setattr.valid == valid &&
40829edc611SAlan Somers 				in.body.setattr.mode == newmode);
409a2bdd737SAlan Somers 		}, Eq(true)),
410a2bdd737SAlan Somers 		_)
41129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
412a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
41329edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
41429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
41529edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
416a2bdd737SAlan Somers 	})));
417a2bdd737SAlan Somers 
418a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
419a2bdd737SAlan Somers }
420a2bdd737SAlan Somers 
421474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */
422474ba6faSAlan Somers TEST_F(Chgrp, eperm)
423474ba6faSAlan Somers {
424474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
425474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
426474ba6faSAlan Somers 	const uint64_t ino = 42;
427474ba6faSAlan Somers 	const mode_t mode = 0755;
428474ba6faSAlan Somers 	uid_t uid;
429474ba6faSAlan Somers 	gid_t gid, newgid;
430474ba6faSAlan Somers 
431474ba6faSAlan Somers 	uid = geteuid();
432474ba6faSAlan Somers 	gid = getegid();
4338cfb4431SAlan Somers 	newgid = excluded_group();
434474ba6faSAlan Somers 
435a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
436474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
437474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
438474ba6faSAlan Somers 		ResultOf([](auto in) {
43929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
440474ba6faSAlan Somers 		}, Eq(true)),
441474ba6faSAlan Somers 		_)
442474ba6faSAlan Somers 	).Times(0);
443474ba6faSAlan Somers 
444474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
445474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
446474ba6faSAlan Somers }
447474ba6faSAlan Somers 
448474ba6faSAlan Somers TEST_F(Chgrp, ok)
449474ba6faSAlan Somers {
450474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
451474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
452474ba6faSAlan Somers 	const uint64_t ino = 42;
453474ba6faSAlan Somers 	const mode_t mode = 0755;
454474ba6faSAlan Somers 	uid_t uid;
455474ba6faSAlan Somers 	gid_t gid, newgid;
456474ba6faSAlan Somers 
457474ba6faSAlan Somers 	uid = geteuid();
458474ba6faSAlan Somers 	gid = 0;
459474ba6faSAlan Somers 	newgid = getegid();
460474ba6faSAlan Somers 
461a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
462474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
4634e83d655SAlan Somers 	/* The OS may optimize chgrp by omitting the redundant setattr */
464474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
465474ba6faSAlan Somers 		ResultOf([](auto in) {
46629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
46729edc611SAlan Somers 				in.header.nodeid == ino);
468474ba6faSAlan Somers 		}, Eq(true)),
469474ba6faSAlan Somers 		_)
47029edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
471474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
47229edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
47329edc611SAlan Somers 		out.body.attr.attr.uid = uid;
47429edc611SAlan Somers 		out.body.attr.attr.gid = newgid;
475474ba6faSAlan Somers 	})));
476474ba6faSAlan Somers 
477474ba6faSAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
478474ba6faSAlan Somers }
479474ba6faSAlan Somers 
480ff4fbdf5SAlan Somers TEST_F(Create, ok)
481ff4fbdf5SAlan Somers {
482ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
483ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
484ff4fbdf5SAlan Somers 	uint64_t ino = 42;
485ff4fbdf5SAlan Somers 	int fd;
486ff4fbdf5SAlan Somers 
487a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
488a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
489a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
490ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
491ff4fbdf5SAlan Somers 
492ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
493ff4fbdf5SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
4947fc0921dSAlan Somers 	leak(fd);
495ff4fbdf5SAlan Somers }
496ff4fbdf5SAlan Somers 
497ff4fbdf5SAlan Somers TEST_F(Create, eacces)
498ff4fbdf5SAlan Somers {
499ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
500ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
501ff4fbdf5SAlan Somers 
502a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
503a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
504a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
505ff4fbdf5SAlan Somers 
506ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
507ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
508ff4fbdf5SAlan Somers }
509ff4fbdf5SAlan Somers 
510ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
511ff4fbdf5SAlan Somers {
512ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
513ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
514ff4fbdf5SAlan Somers 	uint64_t ino = 42;
515ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
516ff4fbdf5SAlan Somers 
517a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
518ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
519ff4fbdf5SAlan Somers 
520ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
521ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
522ff4fbdf5SAlan Somers }
523ff4fbdf5SAlan Somers 
524ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
525ff4fbdf5SAlan Somers {
526ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
527ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
528ff4fbdf5SAlan Somers 	uint64_t ino = 42;
529ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
530ff4fbdf5SAlan Somers 
531a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
532ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
533ff4fbdf5SAlan Somers 	expect_removexattr();
534ff4fbdf5SAlan Somers 
535ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
536ff4fbdf5SAlan Somers 		<< strerror(errno);
537ff4fbdf5SAlan Somers }
538ff4fbdf5SAlan Somers 
539ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
540ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
541ff4fbdf5SAlan Somers {
542ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
543ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
544ff4fbdf5SAlan Somers 	uint64_t ino = 42;
545ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
546ff4fbdf5SAlan Somers 
547a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
548ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
549ff4fbdf5SAlan Somers 
550ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
551ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
552ff4fbdf5SAlan Somers }
553ff4fbdf5SAlan Somers 
554d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */
555d943c93eSAlan Somers TEST_F(Utimensat, utime_now)
556d943c93eSAlan Somers {
557d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
558d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
559d943c93eSAlan Somers 	const uint64_t ino = 42;
560d943c93eSAlan Somers 	/* Write permissions for everybody */
561d943c93eSAlan Somers 	const mode_t mode = 0666;
562d943c93eSAlan Somers 	uid_t owner = 0;
563d943c93eSAlan Somers 	const timespec times[2] = {
564d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
565d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
566d943c93eSAlan Somers 	};
567d943c93eSAlan Somers 
568a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
569d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
570d943c93eSAlan Somers 	EXPECT_CALL(*m_mock, process(
571d943c93eSAlan Somers 		ResultOf([](auto in) {
57229edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
57329edc611SAlan Somers 				in.header.nodeid == ino &&
57429edc611SAlan Somers 				in.body.setattr.valid & FATTR_ATIME &&
57529edc611SAlan Somers 				in.body.setattr.valid & FATTR_MTIME);
576d943c93eSAlan Somers 		}, Eq(true)),
577d943c93eSAlan Somers 		_)
57829edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
579d943c93eSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
58029edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
581d943c93eSAlan Somers 	})));
582d943c93eSAlan Somers 
583d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
584d943c93eSAlan Somers 		<< strerror(errno);
585d943c93eSAlan Somers }
586d943c93eSAlan Somers 
587d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */
588d943c93eSAlan Somers TEST_F(Utimensat, utime_omit)
589d943c93eSAlan Somers {
590d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
591d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
592d943c93eSAlan Somers 	const uint64_t ino = 42;
593d943c93eSAlan Somers 	/* Write permissions for no one */
594d943c93eSAlan Somers 	const mode_t mode = 0444;
595d943c93eSAlan Somers 	uid_t owner = 0;
596d943c93eSAlan Somers 	const timespec times[2] = {
597d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
598d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
599d943c93eSAlan Somers 	};
600d943c93eSAlan Somers 
601a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
602d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
603d943c93eSAlan Somers 
604d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
605d943c93eSAlan Somers 		<< strerror(errno);
606d943c93eSAlan Somers }
607d943c93eSAlan Somers 
608ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
609ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
610ff4fbdf5SAlan Somers {
611ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
612ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
613ff4fbdf5SAlan Somers 	uint64_t ino = 42;
614ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
615ff4fbdf5SAlan Somers 
616a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
617ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
618ff4fbdf5SAlan Somers 	expect_removexattr();
619ff4fbdf5SAlan Somers 
620ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
621ff4fbdf5SAlan Somers 		<< strerror(errno);
622ff4fbdf5SAlan Somers }
623ff4fbdf5SAlan Somers 
624ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
625ff4fbdf5SAlan Somers {
626ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
627ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
628ff4fbdf5SAlan Somers 	uint64_t ino = 42;
629ff4fbdf5SAlan Somers 	char data[80];
630ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
631ff4fbdf5SAlan Somers 
632a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
633ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
634ff4fbdf5SAlan Somers 
635ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
636ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
637ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
638ff4fbdf5SAlan Somers }
639ff4fbdf5SAlan Somers 
640ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
641ff4fbdf5SAlan Somers {
642ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
643ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
644ff4fbdf5SAlan Somers 	uint64_t ino = 42;
645ff4fbdf5SAlan Somers 	char data[80];
646ff4fbdf5SAlan Somers 	const char value[] = "whatever";
647ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
648ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
649ff4fbdf5SAlan Somers 	ssize_t r;
650ff4fbdf5SAlan Somers 
651a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
652ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
653ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
654ff4fbdf5SAlan Somers 	expect_getxattr(
65529edc611SAlan Somers 		ReturnImmediate([&](auto in __unused, auto& out) {
65629edc611SAlan Somers 			memcpy((void*)out.body.bytes, value, value_len);
65729edc611SAlan Somers 			out.header.len = sizeof(out.header) + value_len;
658ff4fbdf5SAlan Somers 		})
659ff4fbdf5SAlan Somers 	);
660ff4fbdf5SAlan Somers 
661ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
662ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
663ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
664ff4fbdf5SAlan Somers }
665ff4fbdf5SAlan Somers 
666ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
667ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
668ff4fbdf5SAlan Somers {
669ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
670ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
671ff4fbdf5SAlan Somers 	uint64_t ino = 42;
672ff4fbdf5SAlan Somers 	char data[80];
673ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
674ff4fbdf5SAlan Somers 
675a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
676ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
677ff4fbdf5SAlan Somers 
678ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
679ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
680ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
681ff4fbdf5SAlan Somers }
682ff4fbdf5SAlan Somers 
683ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
684ff4fbdf5SAlan Somers {
685ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
686ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
687ff4fbdf5SAlan Somers 	uint64_t ino = 42;
688ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
689ff4fbdf5SAlan Somers 
690a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
691ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
692ff4fbdf5SAlan Somers 
693ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
694ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
695ff4fbdf5SAlan Somers }
696ff4fbdf5SAlan Somers 
697ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
698ff4fbdf5SAlan Somers {
699ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
700ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
701ff4fbdf5SAlan Somers 	uint64_t ino = 42;
702ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
703ff4fbdf5SAlan Somers 
704a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
705ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
706ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
707ff4fbdf5SAlan Somers 	expect_listxattr();
708ff4fbdf5SAlan Somers 
709ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
710ff4fbdf5SAlan Somers 		<< strerror(errno);
711ff4fbdf5SAlan Somers }
712ff4fbdf5SAlan Somers 
713ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
714ff4fbdf5SAlan Somers TEST_F(Listextattr, system)
715ff4fbdf5SAlan Somers {
716ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
717ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
718ff4fbdf5SAlan Somers 	uint64_t ino = 42;
719ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
720ff4fbdf5SAlan Somers 
721a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
722ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
723ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
724ff4fbdf5SAlan Somers 
725ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
726ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
727ff4fbdf5SAlan Somers }
728ff4fbdf5SAlan Somers 
729ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
730ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
731ff4fbdf5SAlan Somers {
732ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
733ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
734ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
735ff4fbdf5SAlan Somers 
736a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
737ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
738ff4fbdf5SAlan Somers 
739ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
740ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
741ff4fbdf5SAlan Somers }
742ff4fbdf5SAlan Somers 
743ff4fbdf5SAlan Somers TEST_F(Open, eacces)
744ff4fbdf5SAlan Somers {
745ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
746ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
747ff4fbdf5SAlan Somers 	uint64_t ino = 42;
748ff4fbdf5SAlan Somers 
749a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
750ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
751ff4fbdf5SAlan Somers 
752ff4fbdf5SAlan Somers 	EXPECT_NE(0, open(FULLPATH, O_RDWR));
753ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
754ff4fbdf5SAlan Somers }
755ff4fbdf5SAlan Somers 
7569821f1d3SAlan Somers TEST_F(Open, ok)
7579821f1d3SAlan Somers {
7589821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
7599821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
7609821f1d3SAlan Somers 	uint64_t ino = 42;
7619821f1d3SAlan Somers 	int fd;
7629821f1d3SAlan Somers 
763a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
764ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
7659821f1d3SAlan Somers 	expect_open(ino, 0, 1);
7669821f1d3SAlan Somers 
7679821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
7689821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
7697fc0921dSAlan Somers 	leak(fd);
7709821f1d3SAlan Somers }
7719821f1d3SAlan Somers 
772ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
773ff4fbdf5SAlan Somers {
774ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
775ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
776ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
777ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
778ff4fbdf5SAlan Somers 	uint64_t ino = 42;
779ff4fbdf5SAlan Somers 
780a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
781ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
782a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
783ff4fbdf5SAlan Somers 		.Times(AnyNumber())
784ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
785ff4fbdf5SAlan Somers 
786ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
787ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
788ff4fbdf5SAlan Somers }
789ff4fbdf5SAlan Somers 
790ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
791ff4fbdf5SAlan Somers {
792ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
793ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
794ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
795ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
796ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
797ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
798ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
799ff4fbdf5SAlan Somers 
800a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
801ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
802ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
803ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
804ff4fbdf5SAlan Somers 
805ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
806ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
807ff4fbdf5SAlan Somers }
808ff4fbdf5SAlan Somers 
809ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
810ff4fbdf5SAlan Somers {
811ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
812ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
813ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
814ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
815ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
816ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
817ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
818ff4fbdf5SAlan Somers 
819a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
820ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
821ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
822ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
823ff4fbdf5SAlan Somers 
824ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
825ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
826ff4fbdf5SAlan Somers }
827ff4fbdf5SAlan Somers 
8286124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
829ff4fbdf5SAlan Somers {
830ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
831ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
832ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
833ff4fbdf5SAlan Somers 	uint64_t ino = 42;
834ff4fbdf5SAlan Somers 
835a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
836ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
837ff4fbdf5SAlan Somers 
838ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
839ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
840ff4fbdf5SAlan Somers }
841ff4fbdf5SAlan Somers 
8428e45ec4eSAlan Somers /*
8438e45ec4eSAlan Somers  * A user cannot move out a subdirectory that he does not own, because that
8448e45ec4eSAlan Somers  * would require changing the subdirectory's ".." dirent
8458e45ec4eSAlan Somers  */
8468e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory)
8478e45ec4eSAlan Somers {
8488e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
8498e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
8508e45ec4eSAlan Somers 	const char RELDSTDIR[] = "d";
8518e45ec4eSAlan Somers 	const char RELDST[] = "dst";
8528e45ec4eSAlan Somers 	const char RELSRC[] = "src";
8538e45ec4eSAlan Somers 	uint64_t ino = 42;
8548e45ec4eSAlan Somers 	uint64_t dstdir_ino = 43;
8558e45ec4eSAlan Somers 
856a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
8578e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
8588e45ec4eSAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
8598e45ec4eSAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
8608e45ec4eSAlan Somers 
8618e45ec4eSAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
8628e45ec4eSAlan Somers 	ASSERT_EQ(EACCES, errno);
8638e45ec4eSAlan Somers }
8648e45ec4eSAlan Somers 
8658e45ec4eSAlan Somers /*
8668e45ec4eSAlan Somers  * A user _can_ rename a subdirectory to which he lacks write permissions, if
8678e45ec4eSAlan Somers  * it will keep the same parent
8688e45ec4eSAlan Somers  */
8698e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir)
8708e45ec4eSAlan Somers {
8718e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/dst";
8728e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
8738e45ec4eSAlan Somers 	const char RELDST[] = "dst";
8748e45ec4eSAlan Somers 	const char RELSRC[] = "src";
8758e45ec4eSAlan Somers 	uint64_t ino = 42;
8768e45ec4eSAlan Somers 
877a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
8788e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
879a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
880a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
8818e45ec4eSAlan Somers 	expect_rename(0);
8828e45ec4eSAlan Somers 
8838e45ec4eSAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
8848e45ec4eSAlan Somers }
8858e45ec4eSAlan Somers 
8866124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
887ff4fbdf5SAlan Somers {
888ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
889ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
8906124fd71SAlan Somers 	const char RELDST[] = "dst";
891ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
892ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
893ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
894ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
895ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
896ff4fbdf5SAlan Somers 
897a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
898ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
899ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
9006124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
90129edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
9026124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
90329edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
90429edc611SAlan Somers 		out.body.entry.nodeid = dst_ino;
90529edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
90629edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
90729edc611SAlan Somers 		out.body.entry.attr.uid = 0;
9086124fd71SAlan Somers 	})));
909ff4fbdf5SAlan Somers 
910ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
911ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
912ff4fbdf5SAlan Somers }
913ff4fbdf5SAlan Somers 
914ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
915ff4fbdf5SAlan Somers TEST_F(Rename, ok)
916ff4fbdf5SAlan Somers {
917ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
918ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
919ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
920ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
921ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
922ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
923ff4fbdf5SAlan Somers 	uint64_t ino = 42;
924ff4fbdf5SAlan Somers 
925a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
926ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
927ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
928ff4fbdf5SAlan Somers 	expect_rename(0);
929ff4fbdf5SAlan Somers 
930ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
931ff4fbdf5SAlan Somers }
932ff4fbdf5SAlan Somers 
933ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
934ff4fbdf5SAlan Somers {
935ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
936ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
937ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
938ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
939ff4fbdf5SAlan Somers 	uint64_t ino = 42;
940ff4fbdf5SAlan Somers 
941a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
942ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
943a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
944a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
945ff4fbdf5SAlan Somers 	expect_rename(0);
946ff4fbdf5SAlan Somers 
947ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
948ff4fbdf5SAlan Somers }
949ff4fbdf5SAlan Somers 
950ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
951ff4fbdf5SAlan Somers {
952ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
953ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
954ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
955ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
956ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
957ff4fbdf5SAlan Somers 
958a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
959ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
960ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
961ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
96229edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
96329edc611SAlan Somers 				in.header.nodeid == ino &&
96429edc611SAlan Somers 				in.body.setattr.mode == newmode);
965ff4fbdf5SAlan Somers 		}, Eq(true)),
966ff4fbdf5SAlan Somers 		_)
96729edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
968ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
96929edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
970ff4fbdf5SAlan Somers 	})));
971ff4fbdf5SAlan Somers 
972ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
973ff4fbdf5SAlan Somers }
974ff4fbdf5SAlan Somers 
975ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
976ff4fbdf5SAlan Somers {
977ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
978ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
979ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
980ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
981ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
982ff4fbdf5SAlan Somers 
983a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
984ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
985ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
986ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
98729edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
988ff4fbdf5SAlan Somers 		}, Eq(true)),
989ff4fbdf5SAlan Somers 		_)
990ff4fbdf5SAlan Somers 	).Times(0);
991ff4fbdf5SAlan Somers 
992ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
993ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
994ff4fbdf5SAlan Somers }
995ff4fbdf5SAlan Somers 
9968cfb4431SAlan Somers /*
9973fa12789SAlan Somers  * ftruncate() of a file without writable permissions should succeed as long as
9983fa12789SAlan Somers  * the file descriptor is writable.  This is important when combined with
9993fa12789SAlan Somers  * O_CREAT
10003fa12789SAlan Somers  */
10013fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file)
10023fa12789SAlan Somers {
10033fa12789SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
10043fa12789SAlan Somers 	const char RELPATH[] = "some_file.txt";
10053fa12789SAlan Somers 	const uint64_t ino = 42;
10063fa12789SAlan Somers 	const mode_t mode = 0000;
10073fa12789SAlan Somers 	int fd;
10083fa12789SAlan Somers 
1009a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1010a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1011a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
10123fa12789SAlan Somers 	expect_create(RELPATH, ino);
10133fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
10143fa12789SAlan Somers 		ResultOf([](auto in) {
101529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
101629edc611SAlan Somers 				in.header.nodeid == ino &&
101729edc611SAlan Somers 				(in.body.setattr.valid & FATTR_SIZE));
10183fa12789SAlan Somers 		}, Eq(true)),
10193fa12789SAlan Somers 		_)
102029edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
10213fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
102229edc611SAlan Somers 		out.body.attr.attr.ino = ino;
102329edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
102429edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
10253fa12789SAlan Somers 	})));
10263fa12789SAlan Somers 
10273fa12789SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
10283fa12789SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
10293fa12789SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
10307fc0921dSAlan Somers 	leak(fd);
10313fa12789SAlan Somers }
10323fa12789SAlan Somers 
10333fa12789SAlan Somers /*
10348cfb4431SAlan Somers  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
10358cfb4431SAlan Somers  * to the file's group
10368cfb4431SAlan Somers  */
10378cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member)
10388cfb4431SAlan Somers {
10398cfb4431SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
10408cfb4431SAlan Somers 	const char RELPATH[] = "some_file.txt";
10418cfb4431SAlan Somers 	const uint64_t ino = 42;
10428cfb4431SAlan Somers 	const mode_t oldmode = 0755;
10438cfb4431SAlan Somers 	const mode_t newmode = 02755;
10448cfb4431SAlan Somers 	uid_t uid = geteuid();
10458cfb4431SAlan Somers 	gid_t gid = excluded_group();
10468cfb4431SAlan Somers 
1047a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
10488cfb4431SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
10498cfb4431SAlan Somers 	EXPECT_CALL(*m_mock, process(
10508cfb4431SAlan Somers 		ResultOf([](auto in) {
105129edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
10528cfb4431SAlan Somers 		}, Eq(true)),
10538cfb4431SAlan Somers 		_)
10548cfb4431SAlan Somers 	).Times(0);
10558cfb4431SAlan Somers 
10568cfb4431SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
10578cfb4431SAlan Somers 	EXPECT_EQ(EPERM, errno);
10588cfb4431SAlan Somers }
10598cfb4431SAlan Somers 
1060e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
1061e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
1062e5ff3a7eSAlan Somers {
1063e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1064e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
1065e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
1066e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
1067e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
1068e5ff3a7eSAlan Somers 
1069a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1070e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1071e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
1072e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
107329edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1074e5ff3a7eSAlan Somers 		}, Eq(true)),
1075e5ff3a7eSAlan Somers 		_)
1076e5ff3a7eSAlan Somers 	).Times(0);
1077e5ff3a7eSAlan Somers 
1078e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1079e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
1080e5ff3a7eSAlan Somers }
1081e5ff3a7eSAlan Somers 
1082ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
1083ff4fbdf5SAlan Somers {
1084ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1085ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1086ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1087ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1088ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1089ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1090ff4fbdf5SAlan Somers 	ssize_t r;
1091ff4fbdf5SAlan Somers 
1092a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1093ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1094ff4fbdf5SAlan Somers 	expect_setxattr(0);
1095ff4fbdf5SAlan Somers 
10965a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
10975a0b9a27SAlan Somers 		value_len);
1098ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1099ff4fbdf5SAlan Somers }
1100ff4fbdf5SAlan Somers 
1101ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
1102ff4fbdf5SAlan Somers {
1103ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1104ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1105ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1106ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1107ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1108ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1109ff4fbdf5SAlan Somers 
1110a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1111ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1112ff4fbdf5SAlan Somers 
11135a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
11145a0b9a27SAlan Somers 		value_len));
1115ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1116ff4fbdf5SAlan Somers }
1117ff4fbdf5SAlan Somers 
1118ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
1119ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
1120ff4fbdf5SAlan Somers {
1121ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1122ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1123ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1124ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1125ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1126ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1127ff4fbdf5SAlan Somers 
1128a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1129ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1130ff4fbdf5SAlan Somers 
11315a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
11325a0b9a27SAlan Somers 		value_len));
1133ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1134ff4fbdf5SAlan Somers }
1135ff4fbdf5SAlan Somers 
1136ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
1137ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
1138ff4fbdf5SAlan Somers {
1139ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1140ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1141ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1142ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1143ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1144ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1145ff4fbdf5SAlan Somers 	ssize_t r;
1146ff4fbdf5SAlan Somers 
1147a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1148ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1149ff4fbdf5SAlan Somers 	expect_setxattr(0);
1150ff4fbdf5SAlan Somers 
11515a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
11525a0b9a27SAlan Somers 		value_len);
1153ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1154ff4fbdf5SAlan Somers }
1155ff4fbdf5SAlan Somers 
1156ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
11579821f1d3SAlan Somers {
11589821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11599821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
11609821f1d3SAlan Somers 	uint64_t ino = 42;
1161*331884f2SAlan Somers 	sem_t sem;
1162*331884f2SAlan Somers 
1163*331884f2SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
11649821f1d3SAlan Somers 
1165a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1166ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1167a34cdd26SAlan Somers 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
1168*331884f2SAlan Somers 	expect_forget(ino, 1, &sem);
11699821f1d3SAlan Somers 
1170ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1171*331884f2SAlan Somers 
1172*331884f2SAlan Somers 	sem_wait(&sem);
1173*331884f2SAlan Somers 	sem_destroy(&sem);
1174ff4fbdf5SAlan Somers }
1175ff4fbdf5SAlan Somers 
11766124fd71SAlan Somers /*
11776124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
11786124fd71SAlan Somers  * in VOP_LOOKUP.
11796124fd71SAlan Somers  *
11806124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
11816124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
11826124fd71SAlan Somers  */
11836124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
11846124fd71SAlan Somers {
11856124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11866124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
11876124fd71SAlan Somers 	uint64_t ino = 42;
11886124fd71SAlan Somers 
1189a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1190a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
11916124fd71SAlan Somers 	.Times(AnyNumber())
11926124fd71SAlan Somers 	.WillRepeatedly(Invoke(
119329edc611SAlan Somers 		ReturnImmediate([=](auto i __unused, auto& out) {
11946124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
119529edc611SAlan Somers 			out.body.entry.attr.mode = S_IFREG | 0644;
119629edc611SAlan Somers 			out.body.entry.nodeid = ino;
119729edc611SAlan Somers 			out.body.entry.entry_valid = UINT64_MAX;
11986124fd71SAlan Somers 		}))
11996124fd71SAlan Somers 	);
12006124fd71SAlan Somers 
12016124fd71SAlan Somers 	/* Fill name cache */
12026124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
12036124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
12046124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
12056124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
12066124fd71SAlan Somers }
12076124fd71SAlan Somers 
1208ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
1209ff4fbdf5SAlan Somers {
1210ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1211ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1212ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1213ff4fbdf5SAlan Somers 
1214a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1215ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1216ff4fbdf5SAlan Somers 
1217ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1218ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1219ff4fbdf5SAlan Somers }
1220ff4fbdf5SAlan Somers 
12216124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
1222ff4fbdf5SAlan Somers {
1223ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1224ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1225ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1226ff4fbdf5SAlan Somers 
1227a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1228ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1229ff4fbdf5SAlan Somers 
1230ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1231ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
12329821f1d3SAlan Somers }
1233a90e32deSAlan Somers 
1234a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
1235a90e32deSAlan Somers TEST_F(Write, clear_suid)
1236a90e32deSAlan Somers {
1237a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1238a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1239a90e32deSAlan Somers 	struct stat sb;
1240a90e32deSAlan Somers 	uint64_t ino = 42;
1241a90e32deSAlan Somers 	mode_t oldmode = 04777;
1242a90e32deSAlan Somers 	mode_t newmode = 0777;
1243a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1244a90e32deSAlan Somers 	int fd;
1245a90e32deSAlan Somers 
1246a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1247a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1248a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1249bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
125018a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1251a90e32deSAlan Somers 
1252a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1253a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1254a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1255a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1256a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
12577fc0921dSAlan Somers 	leak(fd);
1258a90e32deSAlan Somers }
1259a90e32deSAlan Somers 
1260a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
1261a90e32deSAlan Somers TEST_F(Write, clear_sgid)
1262a90e32deSAlan Somers {
1263a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1264a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1265a90e32deSAlan Somers 	struct stat sb;
1266a90e32deSAlan Somers 	uint64_t ino = 42;
1267a90e32deSAlan Somers 	mode_t oldmode = 02777;
1268a90e32deSAlan Somers 	mode_t newmode = 0777;
1269a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1270a90e32deSAlan Somers 	int fd;
1271a90e32deSAlan Somers 
1272a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1273a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1274a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1275bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
127618a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1277a90e32deSAlan Somers 
1278a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1279a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1280a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1281a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1282a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
12837fc0921dSAlan Somers 	leak(fd);
1284a90e32deSAlan Somers }
128518a2264eSAlan Somers 
128618a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic
128718a2264eSAlan Somers  *
128818a2264eSAlan Somers  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
128918a2264eSAlan Somers  * may panic.  That happens if the FUSE_SETATTR response indicates that the
129018a2264eSAlan Somers  * file's size has changed since the write.
129118a2264eSAlan Somers  */
129218a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid)
129318a2264eSAlan Somers {
129418a2264eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
129518a2264eSAlan Somers 	const char RELPATH[] = "some_file.txt";
129618a2264eSAlan Somers 	uint64_t ino = 42;
129718a2264eSAlan Somers 	mode_t oldmode = 04777;
129818a2264eSAlan Somers 	mode_t newmode = 0777;
129918a2264eSAlan Somers 	char wbuf[1] = {'x'};
130018a2264eSAlan Somers 	int fd;
130118a2264eSAlan Somers 
1302a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
130318a2264eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
130418a2264eSAlan Somers 	expect_open(ino, 0, 1);
1305bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
130618a2264eSAlan Somers 	/* XXX Return a smaller file size than what we just wrote! */
130718a2264eSAlan Somers 	expect_chmod(ino, newmode, 0);
130818a2264eSAlan Somers 
130918a2264eSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
131018a2264eSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
131118a2264eSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
13127fc0921dSAlan Somers 	leak(fd);
131318a2264eSAlan Somers }
131418a2264eSAlan Somers 
131518a2264eSAlan Somers 
1316