xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 18a2264e27f6220cbd50f1692d64736f3bf5adfa)
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) {
64ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_ACCESS);
65ff4fbdf5SAlan Somers 		}, Eq(true)),
66ff4fbdf5SAlan Somers 		_)
67ff4fbdf5SAlan Somers 	).Times(0);
689821f1d3SAlan Somers }
699821f1d3SAlan Somers 
709821f1d3SAlan Somers public:
71*18a2264eSAlan 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) {
75a90e32deSAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
76a90e32deSAlan Somers 				in->header.nodeid == ino &&
77a90e32deSAlan Somers 				in->body.setattr.valid == FATTR_MODE &&
78a90e32deSAlan Somers 				in->body.setattr.mode == mode);
79a90e32deSAlan Somers 		}, Eq(true)),
80a90e32deSAlan Somers 		_)
81a90e32deSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
82a90e32deSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
83a90e32deSAlan Somers 		out->body.attr.attr.ino = ino;	// Must match nodeid
84a90e32deSAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
85*18a2264eSAlan Somers 		out->body.attr.attr.size = size;
86a90e32deSAlan 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) {
943fa12789SAlan Somers 			const char *name = (const char*)in->body.bytes +
953fa12789SAlan Somers 				sizeof(fuse_open_in);
963fa12789SAlan Somers 			return (in->header.opcode == FUSE_CREATE &&
973fa12789SAlan Somers 				(0 == strcmp(relpath, name)));
983fa12789SAlan Somers 		}, Eq(true)),
993fa12789SAlan Somers 		_)
1003fa12789SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
1013fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
1023fa12789SAlan Somers 		out->body.create.entry.attr.mode = S_IFREG | 0644;
1033fa12789SAlan Somers 		out->body.create.entry.nodeid = ino;
1043fa12789SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
1053fa12789SAlan 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) {
114ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_GETATTR &&
115ff4fbdf5SAlan Somers 				in->header.nodeid == ino);
116ff4fbdf5SAlan Somers 		}, Eq(true)),
117ff4fbdf5SAlan Somers 		_)
118ff4fbdf5SAlan Somers 	).Times(times)
119ff4fbdf5SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
120ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
121ff4fbdf5SAlan Somers 		out->body.attr.attr.ino = ino;	// Must match nodeid
122ff4fbdf5SAlan Somers 		out->body.attr.attr.mode = mode;
123ff4fbdf5SAlan Somers 		out->body.attr.attr.size = 0;
124ff4fbdf5SAlan Somers 		out->body.attr.attr.uid = uid;
125474ba6faSAlan Somers 		out->body.attr.attr.uid = gid;
126ff4fbdf5SAlan 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) {
161ff4fbdf5SAlan 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) {
174ff4fbdf5SAlan 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) {
187ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_LISTXATTR);
188ff4fbdf5SAlan Somers 		}, Eq(true)),
189ff4fbdf5SAlan Somers 		_)
190ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) {
191ff4fbdf5SAlan 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) {
207ff4fbdf5SAlan 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) {
220ff4fbdf5SAlan 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 
255ff4fbdf5SAlan Somers 	expect_getattr(1, 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 
269ff4fbdf5SAlan Somers 	expect_getattr(1, 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 
289ff4fbdf5SAlan Somers 	expect_getattr(1, 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 
3104e83d655SAlan Somers 	expect_getattr(1, 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) {
3154e83d655SAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
3164e83d655SAlan Somers 		}, Eq(true)),
3174e83d655SAlan Somers 		_)
3184e83d655SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out){
3194e83d655SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
3204e83d655SAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
3214e83d655SAlan 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 
341a2bdd737SAlan Somers 	expect_getattr(1, 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) {
345a2bdd737SAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
346a2bdd737SAlan Somers 				in->header.nodeid == ino &&
347a2bdd737SAlan Somers 				in->body.setattr.valid == valid &&
348a2bdd737SAlan Somers 				in->body.setattr.mode == newmode);
349a2bdd737SAlan Somers 		}, Eq(true)),
350a2bdd737SAlan Somers 		_)
351a2bdd737SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
352a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
353a2bdd737SAlan Somers 		out->body.attr.attr.ino = ino;	// Must match nodeid
354a2bdd737SAlan Somers 		out->body.attr.attr.mode = S_IFREG | newmode;
355a2bdd737SAlan 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 
370474ba6faSAlan Somers 	expect_getattr(1, 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) {
374474ba6faSAlan 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 
398a2bdd737SAlan Somers 	expect_getattr(1, 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) {
402a2bdd737SAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
403a2bdd737SAlan Somers 				in->header.nodeid == ino &&
404a2bdd737SAlan Somers 				in->body.setattr.valid == valid &&
405a2bdd737SAlan Somers 				in->body.setattr.mode == newmode);
406a2bdd737SAlan Somers 		}, Eq(true)),
407a2bdd737SAlan Somers 		_)
408a2bdd737SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
409a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
410a2bdd737SAlan Somers 		out->body.attr.attr.ino = ino;	// Must match nodeid
411a2bdd737SAlan Somers 		out->body.attr.attr.mode = S_IFREG | newmode;
412a2bdd737SAlan 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 
432474ba6faSAlan Somers 	expect_getattr(1, 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) {
436474ba6faSAlan 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 
458474ba6faSAlan Somers 	expect_getattr(1, 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) {
463474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
464474ba6faSAlan Somers 				in->header.nodeid == ino);
465474ba6faSAlan Somers 		}, Eq(true)),
466474ba6faSAlan Somers 		_)
4674e83d655SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out){
468474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
469474ba6faSAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
470474ba6faSAlan Somers 		out->body.attr.attr.uid = uid;
471474ba6faSAlan 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 
484ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
485ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
486ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
487ff4fbdf5SAlan Somers 
488ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
489ff4fbdf5SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
490ff4fbdf5SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
491ff4fbdf5SAlan Somers }
492ff4fbdf5SAlan Somers 
493ff4fbdf5SAlan Somers TEST_F(Create, eacces)
494ff4fbdf5SAlan Somers {
495ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
496ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
497ff4fbdf5SAlan Somers 
498ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
499ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
500ff4fbdf5SAlan Somers 
501ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
502ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
503ff4fbdf5SAlan Somers }
504ff4fbdf5SAlan Somers 
505ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
506ff4fbdf5SAlan Somers {
507ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
508ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
509ff4fbdf5SAlan Somers 	uint64_t ino = 42;
510ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
511ff4fbdf5SAlan Somers 
512ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
513ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
514ff4fbdf5SAlan Somers 
515ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
516ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
517ff4fbdf5SAlan Somers }
518ff4fbdf5SAlan Somers 
519ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
520ff4fbdf5SAlan Somers {
521ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
522ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
523ff4fbdf5SAlan Somers 	uint64_t ino = 42;
524ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
525ff4fbdf5SAlan Somers 
526ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
527ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
528ff4fbdf5SAlan Somers 	expect_removexattr();
529ff4fbdf5SAlan Somers 
530ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
531ff4fbdf5SAlan Somers 		<< strerror(errno);
532ff4fbdf5SAlan Somers }
533ff4fbdf5SAlan Somers 
534ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
535ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
536ff4fbdf5SAlan Somers {
537ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
538ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
539ff4fbdf5SAlan Somers 	uint64_t ino = 42;
540ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
541ff4fbdf5SAlan Somers 
542ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
543ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
544ff4fbdf5SAlan Somers 
545ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
546ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
547ff4fbdf5SAlan Somers }
548ff4fbdf5SAlan Somers 
549d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */
550d943c93eSAlan Somers TEST_F(Utimensat, utime_now)
551d943c93eSAlan Somers {
552d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
553d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
554d943c93eSAlan Somers 	const uint64_t ino = 42;
555d943c93eSAlan Somers 	/* Write permissions for everybody */
556d943c93eSAlan Somers 	const mode_t mode = 0666;
557d943c93eSAlan Somers 	uid_t owner = 0;
558d943c93eSAlan Somers 	const timespec times[2] = {
559d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
560d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
561d943c93eSAlan Somers 	};
562d943c93eSAlan Somers 
563d943c93eSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
564d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
565d943c93eSAlan Somers 	EXPECT_CALL(*m_mock, process(
566d943c93eSAlan Somers 		ResultOf([](auto in) {
567d943c93eSAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
568d943c93eSAlan Somers 				in->header.nodeid == ino &&
569d943c93eSAlan Somers 				in->body.setattr.valid & FATTR_ATIME &&
570d943c93eSAlan Somers 				in->body.setattr.valid & FATTR_MTIME);
571d943c93eSAlan Somers 		}, Eq(true)),
572d943c93eSAlan Somers 		_)
573d943c93eSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
574d943c93eSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
575d943c93eSAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
576d943c93eSAlan Somers 	})));
577d943c93eSAlan Somers 
578d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
579d943c93eSAlan Somers 		<< strerror(errno);
580d943c93eSAlan Somers }
581d943c93eSAlan Somers 
582d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */
583d943c93eSAlan Somers TEST_F(Utimensat, utime_omit)
584d943c93eSAlan Somers {
585d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
586d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
587d943c93eSAlan Somers 	const uint64_t ino = 42;
588d943c93eSAlan Somers 	/* Write permissions for no one */
589d943c93eSAlan Somers 	const mode_t mode = 0444;
590d943c93eSAlan Somers 	uid_t owner = 0;
591d943c93eSAlan Somers 	const timespec times[2] = {
592d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
593d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
594d943c93eSAlan Somers 	};
595d943c93eSAlan Somers 
596d943c93eSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
597d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
598d943c93eSAlan Somers 
599d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
600d943c93eSAlan Somers 		<< strerror(errno);
601d943c93eSAlan Somers }
602d943c93eSAlan Somers 
603ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
604ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
605ff4fbdf5SAlan Somers {
606ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
607ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
608ff4fbdf5SAlan Somers 	uint64_t ino = 42;
609ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
610ff4fbdf5SAlan Somers 
611ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
612ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
613ff4fbdf5SAlan Somers 	expect_removexattr();
614ff4fbdf5SAlan Somers 
615ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
616ff4fbdf5SAlan Somers 		<< strerror(errno);
617ff4fbdf5SAlan Somers }
618ff4fbdf5SAlan Somers 
619ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
620ff4fbdf5SAlan Somers {
621ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
622ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
623ff4fbdf5SAlan Somers 	uint64_t ino = 42;
624ff4fbdf5SAlan Somers 	char data[80];
625ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
626ff4fbdf5SAlan Somers 
627ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
628ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
629ff4fbdf5SAlan Somers 
630ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
631ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
632ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
633ff4fbdf5SAlan Somers }
634ff4fbdf5SAlan Somers 
635ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
636ff4fbdf5SAlan Somers {
637ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
638ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
639ff4fbdf5SAlan Somers 	uint64_t ino = 42;
640ff4fbdf5SAlan Somers 	char data[80];
641ff4fbdf5SAlan Somers 	const char value[] = "whatever";
642ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
643ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
644ff4fbdf5SAlan Somers 	ssize_t r;
645ff4fbdf5SAlan Somers 
646ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
647ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
648ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
649ff4fbdf5SAlan Somers 	expect_getxattr(
650ff4fbdf5SAlan Somers 		ReturnImmediate([&](auto in __unused, auto out) {
651ff4fbdf5SAlan Somers 			memcpy((void*)out->body.bytes, value, value_len);
652ff4fbdf5SAlan Somers 			out->header.len = sizeof(out->header) + value_len;
653ff4fbdf5SAlan Somers 		})
654ff4fbdf5SAlan Somers 	);
655ff4fbdf5SAlan Somers 
656ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
657ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
658ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
659ff4fbdf5SAlan Somers }
660ff4fbdf5SAlan Somers 
661ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
662ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
663ff4fbdf5SAlan Somers {
664ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
665ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
666ff4fbdf5SAlan Somers 	uint64_t ino = 42;
667ff4fbdf5SAlan Somers 	char data[80];
668ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
669ff4fbdf5SAlan Somers 
670ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
671ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
672ff4fbdf5SAlan Somers 
673ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
674ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
675ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
676ff4fbdf5SAlan Somers }
677ff4fbdf5SAlan Somers 
678ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
679ff4fbdf5SAlan Somers {
680ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
681ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
682ff4fbdf5SAlan Somers 	uint64_t ino = 42;
683ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
684ff4fbdf5SAlan Somers 
685ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
686ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
687ff4fbdf5SAlan Somers 
688ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
689ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
690ff4fbdf5SAlan Somers }
691ff4fbdf5SAlan Somers 
692ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
693ff4fbdf5SAlan Somers {
694ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
695ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
696ff4fbdf5SAlan Somers 	uint64_t ino = 42;
697ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
698ff4fbdf5SAlan Somers 
699ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
700ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
701ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
702ff4fbdf5SAlan Somers 	expect_listxattr();
703ff4fbdf5SAlan Somers 
704ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
705ff4fbdf5SAlan Somers 		<< strerror(errno);
706ff4fbdf5SAlan Somers }
707ff4fbdf5SAlan Somers 
708ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
709ff4fbdf5SAlan Somers TEST_F(Listextattr, system)
710ff4fbdf5SAlan Somers {
711ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
712ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
713ff4fbdf5SAlan Somers 	uint64_t ino = 42;
714ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
715ff4fbdf5SAlan Somers 
716ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
717ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
718ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
719ff4fbdf5SAlan Somers 
720ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
721ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
722ff4fbdf5SAlan Somers }
723ff4fbdf5SAlan Somers 
724ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
725ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
726ff4fbdf5SAlan Somers {
727ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
728ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
729ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
730ff4fbdf5SAlan Somers 
731ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
732ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
733ff4fbdf5SAlan Somers 
734ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
735ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
736ff4fbdf5SAlan Somers }
737ff4fbdf5SAlan Somers 
738ff4fbdf5SAlan Somers TEST_F(Open, eacces)
739ff4fbdf5SAlan Somers {
740ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
741ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
742ff4fbdf5SAlan Somers 	uint64_t ino = 42;
743ff4fbdf5SAlan Somers 
744ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
745ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
746ff4fbdf5SAlan Somers 
747ff4fbdf5SAlan Somers 	EXPECT_NE(0, open(FULLPATH, O_RDWR));
748ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
749ff4fbdf5SAlan Somers }
750ff4fbdf5SAlan Somers 
7519821f1d3SAlan Somers TEST_F(Open, ok)
7529821f1d3SAlan Somers {
7539821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
7549821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
7559821f1d3SAlan Somers 	uint64_t ino = 42;
7569821f1d3SAlan Somers 	int fd;
7579821f1d3SAlan Somers 
758ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
759ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
7609821f1d3SAlan Somers 	expect_open(ino, 0, 1);
7619821f1d3SAlan Somers 
7629821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
7639821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
7649821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
7659821f1d3SAlan Somers }
7669821f1d3SAlan Somers 
767ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
768ff4fbdf5SAlan Somers {
769ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
770ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
771ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
772ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
773ff4fbdf5SAlan Somers 	uint64_t ino = 42;
774ff4fbdf5SAlan Somers 
775ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
776ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
777ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELDST)
778ff4fbdf5SAlan Somers 		.Times(AnyNumber())
779ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
780ff4fbdf5SAlan Somers 
781ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
782ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
783ff4fbdf5SAlan Somers }
784ff4fbdf5SAlan Somers 
785ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
786ff4fbdf5SAlan Somers {
787ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
788ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
789ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
790ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
791ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
792ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
793ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
794ff4fbdf5SAlan Somers 
795ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
796ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
797ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
798ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
799ff4fbdf5SAlan Somers 
800ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
801ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
802ff4fbdf5SAlan Somers }
803ff4fbdf5SAlan Somers 
804ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
805ff4fbdf5SAlan Somers {
806ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
807ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
808ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
809ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
810ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
811ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
812ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
813ff4fbdf5SAlan Somers 
814ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
815ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
816ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
817ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
818ff4fbdf5SAlan Somers 
819ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
820ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
821ff4fbdf5SAlan Somers }
822ff4fbdf5SAlan Somers 
8236124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
824ff4fbdf5SAlan Somers {
825ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
826ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
827ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
828ff4fbdf5SAlan Somers 	uint64_t ino = 42;
829ff4fbdf5SAlan Somers 
830ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
831ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
832ff4fbdf5SAlan Somers 
833ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
834ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
835ff4fbdf5SAlan Somers }
836ff4fbdf5SAlan Somers 
8378e45ec4eSAlan Somers /*
8388e45ec4eSAlan Somers  * A user cannot move out a subdirectory that he does not own, because that
8398e45ec4eSAlan Somers  * would require changing the subdirectory's ".." dirent
8408e45ec4eSAlan Somers  */
8418e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory)
8428e45ec4eSAlan Somers {
8438e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
8448e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
8458e45ec4eSAlan Somers 	const char RELDSTDIR[] = "d";
8468e45ec4eSAlan Somers 	const char RELDST[] = "dst";
8478e45ec4eSAlan Somers 	const char RELSRC[] = "src";
8488e45ec4eSAlan Somers 	uint64_t ino = 42;
8498e45ec4eSAlan Somers 	uint64_t dstdir_ino = 43;
8508e45ec4eSAlan Somers 
8518e45ec4eSAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
8528e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
8538e45ec4eSAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
8548e45ec4eSAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
8558e45ec4eSAlan Somers 
8568e45ec4eSAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
8578e45ec4eSAlan Somers 	ASSERT_EQ(EACCES, errno);
8588e45ec4eSAlan Somers }
8598e45ec4eSAlan Somers 
8608e45ec4eSAlan Somers /*
8618e45ec4eSAlan Somers  * A user _can_ rename a subdirectory to which he lacks write permissions, if
8628e45ec4eSAlan Somers  * it will keep the same parent
8638e45ec4eSAlan Somers  */
8648e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir)
8658e45ec4eSAlan Somers {
8668e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/dst";
8678e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
8688e45ec4eSAlan Somers 	const char RELDST[] = "dst";
8698e45ec4eSAlan Somers 	const char RELSRC[] = "src";
8708e45ec4eSAlan Somers 	uint64_t ino = 42;
8718e45ec4eSAlan Somers 
8728e45ec4eSAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
8738e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
8748e45ec4eSAlan Somers 	EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
8758e45ec4eSAlan Somers 	expect_rename(0);
8768e45ec4eSAlan Somers 
8778e45ec4eSAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
8788e45ec4eSAlan Somers }
8798e45ec4eSAlan Somers 
8806124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
881ff4fbdf5SAlan Somers {
882ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
883ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
8846124fd71SAlan Somers 	const char RELDST[] = "dst";
885ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
886ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
887ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
888ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
889ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
890ff4fbdf5SAlan Somers 
891ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
892ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
893ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
8946124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
8956124fd71SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
8966124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
8976124fd71SAlan Somers 		out->body.entry.attr.mode = S_IFREG | 0644;
8986124fd71SAlan Somers 		out->body.entry.nodeid = dst_ino;
8996124fd71SAlan Somers 		out->body.entry.attr_valid = UINT64_MAX;
9006124fd71SAlan Somers 		out->body.entry.entry_valid = UINT64_MAX;
9016124fd71SAlan Somers 		out->body.entry.attr.uid = 0;
9026124fd71SAlan Somers 	})));
903ff4fbdf5SAlan Somers 
904ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
905ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
906ff4fbdf5SAlan Somers }
907ff4fbdf5SAlan Somers 
908ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
909ff4fbdf5SAlan Somers TEST_F(Rename, ok)
910ff4fbdf5SAlan Somers {
911ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
912ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
913ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
914ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
915ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
916ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
917ff4fbdf5SAlan Somers 	uint64_t ino = 42;
918ff4fbdf5SAlan Somers 
919ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
920ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
921ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
922ff4fbdf5SAlan Somers 	expect_rename(0);
923ff4fbdf5SAlan Somers 
924ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
925ff4fbdf5SAlan Somers }
926ff4fbdf5SAlan Somers 
927ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
928ff4fbdf5SAlan Somers {
929ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
930ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
931ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
932ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
933ff4fbdf5SAlan Somers 	uint64_t ino = 42;
934ff4fbdf5SAlan Somers 
935ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
936ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
937ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
938ff4fbdf5SAlan Somers 	expect_rename(0);
939ff4fbdf5SAlan Somers 
940ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
941ff4fbdf5SAlan Somers }
942ff4fbdf5SAlan Somers 
943ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
944ff4fbdf5SAlan Somers {
945ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
946ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
947ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
948ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
949ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
950ff4fbdf5SAlan Somers 
951ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
952ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
953ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
954ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
955ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
956ff4fbdf5SAlan Somers 				in->header.nodeid == ino &&
957ff4fbdf5SAlan Somers 				in->body.setattr.mode == newmode);
958ff4fbdf5SAlan Somers 		}, Eq(true)),
959ff4fbdf5SAlan Somers 		_)
960ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
961ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
962ff4fbdf5SAlan Somers 		out->body.attr.attr.mode = S_IFREG | newmode;
963ff4fbdf5SAlan Somers 	})));
964ff4fbdf5SAlan Somers 
965ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
966ff4fbdf5SAlan Somers }
967ff4fbdf5SAlan Somers 
968ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
969ff4fbdf5SAlan Somers {
970ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
971ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
972ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
973ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
974ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
975ff4fbdf5SAlan Somers 
976ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
977ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
978ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
979ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
980ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
981ff4fbdf5SAlan Somers 		}, Eq(true)),
982ff4fbdf5SAlan Somers 		_)
983ff4fbdf5SAlan Somers 	).Times(0);
984ff4fbdf5SAlan Somers 
985ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
986ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
987ff4fbdf5SAlan Somers }
988ff4fbdf5SAlan Somers 
9898cfb4431SAlan Somers /*
9903fa12789SAlan Somers  * ftruncate() of a file without writable permissions should succeed as long as
9913fa12789SAlan Somers  * the file descriptor is writable.  This is important when combined with
9923fa12789SAlan Somers  * O_CREAT
9933fa12789SAlan Somers  */
9943fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file)
9953fa12789SAlan Somers {
9963fa12789SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
9973fa12789SAlan Somers 	const char RELPATH[] = "some_file.txt";
9983fa12789SAlan Somers 	const uint64_t ino = 42;
9993fa12789SAlan Somers 	const mode_t mode = 0000;
10003fa12789SAlan Somers 	int fd;
10013fa12789SAlan Somers 
10023fa12789SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
10033fa12789SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
10043fa12789SAlan Somers 	expect_create(RELPATH, ino);
10053fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
10063fa12789SAlan Somers 		ResultOf([](auto in) {
10073fa12789SAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
10083fa12789SAlan Somers 				in->header.nodeid == ino &&
10093fa12789SAlan Somers 				(in->body.setattr.valid & FATTR_SIZE));
10103fa12789SAlan Somers 		}, Eq(true)),
10113fa12789SAlan Somers 		_)
10123fa12789SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
10133fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
10143fa12789SAlan Somers 		out->body.attr.attr.ino = ino;
10153fa12789SAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
10163fa12789SAlan Somers 		out->body.attr.attr_valid = UINT64_MAX;
10173fa12789SAlan Somers 	})));
10183fa12789SAlan Somers 
10193fa12789SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
10203fa12789SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
10213fa12789SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
10223fa12789SAlan Somers 	/* Deliberately leak fd */
10233fa12789SAlan Somers }
10243fa12789SAlan Somers 
10253fa12789SAlan Somers /*
10268cfb4431SAlan Somers  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
10278cfb4431SAlan Somers  * to the file's group
10288cfb4431SAlan Somers  */
10298cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member)
10308cfb4431SAlan Somers {
10318cfb4431SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
10328cfb4431SAlan Somers 	const char RELPATH[] = "some_file.txt";
10338cfb4431SAlan Somers 	const uint64_t ino = 42;
10348cfb4431SAlan Somers 	const mode_t oldmode = 0755;
10358cfb4431SAlan Somers 	const mode_t newmode = 02755;
10368cfb4431SAlan Somers 	uid_t uid = geteuid();
10378cfb4431SAlan Somers 	gid_t gid = excluded_group();
10388cfb4431SAlan Somers 
10398cfb4431SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
10408cfb4431SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
10418cfb4431SAlan Somers 	EXPECT_CALL(*m_mock, process(
10428cfb4431SAlan Somers 		ResultOf([](auto in) {
10438cfb4431SAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
10448cfb4431SAlan Somers 		}, Eq(true)),
10458cfb4431SAlan Somers 		_)
10468cfb4431SAlan Somers 	).Times(0);
10478cfb4431SAlan Somers 
10488cfb4431SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
10498cfb4431SAlan Somers 	EXPECT_EQ(EPERM, errno);
10508cfb4431SAlan Somers }
10518cfb4431SAlan Somers 
1052e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
1053e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
1054e5ff3a7eSAlan Somers {
1055e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1056e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
1057e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
1058e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
1059e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
1060e5ff3a7eSAlan Somers 
1061e5ff3a7eSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1062e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1063e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
1064e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
1065e5ff3a7eSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
1066e5ff3a7eSAlan Somers 		}, Eq(true)),
1067e5ff3a7eSAlan Somers 		_)
1068e5ff3a7eSAlan Somers 	).Times(0);
1069e5ff3a7eSAlan Somers 
1070e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1071e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
1072e5ff3a7eSAlan Somers }
1073e5ff3a7eSAlan Somers 
1074ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
1075ff4fbdf5SAlan Somers {
1076ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1077ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1078ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1079ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1080ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1081ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1082ff4fbdf5SAlan Somers 	ssize_t r;
1083ff4fbdf5SAlan Somers 
1084ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1085ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1086ff4fbdf5SAlan Somers 	expect_setxattr(0);
1087ff4fbdf5SAlan Somers 
1088ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1089ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1090ff4fbdf5SAlan Somers }
1091ff4fbdf5SAlan Somers 
1092ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
1093ff4fbdf5SAlan Somers {
1094ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1095ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1096ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1097ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1098ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1099ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1100ff4fbdf5SAlan Somers 
1101ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1102ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1103ff4fbdf5SAlan Somers 
1104ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
1105ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1106ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1107ff4fbdf5SAlan Somers }
1108ff4fbdf5SAlan Somers 
1109ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
1110ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
1111ff4fbdf5SAlan Somers {
1112ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1113ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1114ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1115ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1116ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1117ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1118ff4fbdf5SAlan Somers 
1119ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1120ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1121ff4fbdf5SAlan Somers 
1122ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
1123ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1124ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1125ff4fbdf5SAlan Somers }
1126ff4fbdf5SAlan Somers 
1127ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
1128ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
1129ff4fbdf5SAlan Somers {
1130ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1131ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1132ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1133ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1134ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1135ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1136ff4fbdf5SAlan Somers 	ssize_t r;
1137ff4fbdf5SAlan Somers 
1138ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1139ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1140ff4fbdf5SAlan Somers 	expect_setxattr(0);
1141ff4fbdf5SAlan Somers 
1142ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1143ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1144ff4fbdf5SAlan Somers }
1145ff4fbdf5SAlan Somers 
1146ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
11479821f1d3SAlan Somers {
11489821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11499821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
11509821f1d3SAlan Somers 	uint64_t ino = 42;
11519821f1d3SAlan Somers 
1152ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
1153ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1154ff4fbdf5SAlan Somers 	expect_unlink(1, RELPATH, 0);
11559821f1d3SAlan Somers 
1156ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1157ff4fbdf5SAlan Somers }
1158ff4fbdf5SAlan Somers 
11596124fd71SAlan Somers /*
11606124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
11616124fd71SAlan Somers  * in VOP_LOOKUP.
11626124fd71SAlan Somers  *
11636124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
11646124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
11656124fd71SAlan Somers  */
11666124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
11676124fd71SAlan Somers {
11686124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
11696124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
11706124fd71SAlan Somers 	uint64_t ino = 42;
11716124fd71SAlan Somers 
11726124fd71SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
11736124fd71SAlan Somers 	EXPECT_LOOKUP(1, RELPATH)
11746124fd71SAlan Somers 	.Times(AnyNumber())
11756124fd71SAlan Somers 	.WillRepeatedly(Invoke(
11766124fd71SAlan Somers 		ReturnImmediate([=](auto i __unused, auto out) {
11776124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
11786124fd71SAlan Somers 			out->body.entry.attr.mode = S_IFREG | 0644;
11796124fd71SAlan Somers 			out->body.entry.nodeid = ino;
11806124fd71SAlan Somers 			out->body.entry.entry_valid = UINT64_MAX;
11816124fd71SAlan Somers 		}))
11826124fd71SAlan Somers 	);
11836124fd71SAlan Somers 
11846124fd71SAlan Somers 	/* Fill name cache */
11856124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
11866124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
11876124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
11886124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
11896124fd71SAlan Somers }
11906124fd71SAlan Somers 
1191ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
1192ff4fbdf5SAlan Somers {
1193ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1194ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1195ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1196ff4fbdf5SAlan Somers 
1197ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1198ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1199ff4fbdf5SAlan Somers 
1200ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1201ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1202ff4fbdf5SAlan Somers }
1203ff4fbdf5SAlan Somers 
12046124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
1205ff4fbdf5SAlan Somers {
1206ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1207ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1208ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1209ff4fbdf5SAlan Somers 
1210ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
1211ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1212ff4fbdf5SAlan Somers 
1213ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1214ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
12159821f1d3SAlan Somers }
1216a90e32deSAlan Somers 
1217a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
1218a90e32deSAlan Somers TEST_F(Write, clear_suid)
1219a90e32deSAlan Somers {
1220a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1221a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1222a90e32deSAlan Somers 	struct stat sb;
1223a90e32deSAlan Somers 	uint64_t ino = 42;
1224a90e32deSAlan Somers 	mode_t oldmode = 04777;
1225a90e32deSAlan Somers 	mode_t newmode = 0777;
1226a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1227a90e32deSAlan Somers 	int fd;
1228a90e32deSAlan Somers 
1229a90e32deSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1230a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1231a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1232a90e32deSAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1233*18a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1234a90e32deSAlan Somers 
1235a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1236a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1237a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1238a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1239a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1240a90e32deSAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1241a90e32deSAlan Somers }
1242a90e32deSAlan Somers 
1243a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
1244a90e32deSAlan Somers TEST_F(Write, clear_sgid)
1245a90e32deSAlan Somers {
1246a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1247a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1248a90e32deSAlan Somers 	struct stat sb;
1249a90e32deSAlan Somers 	uint64_t ino = 42;
1250a90e32deSAlan Somers 	mode_t oldmode = 02777;
1251a90e32deSAlan Somers 	mode_t newmode = 0777;
1252a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1253a90e32deSAlan Somers 	int fd;
1254a90e32deSAlan Somers 
1255a90e32deSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1256a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1257a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1258a90e32deSAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1259*18a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1260a90e32deSAlan Somers 
1261a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1262a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1263a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1264a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1265a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1266a90e32deSAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1267a90e32deSAlan Somers }
1268*18a2264eSAlan Somers 
1269*18a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic
1270*18a2264eSAlan Somers  *
1271*18a2264eSAlan Somers  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
1272*18a2264eSAlan Somers  * may panic.  That happens if the FUSE_SETATTR response indicates that the
1273*18a2264eSAlan Somers  * file's size has changed since the write.
1274*18a2264eSAlan Somers  */
1275*18a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid)
1276*18a2264eSAlan Somers {
1277*18a2264eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1278*18a2264eSAlan Somers 	const char RELPATH[] = "some_file.txt";
1279*18a2264eSAlan Somers 	uint64_t ino = 42;
1280*18a2264eSAlan Somers 	mode_t oldmode = 04777;
1281*18a2264eSAlan Somers 	mode_t newmode = 0777;
1282*18a2264eSAlan Somers 	char wbuf[1] = {'x'};
1283*18a2264eSAlan Somers 	int fd;
1284*18a2264eSAlan Somers 
1285*18a2264eSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1286*18a2264eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1287*18a2264eSAlan Somers 	expect_open(ino, 0, 1);
1288*18a2264eSAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1289*18a2264eSAlan Somers 	/* XXX Return a smaller file size than what we just wrote! */
1290*18a2264eSAlan Somers 	expect_chmod(ino, newmode, 0);
1291*18a2264eSAlan Somers 
1292*18a2264eSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1293*18a2264eSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1294*18a2264eSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1295*18a2264eSAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1296*18a2264eSAlan Somers }
1297*18a2264eSAlan Somers 
1298*18a2264eSAlan Somers 
1299