xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 474ba6fa3b81b13b183a47f8591e83fa8ba499f4)
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:
71ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
72*474ba6faSAlan Somers 	uid_t uid = 0, gid_t gid = 0)
739821f1d3SAlan Somers {
74ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
75ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
76ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_GETATTR &&
77ff4fbdf5SAlan Somers 				in->header.nodeid == ino);
78ff4fbdf5SAlan Somers 		}, Eq(true)),
79ff4fbdf5SAlan Somers 		_)
80ff4fbdf5SAlan Somers 	).Times(times)
81ff4fbdf5SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
82ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
83ff4fbdf5SAlan Somers 		out->body.attr.attr.ino = ino;	// Must match nodeid
84ff4fbdf5SAlan Somers 		out->body.attr.attr.mode = mode;
85ff4fbdf5SAlan Somers 		out->body.attr.attr.size = 0;
86ff4fbdf5SAlan Somers 		out->body.attr.attr.uid = uid;
87*474ba6faSAlan Somers 		out->body.attr.attr.uid = gid;
88ff4fbdf5SAlan Somers 		out->body.attr.attr_valid = attr_valid;
89ff4fbdf5SAlan Somers 	})));
90ff4fbdf5SAlan Somers }
91ff4fbdf5SAlan Somers 
92ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
93*474ba6faSAlan Somers 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
94ff4fbdf5SAlan Somers {
95*474ba6faSAlan Somers 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
969821f1d3SAlan Somers }
979821f1d3SAlan Somers 
989821f1d3SAlan Somers };
999821f1d3SAlan Somers 
1009821f1d3SAlan Somers class Access: public DefaultPermissions {};
101*474ba6faSAlan Somers class Chown: public DefaultPermissions {};
102*474ba6faSAlan Somers class Chgrp: public DefaultPermissions {};
103ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1049821f1d3SAlan Somers class Open: public DefaultPermissions {};
105ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {};
106ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {};
1079821f1d3SAlan Somers 
108ff4fbdf5SAlan Somers /*
109ff4fbdf5SAlan Somers  * Test permission handling during create, mkdir, mknod, link, symlink, and
110ff4fbdf5SAlan Somers  * rename vops (they all share a common path for permission checks in
111ff4fbdf5SAlan Somers  * VOP_LOOKUP)
112ff4fbdf5SAlan Somers  */
113ff4fbdf5SAlan Somers class Create: public DefaultPermissions {
114ff4fbdf5SAlan Somers public:
115ff4fbdf5SAlan Somers 
116ff4fbdf5SAlan Somers void expect_create(const char *relpath, uint64_t ino)
117ff4fbdf5SAlan Somers {
118ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
119ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
120ff4fbdf5SAlan Somers 			const char *name = (const char*)in->body.bytes +
121ff4fbdf5SAlan Somers 				sizeof(fuse_open_in);
122ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_CREATE &&
123ff4fbdf5SAlan Somers 				(0 == strcmp(relpath, name)));
124ff4fbdf5SAlan Somers 		}, Eq(true)),
125ff4fbdf5SAlan Somers 		_)
126ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
127ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
128ff4fbdf5SAlan Somers 		out->body.create.entry.attr.mode = S_IFREG | 0644;
129ff4fbdf5SAlan Somers 		out->body.create.entry.nodeid = ino;
130ff4fbdf5SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
131ff4fbdf5SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
132ff4fbdf5SAlan Somers 	})));
133ff4fbdf5SAlan Somers }
134ff4fbdf5SAlan Somers 
135ff4fbdf5SAlan Somers };
136ff4fbdf5SAlan Somers 
137ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions {
138ff4fbdf5SAlan Somers public:
139ff4fbdf5SAlan Somers void expect_removexattr()
140ff4fbdf5SAlan Somers {
141ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
142ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
143ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_REMOVEXATTR);
144ff4fbdf5SAlan Somers 		}, Eq(true)),
145ff4fbdf5SAlan Somers 		_)
146ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(0)));
147ff4fbdf5SAlan Somers }
148ff4fbdf5SAlan Somers };
149ff4fbdf5SAlan Somers 
150ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions {
151ff4fbdf5SAlan Somers public:
152ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r)
153ff4fbdf5SAlan Somers {
154ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
155ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
156ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_GETXATTR);
157ff4fbdf5SAlan Somers 		}, Eq(true)),
158ff4fbdf5SAlan Somers 		_)
159ff4fbdf5SAlan Somers 	).WillOnce(Invoke(r));
160ff4fbdf5SAlan Somers }
161ff4fbdf5SAlan Somers };
162ff4fbdf5SAlan Somers 
163ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions {
164ff4fbdf5SAlan Somers public:
165ff4fbdf5SAlan Somers void expect_listxattr()
166ff4fbdf5SAlan Somers {
167ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
168ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
169ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_LISTXATTR);
170ff4fbdf5SAlan Somers 		}, Eq(true)),
171ff4fbdf5SAlan Somers 		_)
172ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) {
173ff4fbdf5SAlan Somers 		out->body.listxattr.size = 0;
174ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
175ff4fbdf5SAlan Somers 	})));
176ff4fbdf5SAlan Somers }
177ff4fbdf5SAlan Somers };
178ff4fbdf5SAlan Somers 
179ff4fbdf5SAlan Somers class Rename: public DefaultPermissions {
180ff4fbdf5SAlan Somers public:
181ff4fbdf5SAlan Somers 	/*
182ff4fbdf5SAlan Somers 	 * Expect a rename and respond with the given error.  Don't both to
183ff4fbdf5SAlan Somers 	 * validate arguments; the tests in rename.cc do that.
184ff4fbdf5SAlan Somers 	 */
185ff4fbdf5SAlan Somers 	void expect_rename(int error)
186ff4fbdf5SAlan Somers 	{
187ff4fbdf5SAlan Somers 		EXPECT_CALL(*m_mock, process(
188ff4fbdf5SAlan Somers 			ResultOf([=](auto in) {
189ff4fbdf5SAlan Somers 				return (in->header.opcode == FUSE_RENAME);
190ff4fbdf5SAlan Somers 			}, Eq(true)),
191ff4fbdf5SAlan Somers 			_)
192ff4fbdf5SAlan Somers 		).WillOnce(Invoke(ReturnErrno(error)));
193ff4fbdf5SAlan Somers 	}
194ff4fbdf5SAlan Somers };
195ff4fbdf5SAlan Somers 
196ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions {
197ff4fbdf5SAlan Somers public:
198ff4fbdf5SAlan Somers void expect_setxattr(int error)
199ff4fbdf5SAlan Somers {
200ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
201ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
202ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETXATTR);
203ff4fbdf5SAlan Somers 		}, Eq(true)),
204ff4fbdf5SAlan Somers 		_)
205ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
206ff4fbdf5SAlan Somers }
207ff4fbdf5SAlan Somers };
208ff4fbdf5SAlan Somers 
209ff4fbdf5SAlan Somers TEST_F(Access, eacces)
2109821f1d3SAlan Somers {
2119821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2129821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2139821f1d3SAlan Somers 	uint64_t ino = 42;
2149821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
2159821f1d3SAlan Somers 
216ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
217ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
218ff4fbdf5SAlan Somers 
219ff4fbdf5SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
220ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
221ff4fbdf5SAlan Somers }
222ff4fbdf5SAlan Somers 
223ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs)
224ff4fbdf5SAlan Somers {
225ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
226ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
227ff4fbdf5SAlan Somers 	uint64_t ino = 42;
228ff4fbdf5SAlan Somers 	mode_t	access_mode = X_OK;
229ff4fbdf5SAlan Somers 
230ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, 0, 1);
231ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
232ff4fbdf5SAlan Somers 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
2339821f1d3SAlan Somers 	/*
2349821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
2359821f1d3SAlan Somers 	 * another FUSE_GETATTR or something in here.  But there should not be
2369821f1d3SAlan Somers 	 * a FUSE_ACCESS
2379821f1d3SAlan Somers 	 */
2389821f1d3SAlan Somers 
2399821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
2409821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
2419821f1d3SAlan Somers }
2429821f1d3SAlan Somers 
243ff4fbdf5SAlan Somers TEST_F(Access, ok)
2449821f1d3SAlan Somers {
2459821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2469821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2479821f1d3SAlan Somers 	uint64_t ino = 42;
2489821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
2499821f1d3SAlan Somers 
250ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
251ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
2529821f1d3SAlan Somers 	/*
2539821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
25491ff3a0dSAlan Somers 	 * another FUSE_GETATTR or something in here.
2559821f1d3SAlan Somers 	 */
2569821f1d3SAlan Somers 
2579821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
2589821f1d3SAlan Somers }
2599821f1d3SAlan Somers 
260*474ba6faSAlan Somers /* Only root may change a file's owner */
261*474ba6faSAlan Somers TEST_F(Chown, eperm)
262*474ba6faSAlan Somers {
263*474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
264*474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
265*474ba6faSAlan Somers 	const uint64_t ino = 42;
266*474ba6faSAlan Somers 	const mode_t mode = 0755;
267*474ba6faSAlan Somers 
268*474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
269*474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
270*474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
271*474ba6faSAlan Somers 		ResultOf([](auto in) {
272*474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
273*474ba6faSAlan Somers 		}, Eq(true)),
274*474ba6faSAlan Somers 		_)
275*474ba6faSAlan Somers 	).Times(0);
276*474ba6faSAlan Somers 
277*474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
278*474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
279*474ba6faSAlan Somers }
280*474ba6faSAlan Somers 
281*474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */
282*474ba6faSAlan Somers TEST_F(Chgrp, eperm)
283*474ba6faSAlan Somers {
284*474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
285*474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
286*474ba6faSAlan Somers 	const uint64_t ino = 42;
287*474ba6faSAlan Somers 	const mode_t mode = 0755;
288*474ba6faSAlan Somers 	int ngroups = 64;
289*474ba6faSAlan Somers 	gid_t groups[ngroups];
290*474ba6faSAlan Somers 	uid_t uid;
291*474ba6faSAlan Somers 	gid_t gid, newgid;
292*474ba6faSAlan Somers 	int i;
293*474ba6faSAlan Somers 
294*474ba6faSAlan Somers 	uid = geteuid();
295*474ba6faSAlan Somers 	gid = getegid();
296*474ba6faSAlan Somers 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
297*474ba6faSAlan Somers 	for (newgid = 0; newgid >= 0; newgid++) {
298*474ba6faSAlan Somers 		bool belongs = false;
299*474ba6faSAlan Somers 
300*474ba6faSAlan Somers 		for (i = 0; i < ngroups; i++) {
301*474ba6faSAlan Somers 			if (groups[i] == newgid)
302*474ba6faSAlan Somers 				belongs = true;
303*474ba6faSAlan Somers 		}
304*474ba6faSAlan Somers 		if (!belongs)
305*474ba6faSAlan Somers 			break;
306*474ba6faSAlan Somers 	}
307*474ba6faSAlan Somers 	/* newgid is now a group to which the current user does not belong */
308*474ba6faSAlan Somers 
309*474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
310*474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
311*474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
312*474ba6faSAlan Somers 		ResultOf([](auto in) {
313*474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
314*474ba6faSAlan Somers 		}, Eq(true)),
315*474ba6faSAlan Somers 		_)
316*474ba6faSAlan Somers 	).Times(0);
317*474ba6faSAlan Somers 
318*474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
319*474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
320*474ba6faSAlan Somers }
321*474ba6faSAlan Somers 
322*474ba6faSAlan Somers TEST_F(Chgrp, ok)
323*474ba6faSAlan Somers {
324*474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
325*474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
326*474ba6faSAlan Somers 	const uint64_t ino = 42;
327*474ba6faSAlan Somers 	const mode_t mode = 0755;
328*474ba6faSAlan Somers 	uid_t uid;
329*474ba6faSAlan Somers 	gid_t gid, newgid;
330*474ba6faSAlan Somers 
331*474ba6faSAlan Somers 	uid = geteuid();
332*474ba6faSAlan Somers 	gid = 0;
333*474ba6faSAlan Somers 	newgid = getegid();
334*474ba6faSAlan Somers 
335*474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
336*474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
337*474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
338*474ba6faSAlan Somers 		ResultOf([](auto in) {
339*474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
340*474ba6faSAlan Somers 		}, Eq(true)),
341*474ba6faSAlan Somers 		_)
342*474ba6faSAlan Somers 	).Times(0);
343*474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
344*474ba6faSAlan Somers 		ResultOf([](auto in) {
345*474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
346*474ba6faSAlan Somers 				in->header.nodeid == ino);
347*474ba6faSAlan Somers 		}, Eq(true)),
348*474ba6faSAlan Somers 		_)
349*474ba6faSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
350*474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
351*474ba6faSAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
352*474ba6faSAlan Somers 		out->body.attr.attr.uid = uid;
353*474ba6faSAlan Somers 		out->body.attr.attr.gid = newgid;
354*474ba6faSAlan Somers 	})));
355*474ba6faSAlan Somers 
356*474ba6faSAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
357*474ba6faSAlan Somers }
358*474ba6faSAlan Somers 
359ff4fbdf5SAlan Somers TEST_F(Create, ok)
360ff4fbdf5SAlan Somers {
361ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
362ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
363ff4fbdf5SAlan Somers 	uint64_t ino = 42;
364ff4fbdf5SAlan Somers 	int fd;
365ff4fbdf5SAlan Somers 
366ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
367ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
368ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
369ff4fbdf5SAlan Somers 
370ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
371ff4fbdf5SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
372ff4fbdf5SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
373ff4fbdf5SAlan Somers }
374ff4fbdf5SAlan Somers 
375ff4fbdf5SAlan Somers TEST_F(Create, eacces)
376ff4fbdf5SAlan Somers {
377ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
378ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
379ff4fbdf5SAlan Somers 
380ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
381ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
382ff4fbdf5SAlan Somers 
383ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
384ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
385ff4fbdf5SAlan Somers }
386ff4fbdf5SAlan Somers 
387ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
388ff4fbdf5SAlan Somers {
389ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
390ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
391ff4fbdf5SAlan Somers 	uint64_t ino = 42;
392ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
393ff4fbdf5SAlan Somers 
394ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
395ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
396ff4fbdf5SAlan Somers 
397ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
398ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
399ff4fbdf5SAlan Somers }
400ff4fbdf5SAlan Somers 
401ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
402ff4fbdf5SAlan Somers {
403ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
404ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
405ff4fbdf5SAlan Somers 	uint64_t ino = 42;
406ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
407ff4fbdf5SAlan Somers 
408ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
409ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
410ff4fbdf5SAlan Somers 	expect_removexattr();
411ff4fbdf5SAlan Somers 
412ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
413ff4fbdf5SAlan Somers 		<< strerror(errno);
414ff4fbdf5SAlan Somers }
415ff4fbdf5SAlan Somers 
416ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
417ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
418ff4fbdf5SAlan Somers {
419ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
420ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
421ff4fbdf5SAlan Somers 	uint64_t ino = 42;
422ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
423ff4fbdf5SAlan Somers 
424ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
425ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
426ff4fbdf5SAlan Somers 
427ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
428ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
429ff4fbdf5SAlan Somers }
430ff4fbdf5SAlan Somers 
431ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
432ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
433ff4fbdf5SAlan Somers {
434ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
435ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
436ff4fbdf5SAlan Somers 	uint64_t ino = 42;
437ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
438ff4fbdf5SAlan Somers 
439ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
440ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
441ff4fbdf5SAlan Somers 	expect_removexattr();
442ff4fbdf5SAlan Somers 
443ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
444ff4fbdf5SAlan Somers 		<< strerror(errno);
445ff4fbdf5SAlan Somers }
446ff4fbdf5SAlan Somers 
447ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
448ff4fbdf5SAlan Somers {
449ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
450ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
451ff4fbdf5SAlan Somers 	uint64_t ino = 42;
452ff4fbdf5SAlan Somers 	char data[80];
453ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
454ff4fbdf5SAlan Somers 
455ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
456ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
457ff4fbdf5SAlan Somers 
458ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
459ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
460ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
461ff4fbdf5SAlan Somers }
462ff4fbdf5SAlan Somers 
463ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
464ff4fbdf5SAlan Somers {
465ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
466ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
467ff4fbdf5SAlan Somers 	uint64_t ino = 42;
468ff4fbdf5SAlan Somers 	char data[80];
469ff4fbdf5SAlan Somers 	const char value[] = "whatever";
470ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
471ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
472ff4fbdf5SAlan Somers 	ssize_t r;
473ff4fbdf5SAlan Somers 
474ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
475ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
476ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
477ff4fbdf5SAlan Somers 	expect_getxattr(
478ff4fbdf5SAlan Somers 		ReturnImmediate([&](auto in __unused, auto out) {
479ff4fbdf5SAlan Somers 			memcpy((void*)out->body.bytes, value, value_len);
480ff4fbdf5SAlan Somers 			out->header.len = sizeof(out->header) + value_len;
481ff4fbdf5SAlan Somers 		})
482ff4fbdf5SAlan Somers 	);
483ff4fbdf5SAlan Somers 
484ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
485ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
486ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
487ff4fbdf5SAlan Somers }
488ff4fbdf5SAlan Somers 
489ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
490ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
491ff4fbdf5SAlan Somers {
492ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
493ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
494ff4fbdf5SAlan Somers 	uint64_t ino = 42;
495ff4fbdf5SAlan Somers 	char data[80];
496ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
497ff4fbdf5SAlan Somers 
498ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
499ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
500ff4fbdf5SAlan Somers 
501ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
502ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
503ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
504ff4fbdf5SAlan Somers }
505ff4fbdf5SAlan Somers 
506ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
507ff4fbdf5SAlan Somers {
508ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
509ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
510ff4fbdf5SAlan Somers 	uint64_t ino = 42;
511ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
512ff4fbdf5SAlan Somers 
513ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
514ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
515ff4fbdf5SAlan Somers 
516ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
517ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
518ff4fbdf5SAlan Somers }
519ff4fbdf5SAlan Somers 
520ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
521ff4fbdf5SAlan Somers {
522ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
523ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
524ff4fbdf5SAlan Somers 	uint64_t ino = 42;
525ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
526ff4fbdf5SAlan Somers 
527ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
528ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
529ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
530ff4fbdf5SAlan Somers 	expect_listxattr();
531ff4fbdf5SAlan Somers 
532ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
533ff4fbdf5SAlan Somers 		<< strerror(errno);
534ff4fbdf5SAlan Somers }
535ff4fbdf5SAlan Somers 
536ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
537ff4fbdf5SAlan Somers TEST_F(Listextattr, 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 
544ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
545ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
546ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
547ff4fbdf5SAlan Somers 
548ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
549ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
550ff4fbdf5SAlan Somers }
551ff4fbdf5SAlan Somers 
552ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
553ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
554ff4fbdf5SAlan Somers {
555ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
556ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
557ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
558ff4fbdf5SAlan Somers 
559ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
560ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
561ff4fbdf5SAlan Somers 
562ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
563ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
564ff4fbdf5SAlan Somers }
565ff4fbdf5SAlan Somers 
566ff4fbdf5SAlan Somers TEST_F(Open, eacces)
567ff4fbdf5SAlan Somers {
568ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
569ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
570ff4fbdf5SAlan Somers 	uint64_t ino = 42;
571ff4fbdf5SAlan Somers 
572ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
573ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
574ff4fbdf5SAlan Somers 
575ff4fbdf5SAlan Somers 	EXPECT_NE(0, open(FULLPATH, O_RDWR));
576ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
577ff4fbdf5SAlan Somers }
578ff4fbdf5SAlan Somers 
5799821f1d3SAlan Somers TEST_F(Open, ok)
5809821f1d3SAlan Somers {
5819821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
5829821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
5839821f1d3SAlan Somers 	uint64_t ino = 42;
5849821f1d3SAlan Somers 	int fd;
5859821f1d3SAlan Somers 
586ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
587ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
5889821f1d3SAlan Somers 	expect_open(ino, 0, 1);
5899821f1d3SAlan Somers 
5909821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
5919821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
5929821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
5939821f1d3SAlan Somers }
5949821f1d3SAlan Somers 
595ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
596ff4fbdf5SAlan Somers {
597ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
598ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
599ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
600ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
601ff4fbdf5SAlan Somers 	uint64_t ino = 42;
602ff4fbdf5SAlan Somers 
603ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
604ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
605ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELDST)
606ff4fbdf5SAlan Somers 		.Times(AnyNumber())
607ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
608ff4fbdf5SAlan Somers 
609ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
610ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
611ff4fbdf5SAlan Somers }
612ff4fbdf5SAlan Somers 
613ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
614ff4fbdf5SAlan Somers {
615ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
616ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
617ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
618ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
619ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
620ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
621ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
622ff4fbdf5SAlan Somers 
623ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
624ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
625ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
626ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
627ff4fbdf5SAlan Somers 
628ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
629ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
630ff4fbdf5SAlan Somers }
631ff4fbdf5SAlan Somers 
632ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
633ff4fbdf5SAlan Somers {
634ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
635ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
636ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
637ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
638ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
639ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
640ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
641ff4fbdf5SAlan Somers 
642ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
643ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
644ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
645ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
646ff4fbdf5SAlan Somers 
647ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
648ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
649ff4fbdf5SAlan Somers }
650ff4fbdf5SAlan Somers 
6516124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
652ff4fbdf5SAlan Somers {
653ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
654ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
655ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
656ff4fbdf5SAlan Somers 	uint64_t ino = 42;
657ff4fbdf5SAlan Somers 
658ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
659ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
660ff4fbdf5SAlan Somers 
661ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
662ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
663ff4fbdf5SAlan Somers }
664ff4fbdf5SAlan Somers 
6656124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
666ff4fbdf5SAlan Somers {
667ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
668ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
6696124fd71SAlan Somers 	const char RELDST[] = "dst";
670ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
671ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
672ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
673ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
674ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
675ff4fbdf5SAlan Somers 
676ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
677ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
678ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
6796124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
6806124fd71SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
6816124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
6826124fd71SAlan Somers 		out->body.entry.attr.mode = S_IFREG | 0644;
6836124fd71SAlan Somers 		out->body.entry.nodeid = dst_ino;
6846124fd71SAlan Somers 		out->body.entry.attr_valid = UINT64_MAX;
6856124fd71SAlan Somers 		out->body.entry.entry_valid = UINT64_MAX;
6866124fd71SAlan Somers 		out->body.entry.attr.uid = 0;
6876124fd71SAlan Somers 	})));
688ff4fbdf5SAlan Somers 
689ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
690ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
691ff4fbdf5SAlan Somers }
692ff4fbdf5SAlan Somers 
693ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
694ff4fbdf5SAlan Somers TEST_F(Rename, ok)
695ff4fbdf5SAlan Somers {
696ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
697ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
698ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
699ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
700ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
701ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
702ff4fbdf5SAlan Somers 	uint64_t ino = 42;
703ff4fbdf5SAlan Somers 
704ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
705ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
706ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
707ff4fbdf5SAlan Somers 	expect_rename(0);
708ff4fbdf5SAlan Somers 
709ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
710ff4fbdf5SAlan Somers }
711ff4fbdf5SAlan Somers 
712ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
713ff4fbdf5SAlan Somers {
714ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
715ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
716ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
717ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
718ff4fbdf5SAlan Somers 	uint64_t ino = 42;
719ff4fbdf5SAlan Somers 
720ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
721ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
722ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
723ff4fbdf5SAlan Somers 	expect_rename(0);
724ff4fbdf5SAlan Somers 
725ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
726ff4fbdf5SAlan Somers }
727ff4fbdf5SAlan Somers 
728ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
729ff4fbdf5SAlan Somers {
730ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
731ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
732ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
733ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
734ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
735ff4fbdf5SAlan Somers 
736ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
737ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
738ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
739ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
740ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
741ff4fbdf5SAlan Somers 				in->header.nodeid == ino &&
742ff4fbdf5SAlan Somers 				in->body.setattr.mode == newmode);
743ff4fbdf5SAlan Somers 		}, Eq(true)),
744ff4fbdf5SAlan Somers 		_)
745ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
746ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
747ff4fbdf5SAlan Somers 		out->body.attr.attr.mode = S_IFREG | newmode;
748ff4fbdf5SAlan Somers 	})));
749ff4fbdf5SAlan Somers 
750ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
751ff4fbdf5SAlan Somers }
752ff4fbdf5SAlan Somers 
753ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
754ff4fbdf5SAlan Somers {
755ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
756ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
757ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
758ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
759ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
760ff4fbdf5SAlan Somers 
761ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
762ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
763ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
764ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
765ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
766ff4fbdf5SAlan Somers 		}, Eq(true)),
767ff4fbdf5SAlan Somers 		_)
768ff4fbdf5SAlan Somers 	).Times(0);
769ff4fbdf5SAlan Somers 
770ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
771ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
772ff4fbdf5SAlan Somers }
773ff4fbdf5SAlan Somers 
774ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
775ff4fbdf5SAlan Somers {
776ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
777ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
778ff4fbdf5SAlan Somers 	uint64_t ino = 42;
779ff4fbdf5SAlan Somers 	const char value[] = "whatever";
780ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
781ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
782ff4fbdf5SAlan Somers 	ssize_t r;
783ff4fbdf5SAlan Somers 
784ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
785ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
786ff4fbdf5SAlan Somers 	expect_setxattr(0);
787ff4fbdf5SAlan Somers 
788ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
789ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
790ff4fbdf5SAlan Somers }
791ff4fbdf5SAlan Somers 
792ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
793ff4fbdf5SAlan Somers {
794ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
795ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
796ff4fbdf5SAlan Somers 	uint64_t ino = 42;
797ff4fbdf5SAlan Somers 	const char value[] = "whatever";
798ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
799ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
800ff4fbdf5SAlan Somers 
801ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
802ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
803ff4fbdf5SAlan Somers 
804ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
805ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
806ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
807ff4fbdf5SAlan Somers }
808ff4fbdf5SAlan Somers 
809ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
810ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
811ff4fbdf5SAlan Somers {
812ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
813ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
814ff4fbdf5SAlan Somers 	uint64_t ino = 42;
815ff4fbdf5SAlan Somers 	const char value[] = "whatever";
816ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
817ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
818ff4fbdf5SAlan Somers 
819ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
820ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
821ff4fbdf5SAlan Somers 
822ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
823ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
824ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
825ff4fbdf5SAlan Somers }
826ff4fbdf5SAlan Somers 
827ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
828ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
829ff4fbdf5SAlan Somers {
830ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
831ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
832ff4fbdf5SAlan Somers 	uint64_t ino = 42;
833ff4fbdf5SAlan Somers 	const char value[] = "whatever";
834ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
835ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
836ff4fbdf5SAlan Somers 	ssize_t r;
837ff4fbdf5SAlan Somers 
838ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
839ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
840ff4fbdf5SAlan Somers 	expect_setxattr(0);
841ff4fbdf5SAlan Somers 
842ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
843ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
844ff4fbdf5SAlan Somers }
845ff4fbdf5SAlan Somers 
846ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
8479821f1d3SAlan Somers {
8489821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
8499821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
8509821f1d3SAlan Somers 	uint64_t ino = 42;
8519821f1d3SAlan Somers 
852ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
853ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
854ff4fbdf5SAlan Somers 	expect_unlink(1, RELPATH, 0);
8559821f1d3SAlan Somers 
856ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
857ff4fbdf5SAlan Somers }
858ff4fbdf5SAlan Somers 
8596124fd71SAlan Somers /*
8606124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
8616124fd71SAlan Somers  * in VOP_LOOKUP.
8626124fd71SAlan Somers  *
8636124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
8646124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
8656124fd71SAlan Somers  */
8666124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
8676124fd71SAlan Somers {
8686124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
8696124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
8706124fd71SAlan Somers 	uint64_t ino = 42;
8716124fd71SAlan Somers 
8726124fd71SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
8736124fd71SAlan Somers 	EXPECT_LOOKUP(1, RELPATH)
8746124fd71SAlan Somers 	.Times(AnyNumber())
8756124fd71SAlan Somers 	.WillRepeatedly(Invoke(
8766124fd71SAlan Somers 		ReturnImmediate([=](auto i __unused, auto out) {
8776124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
8786124fd71SAlan Somers 			out->body.entry.attr.mode = S_IFREG | 0644;
8796124fd71SAlan Somers 			out->body.entry.nodeid = ino;
8806124fd71SAlan Somers 			out->body.entry.entry_valid = UINT64_MAX;
8816124fd71SAlan Somers 		}))
8826124fd71SAlan Somers 	);
8836124fd71SAlan Somers 
8846124fd71SAlan Somers 	/* Fill name cache */
8856124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
8866124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
8876124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
8886124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
8896124fd71SAlan Somers }
8906124fd71SAlan Somers 
891ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
892ff4fbdf5SAlan Somers {
893ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
894ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
895ff4fbdf5SAlan Somers 	uint64_t ino = 42;
896ff4fbdf5SAlan Somers 
897ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
898ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
899ff4fbdf5SAlan Somers 
900ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
901ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
902ff4fbdf5SAlan Somers }
903ff4fbdf5SAlan Somers 
9046124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
905ff4fbdf5SAlan Somers {
906ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
907ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
908ff4fbdf5SAlan Somers 	uint64_t ino = 42;
909ff4fbdf5SAlan Somers 
910ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
911ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
912ff4fbdf5SAlan Somers 
913ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
914ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
9159821f1d3SAlan Somers }
916