xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 7fc0921d7e3c52f66ca8e47c0413ee46f6d73328)
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.
299821f1d3SAlan Somers  */
309821f1d3SAlan Somers 
319821f1d3SAlan Somers /*
329821f1d3SAlan Somers  * Tests for the "default_permissions" mount option.  They must be in their own
339821f1d3SAlan Somers  * file so they can be run as an unprivileged user
349821f1d3SAlan Somers  */
359821f1d3SAlan Somers 
369821f1d3SAlan Somers extern "C" {
37ff4fbdf5SAlan Somers #include <sys/types.h>
38ff4fbdf5SAlan Somers #include <sys/extattr.h>
39ff4fbdf5SAlan Somers 
409821f1d3SAlan Somers #include <fcntl.h>
419821f1d3SAlan Somers #include <unistd.h>
429821f1d3SAlan Somers }
439821f1d3SAlan Somers 
449821f1d3SAlan Somers #include "mockfs.hh"
459821f1d3SAlan Somers #include "utils.hh"
469821f1d3SAlan Somers 
479821f1d3SAlan Somers using namespace testing;
489821f1d3SAlan Somers 
499821f1d3SAlan Somers class DefaultPermissions: public FuseTest {
509821f1d3SAlan Somers 
519821f1d3SAlan Somers virtual void SetUp() {
52ff4fbdf5SAlan Somers 	m_default_permissions = true;
539821f1d3SAlan Somers 	FuseTest::SetUp();
54ff4fbdf5SAlan Somers 	if (HasFatalFailure() || IsSkipped())
55ff4fbdf5SAlan Somers 		return;
569821f1d3SAlan Somers 
579821f1d3SAlan Somers 	if (geteuid() == 0) {
589821f1d3SAlan Somers 		GTEST_SKIP() << "This test requires an unprivileged user";
599821f1d3SAlan Somers 	}
60ff4fbdf5SAlan Somers 
61ff4fbdf5SAlan Somers 	/* With -o default_permissions, FUSE_ACCESS should never be called */
62ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
63ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
6429edc611SAlan Somers 			return (in.header.opcode == FUSE_ACCESS);
65ff4fbdf5SAlan Somers 		}, Eq(true)),
66ff4fbdf5SAlan Somers 		_)
67ff4fbdf5SAlan Somers 	).Times(0);
689821f1d3SAlan Somers }
699821f1d3SAlan Somers 
709821f1d3SAlan Somers public:
7118a2264eSAlan Somers void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0)
72a90e32deSAlan Somers {
73a90e32deSAlan Somers 	EXPECT_CALL(*m_mock, process(
74a90e32deSAlan Somers 		ResultOf([=](auto in) {
7529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
7629edc611SAlan Somers 				in.header.nodeid == ino &&
7729edc611SAlan Somers 				in.body.setattr.valid == FATTR_MODE &&
7829edc611SAlan Somers 				in.body.setattr.mode == mode);
79a90e32deSAlan Somers 		}, Eq(true)),
80a90e32deSAlan Somers 		_)
8129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
82a90e32deSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
8329edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
8429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
8529edc611SAlan Somers 		out.body.attr.attr.size = size;
8629edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
87a90e32deSAlan Somers 	})));
88a90e32deSAlan Somers }
89a90e32deSAlan Somers 
903fa12789SAlan Somers void expect_create(const char *relpath, uint64_t ino)
913fa12789SAlan Somers {
923fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
933fa12789SAlan Somers 		ResultOf([=](auto in) {
9429edc611SAlan Somers 			const char *name = (const char*)in.body.bytes +
95a4856c96SAlan Somers 				sizeof(fuse_create_in);
9629edc611SAlan Somers 			return (in.header.opcode == FUSE_CREATE &&
973fa12789SAlan Somers 				(0 == strcmp(relpath, name)));
983fa12789SAlan Somers 		}, Eq(true)),
993fa12789SAlan Somers 		_)
10029edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1013fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
10229edc611SAlan Somers 		out.body.create.entry.attr.mode = S_IFREG | 0644;
10329edc611SAlan Somers 		out.body.create.entry.nodeid = ino;
10429edc611SAlan Somers 		out.body.create.entry.entry_valid = UINT64_MAX;
10529edc611SAlan Somers 		out.body.create.entry.attr_valid = UINT64_MAX;
1063fa12789SAlan Somers 	})));
1073fa12789SAlan Somers }
1083fa12789SAlan Somers 
109ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
110474ba6faSAlan Somers 	uid_t uid = 0, gid_t gid = 0)
1119821f1d3SAlan Somers {
112ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
113ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
11429edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
11529edc611SAlan Somers 				in.header.nodeid == ino);
116ff4fbdf5SAlan Somers 		}, Eq(true)),
117ff4fbdf5SAlan Somers 		_)
118ff4fbdf5SAlan Somers 	).Times(times)
11929edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
120ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
12129edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
12229edc611SAlan Somers 		out.body.attr.attr.mode = mode;
12329edc611SAlan Somers 		out.body.attr.attr.size = 0;
12429edc611SAlan Somers 		out.body.attr.attr.uid = uid;
12529edc611SAlan Somers 		out.body.attr.attr.uid = gid;
12629edc611SAlan Somers 		out.body.attr.attr_valid = attr_valid;
127ff4fbdf5SAlan Somers 	})));
128ff4fbdf5SAlan Somers }
129ff4fbdf5SAlan Somers 
130ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
131474ba6faSAlan Somers 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
132ff4fbdf5SAlan Somers {
133474ba6faSAlan Somers 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
1349821f1d3SAlan Somers }
1359821f1d3SAlan Somers 
1369821f1d3SAlan Somers };
1379821f1d3SAlan Somers 
1389821f1d3SAlan Somers class Access: public DefaultPermissions {};
139474ba6faSAlan Somers class Chown: public DefaultPermissions {};
140474ba6faSAlan Somers class Chgrp: public DefaultPermissions {};
141ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1429821f1d3SAlan Somers class Open: public DefaultPermissions {};
143ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {};
144ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {};
145d943c93eSAlan Somers class Utimensat: public DefaultPermissions {};
146a90e32deSAlan Somers class Write: public DefaultPermissions {};
1479821f1d3SAlan Somers 
148ff4fbdf5SAlan Somers /*
149ff4fbdf5SAlan Somers  * Test permission handling during create, mkdir, mknod, link, symlink, and
150ff4fbdf5SAlan Somers  * rename vops (they all share a common path for permission checks in
151ff4fbdf5SAlan Somers  * VOP_LOOKUP)
152ff4fbdf5SAlan Somers  */
1533fa12789SAlan Somers class Create: public DefaultPermissions {};
154ff4fbdf5SAlan Somers 
155ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions {
156ff4fbdf5SAlan Somers public:
157ff4fbdf5SAlan Somers void expect_removexattr()
158ff4fbdf5SAlan Somers {
159ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
160ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
16129edc611SAlan Somers 			return (in.header.opcode == FUSE_REMOVEXATTR);
162ff4fbdf5SAlan Somers 		}, Eq(true)),
163ff4fbdf5SAlan Somers 		_)
164ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(0)));
165ff4fbdf5SAlan Somers }
166ff4fbdf5SAlan Somers };
167ff4fbdf5SAlan Somers 
168ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions {
169ff4fbdf5SAlan Somers public:
170ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r)
171ff4fbdf5SAlan Somers {
172ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
173ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
17429edc611SAlan Somers 			return (in.header.opcode == FUSE_GETXATTR);
175ff4fbdf5SAlan Somers 		}, Eq(true)),
176ff4fbdf5SAlan Somers 		_)
177ff4fbdf5SAlan Somers 	).WillOnce(Invoke(r));
178ff4fbdf5SAlan Somers }
179ff4fbdf5SAlan Somers };
180ff4fbdf5SAlan Somers 
181ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions {
182ff4fbdf5SAlan Somers public:
183ff4fbdf5SAlan Somers void expect_listxattr()
184ff4fbdf5SAlan Somers {
185ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
186ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
18729edc611SAlan Somers 			return (in.header.opcode == FUSE_LISTXATTR);
188ff4fbdf5SAlan Somers 		}, Eq(true)),
189ff4fbdf5SAlan Somers 		_)
19029edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
19129edc611SAlan Somers 		out.body.listxattr.size = 0;
192ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
193ff4fbdf5SAlan Somers 	})));
194ff4fbdf5SAlan Somers }
195ff4fbdf5SAlan Somers };
196ff4fbdf5SAlan Somers 
197ff4fbdf5SAlan Somers class Rename: public DefaultPermissions {
198ff4fbdf5SAlan Somers public:
199ff4fbdf5SAlan Somers 	/*
200ff4fbdf5SAlan Somers 	 * Expect a rename and respond with the given error.  Don't both to
201ff4fbdf5SAlan Somers 	 * validate arguments; the tests in rename.cc do that.
202ff4fbdf5SAlan Somers 	 */
203ff4fbdf5SAlan Somers 	void expect_rename(int error)
204ff4fbdf5SAlan Somers 	{
205ff4fbdf5SAlan Somers 		EXPECT_CALL(*m_mock, process(
206ff4fbdf5SAlan Somers 			ResultOf([=](auto in) {
20729edc611SAlan Somers 				return (in.header.opcode == FUSE_RENAME);
208ff4fbdf5SAlan Somers 			}, Eq(true)),
209ff4fbdf5SAlan Somers 			_)
210ff4fbdf5SAlan Somers 		).WillOnce(Invoke(ReturnErrno(error)));
211ff4fbdf5SAlan Somers 	}
212ff4fbdf5SAlan Somers };
213ff4fbdf5SAlan Somers 
214ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions {
215ff4fbdf5SAlan Somers public:
216ff4fbdf5SAlan Somers void expect_setxattr(int error)
217ff4fbdf5SAlan Somers {
218ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
219ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
22029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETXATTR);
221ff4fbdf5SAlan Somers 		}, Eq(true)),
222ff4fbdf5SAlan Somers 		_)
223ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
224ff4fbdf5SAlan Somers }
225ff4fbdf5SAlan Somers };
226ff4fbdf5SAlan Somers 
2278cfb4431SAlan Somers /* Return a group to which this user does not belong */
2288cfb4431SAlan Somers static gid_t excluded_group()
2298cfb4431SAlan Somers {
2308cfb4431SAlan Somers 	int i, ngroups = 64;
2318cfb4431SAlan Somers 	gid_t newgid, groups[ngroups];
2328cfb4431SAlan Somers 
2338cfb4431SAlan Somers 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
2348cfb4431SAlan Somers 	for (newgid = 0; newgid >= 0; newgid++) {
2358cfb4431SAlan Somers 		bool belongs = false;
2368cfb4431SAlan Somers 
2378cfb4431SAlan Somers 		for (i = 0; i < ngroups; i++) {
2388cfb4431SAlan Somers 			if (groups[i] == newgid)
2398cfb4431SAlan Somers 				belongs = true;
2408cfb4431SAlan Somers 		}
2418cfb4431SAlan Somers 		if (!belongs)
2428cfb4431SAlan Somers 			break;
2438cfb4431SAlan Somers 	}
2448cfb4431SAlan Somers 	/* newgid is now a group to which the current user does not belong */
2458cfb4431SAlan Somers 	return newgid;
2468cfb4431SAlan Somers }
2478cfb4431SAlan Somers 
248ff4fbdf5SAlan Somers TEST_F(Access, eacces)
2499821f1d3SAlan Somers {
2509821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2519821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2529821f1d3SAlan Somers 	uint64_t ino = 42;
2539821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
2549821f1d3SAlan Somers 
255a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
256ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
257ff4fbdf5SAlan Somers 
258ff4fbdf5SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
259ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
260ff4fbdf5SAlan Somers }
261ff4fbdf5SAlan Somers 
262ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs)
263ff4fbdf5SAlan Somers {
264ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
265ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
266ff4fbdf5SAlan Somers 	uint64_t ino = 42;
267ff4fbdf5SAlan Somers 	mode_t	access_mode = X_OK;
268ff4fbdf5SAlan Somers 
269a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1);
270ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
271ff4fbdf5SAlan Somers 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
2729821f1d3SAlan Somers 	/*
2739821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
2749821f1d3SAlan Somers 	 * another FUSE_GETATTR or something in here.  But there should not be
2759821f1d3SAlan Somers 	 * a FUSE_ACCESS
2769821f1d3SAlan Somers 	 */
2779821f1d3SAlan Somers 
2789821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
2799821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
2809821f1d3SAlan Somers }
2819821f1d3SAlan Somers 
282ff4fbdf5SAlan Somers TEST_F(Access, ok)
2839821f1d3SAlan Somers {
2849821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2859821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2869821f1d3SAlan Somers 	uint64_t ino = 42;
2879821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
2889821f1d3SAlan Somers 
289a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
290ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
2919821f1d3SAlan Somers 	/*
2929821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
29391ff3a0dSAlan Somers 	 * another FUSE_GETATTR or something in here.
2949821f1d3SAlan Somers 	 */
2959821f1d3SAlan Somers 
2969821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
2979821f1d3SAlan Somers }
2989821f1d3SAlan Somers 
2994e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */
3004e83d655SAlan Somers TEST_F(Chown, chown_to_self)
3014e83d655SAlan Somers {
3024e83d655SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3034e83d655SAlan Somers 	const char RELPATH[] = "some_file.txt";
3044e83d655SAlan Somers 	const uint64_t ino = 42;
3054e83d655SAlan Somers 	const mode_t mode = 0755;
3064e83d655SAlan Somers 	uid_t uid;
3074e83d655SAlan Somers 
3084e83d655SAlan Somers 	uid = geteuid();
3094e83d655SAlan Somers 
310a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
3114e83d655SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
3124e83d655SAlan Somers 	/* The OS may optimize chown by omitting the redundant setattr */
3134e83d655SAlan Somers 	EXPECT_CALL(*m_mock, process(
3144e83d655SAlan Somers 		ResultOf([](auto in) {
31529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
3164e83d655SAlan Somers 		}, Eq(true)),
3174e83d655SAlan Somers 		_)
31829edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
3194e83d655SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
32029edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
32129edc611SAlan Somers 		out.body.attr.attr.uid = uid;
3224e83d655SAlan Somers 	})));
3234e83d655SAlan Somers 
3244e83d655SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
3254e83d655SAlan Somers }
3264e83d655SAlan Somers 
327a2bdd737SAlan Somers /*
328a2bdd737SAlan Somers  * A successful chown by a non-privileged non-owner should clear a file's SUID
329a2bdd737SAlan Somers  * bit
330a2bdd737SAlan Somers  */
331a2bdd737SAlan Somers TEST_F(Chown, clear_suid)
332a2bdd737SAlan Somers {
333a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
334a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
335a2bdd737SAlan Somers 	uint64_t ino = 42;
336a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
337a2bdd737SAlan Somers 	const mode_t newmode = 0755;
338a2bdd737SAlan Somers 	uid_t uid = geteuid();
339a2bdd737SAlan Somers 	uint32_t valid = FATTR_UID | FATTR_MODE;
340a2bdd737SAlan Somers 
341a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
342a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
343a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
344a2bdd737SAlan Somers 		ResultOf([=](auto in) {
34529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
34629edc611SAlan Somers 				in.header.nodeid == ino &&
34729edc611SAlan Somers 				in.body.setattr.valid == valid &&
34829edc611SAlan Somers 				in.body.setattr.mode == newmode);
349a2bdd737SAlan Somers 		}, Eq(true)),
350a2bdd737SAlan Somers 		_)
35129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
352a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
35329edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
35429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
35529edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
356a2bdd737SAlan Somers 	})));
357a2bdd737SAlan Somers 
358a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
359a2bdd737SAlan Somers }
360a2bdd737SAlan Somers 
361a2bdd737SAlan Somers 
362474ba6faSAlan Somers /* Only root may change a file's owner */
363474ba6faSAlan Somers TEST_F(Chown, eperm)
364474ba6faSAlan Somers {
365474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
366474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
367474ba6faSAlan Somers 	const uint64_t ino = 42;
368474ba6faSAlan Somers 	const mode_t mode = 0755;
369474ba6faSAlan Somers 
370a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
371474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
372474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
373474ba6faSAlan Somers 		ResultOf([](auto in) {
37429edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
375474ba6faSAlan Somers 		}, Eq(true)),
376474ba6faSAlan Somers 		_)
377474ba6faSAlan Somers 	).Times(0);
378474ba6faSAlan Somers 
379474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
380474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
381474ba6faSAlan Somers }
382474ba6faSAlan Somers 
383a2bdd737SAlan Somers /*
384a2bdd737SAlan Somers  * A successful chgrp by a non-privileged non-owner should clear a file's SUID
385a2bdd737SAlan Somers  * bit
386a2bdd737SAlan Somers  */
387a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid)
388a2bdd737SAlan Somers {
389a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
390a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
391a2bdd737SAlan Somers 	uint64_t ino = 42;
392a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
393a2bdd737SAlan Somers 	const mode_t newmode = 0755;
394a2bdd737SAlan Somers 	uid_t uid = geteuid();
395a2bdd737SAlan Somers 	gid_t gid = getegid();
396a2bdd737SAlan Somers 	uint32_t valid = FATTR_GID | FATTR_MODE;
397a2bdd737SAlan Somers 
398a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
399a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
400a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
401a2bdd737SAlan Somers 		ResultOf([=](auto in) {
40229edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
40329edc611SAlan Somers 				in.header.nodeid == ino &&
40429edc611SAlan Somers 				in.body.setattr.valid == valid &&
40529edc611SAlan Somers 				in.body.setattr.mode == newmode);
406a2bdd737SAlan Somers 		}, Eq(true)),
407a2bdd737SAlan Somers 		_)
40829edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
409a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
41029edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
41129edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
41229edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
413a2bdd737SAlan Somers 	})));
414a2bdd737SAlan Somers 
415a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
416a2bdd737SAlan Somers }
417a2bdd737SAlan Somers 
418474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */
419474ba6faSAlan Somers TEST_F(Chgrp, eperm)
420474ba6faSAlan Somers {
421474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
422474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
423474ba6faSAlan Somers 	const uint64_t ino = 42;
424474ba6faSAlan Somers 	const mode_t mode = 0755;
425474ba6faSAlan Somers 	uid_t uid;
426474ba6faSAlan Somers 	gid_t gid, newgid;
427474ba6faSAlan Somers 
428474ba6faSAlan Somers 	uid = geteuid();
429474ba6faSAlan Somers 	gid = getegid();
4308cfb4431SAlan Somers 	newgid = excluded_group();
431474ba6faSAlan Somers 
432a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
433474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
434474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
435474ba6faSAlan Somers 		ResultOf([](auto in) {
43629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
437474ba6faSAlan Somers 		}, Eq(true)),
438474ba6faSAlan Somers 		_)
439474ba6faSAlan Somers 	).Times(0);
440474ba6faSAlan Somers 
441474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
442474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
443474ba6faSAlan Somers }
444474ba6faSAlan Somers 
445474ba6faSAlan Somers TEST_F(Chgrp, ok)
446474ba6faSAlan Somers {
447474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
448474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
449474ba6faSAlan Somers 	const uint64_t ino = 42;
450474ba6faSAlan Somers 	const mode_t mode = 0755;
451474ba6faSAlan Somers 	uid_t uid;
452474ba6faSAlan Somers 	gid_t gid, newgid;
453474ba6faSAlan Somers 
454474ba6faSAlan Somers 	uid = geteuid();
455474ba6faSAlan Somers 	gid = 0;
456474ba6faSAlan Somers 	newgid = getegid();
457474ba6faSAlan Somers 
458a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
459474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
4604e83d655SAlan Somers 	/* The OS may optimize chgrp by omitting the redundant setattr */
461474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
462474ba6faSAlan Somers 		ResultOf([](auto in) {
46329edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
46429edc611SAlan Somers 				in.header.nodeid == ino);
465474ba6faSAlan Somers 		}, Eq(true)),
466474ba6faSAlan Somers 		_)
46729edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
468474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
46929edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
47029edc611SAlan Somers 		out.body.attr.attr.uid = uid;
47129edc611SAlan Somers 		out.body.attr.attr.gid = newgid;
472474ba6faSAlan Somers 	})));
473474ba6faSAlan Somers 
474474ba6faSAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
475474ba6faSAlan Somers }
476474ba6faSAlan Somers 
477ff4fbdf5SAlan Somers TEST_F(Create, ok)
478ff4fbdf5SAlan Somers {
479ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
480ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
481ff4fbdf5SAlan Somers 	uint64_t ino = 42;
482ff4fbdf5SAlan Somers 	int fd;
483ff4fbdf5SAlan Somers 
484a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
485a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
486a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
487ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
488ff4fbdf5SAlan Somers 
489ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
490ff4fbdf5SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
491*7fc0921dSAlan Somers 	leak(fd);
492ff4fbdf5SAlan Somers }
493ff4fbdf5SAlan Somers 
494ff4fbdf5SAlan Somers TEST_F(Create, eacces)
495ff4fbdf5SAlan Somers {
496ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
497ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
498ff4fbdf5SAlan Somers 
499a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
500a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
501a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
502ff4fbdf5SAlan Somers 
503ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
504ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
505ff4fbdf5SAlan Somers }
506ff4fbdf5SAlan Somers 
507ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
508ff4fbdf5SAlan Somers {
509ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
510ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
511ff4fbdf5SAlan Somers 	uint64_t ino = 42;
512ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
513ff4fbdf5SAlan Somers 
514a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
515ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
516ff4fbdf5SAlan Somers 
517ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
518ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
519ff4fbdf5SAlan Somers }
520ff4fbdf5SAlan Somers 
521ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
522ff4fbdf5SAlan Somers {
523ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
524ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
525ff4fbdf5SAlan Somers 	uint64_t ino = 42;
526ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
527ff4fbdf5SAlan Somers 
528a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
529ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
530ff4fbdf5SAlan Somers 	expect_removexattr();
531ff4fbdf5SAlan Somers 
532ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
533ff4fbdf5SAlan Somers 		<< strerror(errno);
534ff4fbdf5SAlan Somers }
535ff4fbdf5SAlan Somers 
536ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
537ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
538ff4fbdf5SAlan Somers {
539ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
540ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
541ff4fbdf5SAlan Somers 	uint64_t ino = 42;
542ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
543ff4fbdf5SAlan Somers 
544a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
545ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
546ff4fbdf5SAlan Somers 
547ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
548ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
549ff4fbdf5SAlan Somers }
550ff4fbdf5SAlan Somers 
551d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */
552d943c93eSAlan Somers TEST_F(Utimensat, utime_now)
553d943c93eSAlan Somers {
554d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
555d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
556d943c93eSAlan Somers 	const uint64_t ino = 42;
557d943c93eSAlan Somers 	/* Write permissions for everybody */
558d943c93eSAlan Somers 	const mode_t mode = 0666;
559d943c93eSAlan Somers 	uid_t owner = 0;
560d943c93eSAlan Somers 	const timespec times[2] = {
561d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
562d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
563d943c93eSAlan Somers 	};
564d943c93eSAlan Somers 
565a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
566d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
567d943c93eSAlan Somers 	EXPECT_CALL(*m_mock, process(
568d943c93eSAlan Somers 		ResultOf([](auto in) {
56929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
57029edc611SAlan Somers 				in.header.nodeid == ino &&
57129edc611SAlan Somers 				in.body.setattr.valid & FATTR_ATIME &&
57229edc611SAlan Somers 				in.body.setattr.valid & FATTR_MTIME);
573d943c93eSAlan Somers 		}, Eq(true)),
574d943c93eSAlan Somers 		_)
57529edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
576d943c93eSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
57729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
578d943c93eSAlan Somers 	})));
579d943c93eSAlan Somers 
580d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
581d943c93eSAlan Somers 		<< strerror(errno);
582d943c93eSAlan Somers }
583d943c93eSAlan Somers 
584d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */
585d943c93eSAlan Somers TEST_F(Utimensat, utime_omit)
586d943c93eSAlan Somers {
587d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
588d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
589d943c93eSAlan Somers 	const uint64_t ino = 42;
590d943c93eSAlan Somers 	/* Write permissions for no one */
591d943c93eSAlan Somers 	const mode_t mode = 0444;
592d943c93eSAlan Somers 	uid_t owner = 0;
593d943c93eSAlan Somers 	const timespec times[2] = {
594d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
595d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
596d943c93eSAlan Somers 	};
597d943c93eSAlan Somers 
598a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
599d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
600d943c93eSAlan Somers 
601d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
602d943c93eSAlan Somers 		<< strerror(errno);
603d943c93eSAlan Somers }
604d943c93eSAlan Somers 
605ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
606ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
607ff4fbdf5SAlan Somers {
608ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
609ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
610ff4fbdf5SAlan Somers 	uint64_t ino = 42;
611ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
612ff4fbdf5SAlan Somers 
613a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
614ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
615ff4fbdf5SAlan Somers 	expect_removexattr();
616ff4fbdf5SAlan Somers 
617ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
618ff4fbdf5SAlan Somers 		<< strerror(errno);
619ff4fbdf5SAlan Somers }
620ff4fbdf5SAlan Somers 
621ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
622ff4fbdf5SAlan Somers {
623ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
624ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
625ff4fbdf5SAlan Somers 	uint64_t ino = 42;
626ff4fbdf5SAlan Somers 	char data[80];
627ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
628ff4fbdf5SAlan Somers 
629a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
630ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
631ff4fbdf5SAlan Somers 
632ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
633ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
634ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
635ff4fbdf5SAlan Somers }
636ff4fbdf5SAlan Somers 
637ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
638ff4fbdf5SAlan Somers {
639ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
640ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
641ff4fbdf5SAlan Somers 	uint64_t ino = 42;
642ff4fbdf5SAlan Somers 	char data[80];
643ff4fbdf5SAlan Somers 	const char value[] = "whatever";
644ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
645ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
646ff4fbdf5SAlan Somers 	ssize_t r;
647ff4fbdf5SAlan Somers 
648a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
649ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
650ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
651ff4fbdf5SAlan Somers 	expect_getxattr(
65229edc611SAlan Somers 		ReturnImmediate([&](auto in __unused, auto& out) {
65329edc611SAlan Somers 			memcpy((void*)out.body.bytes, value, value_len);
65429edc611SAlan Somers 			out.header.len = sizeof(out.header) + value_len;
655ff4fbdf5SAlan Somers 		})
656ff4fbdf5SAlan Somers 	);
657ff4fbdf5SAlan Somers 
658ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
659ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
660ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
661ff4fbdf5SAlan Somers }
662ff4fbdf5SAlan Somers 
663ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
664ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
665ff4fbdf5SAlan Somers {
666ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
667ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
668ff4fbdf5SAlan Somers 	uint64_t ino = 42;
669ff4fbdf5SAlan Somers 	char data[80];
670ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
671ff4fbdf5SAlan Somers 
672a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
673ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
674ff4fbdf5SAlan Somers 
675ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
676ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
677ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
678ff4fbdf5SAlan Somers }
679ff4fbdf5SAlan Somers 
680ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
681ff4fbdf5SAlan Somers {
682ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
683ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
684ff4fbdf5SAlan Somers 	uint64_t ino = 42;
685ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
686ff4fbdf5SAlan Somers 
687a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
688ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
689ff4fbdf5SAlan Somers 
690ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
691ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
692ff4fbdf5SAlan Somers }
693ff4fbdf5SAlan Somers 
694ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
695ff4fbdf5SAlan Somers {
696ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
697ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
698ff4fbdf5SAlan Somers 	uint64_t ino = 42;
699ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
700ff4fbdf5SAlan Somers 
701a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
702ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
703ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
704ff4fbdf5SAlan Somers 	expect_listxattr();
705ff4fbdf5SAlan Somers 
706ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
707ff4fbdf5SAlan Somers 		<< strerror(errno);
708ff4fbdf5SAlan Somers }
709ff4fbdf5SAlan Somers 
710ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
711ff4fbdf5SAlan Somers TEST_F(Listextattr, system)
712ff4fbdf5SAlan Somers {
713ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
714ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
715ff4fbdf5SAlan Somers 	uint64_t ino = 42;
716ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
717ff4fbdf5SAlan Somers 
718a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
719ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
720ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
721ff4fbdf5SAlan Somers 
722ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
723ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
724ff4fbdf5SAlan Somers }
725ff4fbdf5SAlan Somers 
726ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
727ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
728ff4fbdf5SAlan Somers {
729ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
730ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
731ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
732ff4fbdf5SAlan Somers 
733a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
734ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
735ff4fbdf5SAlan Somers 
736ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
737ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
738ff4fbdf5SAlan Somers }
739ff4fbdf5SAlan Somers 
740ff4fbdf5SAlan Somers TEST_F(Open, eacces)
741ff4fbdf5SAlan Somers {
742ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
743ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
744ff4fbdf5SAlan Somers 	uint64_t ino = 42;
745ff4fbdf5SAlan Somers 
746a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
747ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
748ff4fbdf5SAlan Somers 
749ff4fbdf5SAlan Somers 	EXPECT_NE(0, open(FULLPATH, O_RDWR));
750ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
751ff4fbdf5SAlan Somers }
752ff4fbdf5SAlan Somers 
7539821f1d3SAlan Somers TEST_F(Open, ok)
7549821f1d3SAlan Somers {
7559821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
7569821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
7579821f1d3SAlan Somers 	uint64_t ino = 42;
7589821f1d3SAlan Somers 	int fd;
7599821f1d3SAlan Somers 
760a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
761ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
7629821f1d3SAlan Somers 	expect_open(ino, 0, 1);
7639821f1d3SAlan Somers 
7649821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
7659821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
766*7fc0921dSAlan Somers 	leak(fd);
7679821f1d3SAlan Somers }
7689821f1d3SAlan Somers 
769ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
770ff4fbdf5SAlan Somers {
771ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
772ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
773ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
774ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
775ff4fbdf5SAlan Somers 	uint64_t ino = 42;
776ff4fbdf5SAlan Somers 
777a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
778ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
779a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
780ff4fbdf5SAlan Somers 		.Times(AnyNumber())
781ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
782ff4fbdf5SAlan Somers 
783ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
784ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
785ff4fbdf5SAlan Somers }
786ff4fbdf5SAlan Somers 
787ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
788ff4fbdf5SAlan Somers {
789ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
790ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
791ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
792ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
793ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
794ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
795ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
796ff4fbdf5SAlan Somers 
797a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
798ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
799ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
800ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
801ff4fbdf5SAlan Somers 
802ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
803ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
804ff4fbdf5SAlan Somers }
805ff4fbdf5SAlan Somers 
806ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
807ff4fbdf5SAlan Somers {
808ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
809ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
810ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
811ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
812ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
813ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
814ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
815ff4fbdf5SAlan Somers 
816a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
817ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
818ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
819ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
820ff4fbdf5SAlan Somers 
821ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
822ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
823ff4fbdf5SAlan Somers }
824ff4fbdf5SAlan Somers 
8256124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
826ff4fbdf5SAlan Somers {
827ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
828ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
829ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
830ff4fbdf5SAlan Somers 	uint64_t ino = 42;
831ff4fbdf5SAlan Somers 
832a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
833ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
834ff4fbdf5SAlan Somers 
835ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
836ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
837ff4fbdf5SAlan Somers }
838ff4fbdf5SAlan Somers 
8398e45ec4eSAlan Somers /*
8408e45ec4eSAlan Somers  * A user cannot move out a subdirectory that he does not own, because that
8418e45ec4eSAlan Somers  * would require changing the subdirectory's ".." dirent
8428e45ec4eSAlan Somers  */
8438e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory)
8448e45ec4eSAlan Somers {
8458e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
8468e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
8478e45ec4eSAlan Somers 	const char RELDSTDIR[] = "d";
8488e45ec4eSAlan Somers 	const char RELDST[] = "dst";
8498e45ec4eSAlan Somers 	const char RELSRC[] = "src";
8508e45ec4eSAlan Somers 	uint64_t ino = 42;
8518e45ec4eSAlan Somers 	uint64_t dstdir_ino = 43;
8528e45ec4eSAlan Somers 
853a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
8548e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
8558e45ec4eSAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
8568e45ec4eSAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
8578e45ec4eSAlan Somers 
8588e45ec4eSAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
8598e45ec4eSAlan Somers 	ASSERT_EQ(EACCES, errno);
8608e45ec4eSAlan Somers }
8618e45ec4eSAlan Somers 
8628e45ec4eSAlan Somers /*
8638e45ec4eSAlan Somers  * A user _can_ rename a subdirectory to which he lacks write permissions, if
8648e45ec4eSAlan Somers  * it will keep the same parent
8658e45ec4eSAlan Somers  */
8668e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir)
8678e45ec4eSAlan Somers {
8688e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/dst";
8698e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
8708e45ec4eSAlan Somers 	const char RELDST[] = "dst";
8718e45ec4eSAlan Somers 	const char RELSRC[] = "src";
8728e45ec4eSAlan Somers 	uint64_t ino = 42;
8738e45ec4eSAlan Somers 
874a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
8758e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
876a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
877a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
8788e45ec4eSAlan Somers 	expect_rename(0);
8798e45ec4eSAlan Somers 
8808e45ec4eSAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
8818e45ec4eSAlan Somers }
8828e45ec4eSAlan Somers 
8836124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
884ff4fbdf5SAlan Somers {
885ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
886ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
8876124fd71SAlan Somers 	const char RELDST[] = "dst";
888ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
889ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
890ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
891ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
892ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
893ff4fbdf5SAlan Somers 
894a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
895ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
896ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
8976124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
89829edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
8996124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
90029edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
90129edc611SAlan Somers 		out.body.entry.nodeid = dst_ino;
90229edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
90329edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
90429edc611SAlan Somers 		out.body.entry.attr.uid = 0;
9056124fd71SAlan Somers 	})));
906ff4fbdf5SAlan Somers 
907ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
908ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
909ff4fbdf5SAlan Somers }
910ff4fbdf5SAlan Somers 
911ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
912ff4fbdf5SAlan Somers TEST_F(Rename, ok)
913ff4fbdf5SAlan Somers {
914ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
915ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
916ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
917ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
918ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
919ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
920ff4fbdf5SAlan Somers 	uint64_t ino = 42;
921ff4fbdf5SAlan Somers 
922a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
923ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
924ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
925ff4fbdf5SAlan Somers 	expect_rename(0);
926ff4fbdf5SAlan Somers 
927ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
928ff4fbdf5SAlan Somers }
929ff4fbdf5SAlan Somers 
930ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
931ff4fbdf5SAlan Somers {
932ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
933ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
934ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
935ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
936ff4fbdf5SAlan Somers 	uint64_t ino = 42;
937ff4fbdf5SAlan Somers 
938a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
939ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
940a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
941a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
942ff4fbdf5SAlan Somers 	expect_rename(0);
943ff4fbdf5SAlan Somers 
944ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
945ff4fbdf5SAlan Somers }
946ff4fbdf5SAlan Somers 
947ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
948ff4fbdf5SAlan Somers {
949ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
950ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
951ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
952ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
953ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
954ff4fbdf5SAlan Somers 
955a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
956ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
957ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
958ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
95929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
96029edc611SAlan Somers 				in.header.nodeid == ino &&
96129edc611SAlan Somers 				in.body.setattr.mode == newmode);
962ff4fbdf5SAlan Somers 		}, Eq(true)),
963ff4fbdf5SAlan Somers 		_)
96429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
965ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
96629edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
967ff4fbdf5SAlan Somers 	})));
968ff4fbdf5SAlan Somers 
969ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
970ff4fbdf5SAlan Somers }
971ff4fbdf5SAlan Somers 
972ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
973ff4fbdf5SAlan Somers {
974ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
975ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
976ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
977ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
978ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
979ff4fbdf5SAlan Somers 
980a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
981ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
982ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
983ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
98429edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
985ff4fbdf5SAlan Somers 		}, Eq(true)),
986ff4fbdf5SAlan Somers 		_)
987ff4fbdf5SAlan Somers 	).Times(0);
988ff4fbdf5SAlan Somers 
989ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
990ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
991ff4fbdf5SAlan Somers }
992ff4fbdf5SAlan Somers 
9938cfb4431SAlan Somers /*
9943fa12789SAlan Somers  * ftruncate() of a file without writable permissions should succeed as long as
9953fa12789SAlan Somers  * the file descriptor is writable.  This is important when combined with
9963fa12789SAlan Somers  * O_CREAT
9973fa12789SAlan Somers  */
9983fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file)
9993fa12789SAlan Somers {
10003fa12789SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
10013fa12789SAlan Somers 	const char RELPATH[] = "some_file.txt";
10023fa12789SAlan Somers 	const uint64_t ino = 42;
10033fa12789SAlan Somers 	const mode_t mode = 0000;
10043fa12789SAlan Somers 	int fd;
10053fa12789SAlan Somers 
1006a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1007a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1008a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
10093fa12789SAlan Somers 	expect_create(RELPATH, ino);
10103fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
10113fa12789SAlan Somers 		ResultOf([](auto in) {
101229edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
101329edc611SAlan Somers 				in.header.nodeid == ino &&
101429edc611SAlan Somers 				(in.body.setattr.valid & FATTR_SIZE));
10153fa12789SAlan Somers 		}, Eq(true)),
10163fa12789SAlan Somers 		_)
101729edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
10183fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
101929edc611SAlan Somers 		out.body.attr.attr.ino = ino;
102029edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
102129edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
10223fa12789SAlan Somers 	})));
10233fa12789SAlan Somers 
10243fa12789SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
10253fa12789SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
10263fa12789SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
1027*7fc0921dSAlan Somers 	leak(fd);
10283fa12789SAlan Somers }
10293fa12789SAlan Somers 
10303fa12789SAlan Somers /*
10318cfb4431SAlan Somers  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
10328cfb4431SAlan Somers  * to the file's group
10338cfb4431SAlan Somers  */
10348cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member)
10358cfb4431SAlan Somers {
10368cfb4431SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
10378cfb4431SAlan Somers 	const char RELPATH[] = "some_file.txt";
10388cfb4431SAlan Somers 	const uint64_t ino = 42;
10398cfb4431SAlan Somers 	const mode_t oldmode = 0755;
10408cfb4431SAlan Somers 	const mode_t newmode = 02755;
10418cfb4431SAlan Somers 	uid_t uid = geteuid();
10428cfb4431SAlan Somers 	gid_t gid = excluded_group();
10438cfb4431SAlan Somers 
1044a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
10458cfb4431SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
10468cfb4431SAlan Somers 	EXPECT_CALL(*m_mock, process(
10478cfb4431SAlan Somers 		ResultOf([](auto in) {
104829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
10498cfb4431SAlan Somers 		}, Eq(true)),
10508cfb4431SAlan Somers 		_)
10518cfb4431SAlan Somers 	).Times(0);
10528cfb4431SAlan Somers 
10538cfb4431SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
10548cfb4431SAlan Somers 	EXPECT_EQ(EPERM, errno);
10558cfb4431SAlan Somers }
10568cfb4431SAlan Somers 
1057e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
1058e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
1059e5ff3a7eSAlan Somers {
1060e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1061e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
1062e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
1063e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
1064e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
1065e5ff3a7eSAlan Somers 
1066a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1067e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1068e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
1069e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
107029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1071e5ff3a7eSAlan Somers 		}, Eq(true)),
1072e5ff3a7eSAlan Somers 		_)
1073e5ff3a7eSAlan Somers 	).Times(0);
1074e5ff3a7eSAlan Somers 
1075e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1076e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
1077e5ff3a7eSAlan Somers }
1078e5ff3a7eSAlan Somers 
1079ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
1080ff4fbdf5SAlan Somers {
1081ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1082ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1083ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1084ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1085ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1086ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1087ff4fbdf5SAlan Somers 	ssize_t r;
1088ff4fbdf5SAlan Somers 
1089a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1090ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1091ff4fbdf5SAlan Somers 	expect_setxattr(0);
1092ff4fbdf5SAlan Somers 
1093ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1094ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1095ff4fbdf5SAlan Somers }
1096ff4fbdf5SAlan Somers 
1097ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
1098ff4fbdf5SAlan Somers {
1099ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1100ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1101ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1102ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1103ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1104ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1105ff4fbdf5SAlan Somers 
1106a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1107ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1108ff4fbdf5SAlan Somers 
1109ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
1110ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1111ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1112ff4fbdf5SAlan Somers }
1113ff4fbdf5SAlan Somers 
1114ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
1115ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
1116ff4fbdf5SAlan Somers {
1117ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1118ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1119ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1120ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1121ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1122ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1123ff4fbdf5SAlan Somers 
1124a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1125ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1126ff4fbdf5SAlan Somers 
1127ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
1128ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1129ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1130ff4fbdf5SAlan Somers }
1131ff4fbdf5SAlan Somers 
1132ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
1133ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
1134ff4fbdf5SAlan Somers {
1135ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1136ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1137ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1138ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1139ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1140ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1141ff4fbdf5SAlan Somers 	ssize_t r;
1142ff4fbdf5SAlan Somers 
1143a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1144ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1145ff4fbdf5SAlan Somers 	expect_setxattr(0);
1146ff4fbdf5SAlan Somers 
1147ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1148ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1149ff4fbdf5SAlan Somers }
1150ff4fbdf5SAlan Somers 
1151ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
11529821f1d3SAlan Somers {
11539821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11549821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
11559821f1d3SAlan Somers 	uint64_t ino = 42;
11569821f1d3SAlan Somers 
1157a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1158ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1159a34cdd26SAlan Somers 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
11609821f1d3SAlan Somers 
1161ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1162ff4fbdf5SAlan Somers }
1163ff4fbdf5SAlan Somers 
11646124fd71SAlan Somers /*
11656124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
11666124fd71SAlan Somers  * in VOP_LOOKUP.
11676124fd71SAlan Somers  *
11686124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
11696124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
11706124fd71SAlan Somers  */
11716124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
11726124fd71SAlan Somers {
11736124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11746124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
11756124fd71SAlan Somers 	uint64_t ino = 42;
11766124fd71SAlan Somers 
1177a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1178a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
11796124fd71SAlan Somers 	.Times(AnyNumber())
11806124fd71SAlan Somers 	.WillRepeatedly(Invoke(
118129edc611SAlan Somers 		ReturnImmediate([=](auto i __unused, auto& out) {
11826124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
118329edc611SAlan Somers 			out.body.entry.attr.mode = S_IFREG | 0644;
118429edc611SAlan Somers 			out.body.entry.nodeid = ino;
118529edc611SAlan Somers 			out.body.entry.entry_valid = UINT64_MAX;
11866124fd71SAlan Somers 		}))
11876124fd71SAlan Somers 	);
11886124fd71SAlan Somers 
11896124fd71SAlan Somers 	/* Fill name cache */
11906124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
11916124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
11926124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
11936124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
11946124fd71SAlan Somers }
11956124fd71SAlan Somers 
1196ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
1197ff4fbdf5SAlan Somers {
1198ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1199ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1200ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1201ff4fbdf5SAlan Somers 
1202a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1203ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1204ff4fbdf5SAlan Somers 
1205ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1206ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1207ff4fbdf5SAlan Somers }
1208ff4fbdf5SAlan Somers 
12096124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
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 
1215a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1216ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1217ff4fbdf5SAlan Somers 
1218ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1219ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
12209821f1d3SAlan Somers }
1221a90e32deSAlan Somers 
1222a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
1223a90e32deSAlan Somers TEST_F(Write, clear_suid)
1224a90e32deSAlan Somers {
1225a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1226a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1227a90e32deSAlan Somers 	struct stat sb;
1228a90e32deSAlan Somers 	uint64_t ino = 42;
1229a90e32deSAlan Somers 	mode_t oldmode = 04777;
1230a90e32deSAlan Somers 	mode_t newmode = 0777;
1231a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1232a90e32deSAlan Somers 	int fd;
1233a90e32deSAlan Somers 
1234a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1235a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1236a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1237bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
123818a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1239a90e32deSAlan Somers 
1240a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1241a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1242a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1243a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1244a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1245*7fc0921dSAlan Somers 	leak(fd);
1246a90e32deSAlan Somers }
1247a90e32deSAlan Somers 
1248a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
1249a90e32deSAlan Somers TEST_F(Write, clear_sgid)
1250a90e32deSAlan Somers {
1251a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1252a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1253a90e32deSAlan Somers 	struct stat sb;
1254a90e32deSAlan Somers 	uint64_t ino = 42;
1255a90e32deSAlan Somers 	mode_t oldmode = 02777;
1256a90e32deSAlan Somers 	mode_t newmode = 0777;
1257a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1258a90e32deSAlan Somers 	int fd;
1259a90e32deSAlan Somers 
1260a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1261a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1262a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1263bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
126418a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1265a90e32deSAlan Somers 
1266a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1267a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1268a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1269a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1270a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1271*7fc0921dSAlan Somers 	leak(fd);
1272a90e32deSAlan Somers }
127318a2264eSAlan Somers 
127418a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic
127518a2264eSAlan Somers  *
127618a2264eSAlan Somers  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
127718a2264eSAlan Somers  * may panic.  That happens if the FUSE_SETATTR response indicates that the
127818a2264eSAlan Somers  * file's size has changed since the write.
127918a2264eSAlan Somers  */
128018a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid)
128118a2264eSAlan Somers {
128218a2264eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
128318a2264eSAlan Somers 	const char RELPATH[] = "some_file.txt";
128418a2264eSAlan Somers 	uint64_t ino = 42;
128518a2264eSAlan Somers 	mode_t oldmode = 04777;
128618a2264eSAlan Somers 	mode_t newmode = 0777;
128718a2264eSAlan Somers 	char wbuf[1] = {'x'};
128818a2264eSAlan Somers 	int fd;
128918a2264eSAlan Somers 
1290a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
129118a2264eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
129218a2264eSAlan Somers 	expect_open(ino, 0, 1);
1293bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
129418a2264eSAlan Somers 	/* XXX Return a smaller file size than what we just wrote! */
129518a2264eSAlan Somers 	expect_chmod(ino, newmode, 0);
129618a2264eSAlan Somers 
129718a2264eSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
129818a2264eSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
129918a2264eSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1300*7fc0921dSAlan Somers 	leak(fd);
130118a2264eSAlan Somers }
130218a2264eSAlan Somers 
130318a2264eSAlan Somers 
1304