xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 3fa127896b72f2b1e9855979ce4b0300cb6d13e9)
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:
71a90e32deSAlan Somers void expect_chmod(uint64_t ino, mode_t mode)
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;
85a90e32deSAlan Somers 		out->body.attr.attr_valid = UINT64_MAX;
86a90e32deSAlan Somers 	})));
87a90e32deSAlan Somers }
88a90e32deSAlan Somers 
89*3fa12789SAlan Somers void expect_create(const char *relpath, uint64_t ino)
90*3fa12789SAlan Somers {
91*3fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
92*3fa12789SAlan Somers 		ResultOf([=](auto in) {
93*3fa12789SAlan Somers 			const char *name = (const char*)in->body.bytes +
94*3fa12789SAlan Somers 				sizeof(fuse_open_in);
95*3fa12789SAlan Somers 			return (in->header.opcode == FUSE_CREATE &&
96*3fa12789SAlan Somers 				(0 == strcmp(relpath, name)));
97*3fa12789SAlan Somers 		}, Eq(true)),
98*3fa12789SAlan Somers 		_)
99*3fa12789SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
100*3fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
101*3fa12789SAlan Somers 		out->body.create.entry.attr.mode = S_IFREG | 0644;
102*3fa12789SAlan Somers 		out->body.create.entry.nodeid = ino;
103*3fa12789SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
104*3fa12789SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
105*3fa12789SAlan Somers 	})));
106*3fa12789SAlan Somers }
107*3fa12789SAlan Somers 
108ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
109474ba6faSAlan Somers 	uid_t uid = 0, gid_t gid = 0)
1109821f1d3SAlan Somers {
111ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
112ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
113ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_GETATTR &&
114ff4fbdf5SAlan Somers 				in->header.nodeid == ino);
115ff4fbdf5SAlan Somers 		}, Eq(true)),
116ff4fbdf5SAlan Somers 		_)
117ff4fbdf5SAlan Somers 	).Times(times)
118ff4fbdf5SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
119ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
120ff4fbdf5SAlan Somers 		out->body.attr.attr.ino = ino;	// Must match nodeid
121ff4fbdf5SAlan Somers 		out->body.attr.attr.mode = mode;
122ff4fbdf5SAlan Somers 		out->body.attr.attr.size = 0;
123ff4fbdf5SAlan Somers 		out->body.attr.attr.uid = uid;
124474ba6faSAlan Somers 		out->body.attr.attr.uid = gid;
125ff4fbdf5SAlan Somers 		out->body.attr.attr_valid = attr_valid;
126ff4fbdf5SAlan Somers 	})));
127ff4fbdf5SAlan Somers }
128ff4fbdf5SAlan Somers 
129ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
130474ba6faSAlan Somers 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
131ff4fbdf5SAlan Somers {
132474ba6faSAlan Somers 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
1339821f1d3SAlan Somers }
1349821f1d3SAlan Somers 
1359821f1d3SAlan Somers };
1369821f1d3SAlan Somers 
1379821f1d3SAlan Somers class Access: public DefaultPermissions {};
138474ba6faSAlan Somers class Chown: public DefaultPermissions {};
139474ba6faSAlan Somers class Chgrp: public DefaultPermissions {};
140ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1419821f1d3SAlan Somers class Open: public DefaultPermissions {};
142ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {};
143ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {};
144a90e32deSAlan Somers class Write: public DefaultPermissions {};
1459821f1d3SAlan Somers 
146ff4fbdf5SAlan Somers /*
147ff4fbdf5SAlan Somers  * Test permission handling during create, mkdir, mknod, link, symlink, and
148ff4fbdf5SAlan Somers  * rename vops (they all share a common path for permission checks in
149ff4fbdf5SAlan Somers  * VOP_LOOKUP)
150ff4fbdf5SAlan Somers  */
151*3fa12789SAlan Somers class Create: public DefaultPermissions {};
152ff4fbdf5SAlan Somers 
153ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions {
154ff4fbdf5SAlan Somers public:
155ff4fbdf5SAlan Somers void expect_removexattr()
156ff4fbdf5SAlan Somers {
157ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
158ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
159ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_REMOVEXATTR);
160ff4fbdf5SAlan Somers 		}, Eq(true)),
161ff4fbdf5SAlan Somers 		_)
162ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(0)));
163ff4fbdf5SAlan Somers }
164ff4fbdf5SAlan Somers };
165ff4fbdf5SAlan Somers 
166ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions {
167ff4fbdf5SAlan Somers public:
168ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r)
169ff4fbdf5SAlan Somers {
170ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
171ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
172ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_GETXATTR);
173ff4fbdf5SAlan Somers 		}, Eq(true)),
174ff4fbdf5SAlan Somers 		_)
175ff4fbdf5SAlan Somers 	).WillOnce(Invoke(r));
176ff4fbdf5SAlan Somers }
177ff4fbdf5SAlan Somers };
178ff4fbdf5SAlan Somers 
179ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions {
180ff4fbdf5SAlan Somers public:
181ff4fbdf5SAlan Somers void expect_listxattr()
182ff4fbdf5SAlan Somers {
183ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
184ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
185ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_LISTXATTR);
186ff4fbdf5SAlan Somers 		}, Eq(true)),
187ff4fbdf5SAlan Somers 		_)
188ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) {
189ff4fbdf5SAlan Somers 		out->body.listxattr.size = 0;
190ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
191ff4fbdf5SAlan Somers 	})));
192ff4fbdf5SAlan Somers }
193ff4fbdf5SAlan Somers };
194ff4fbdf5SAlan Somers 
195ff4fbdf5SAlan Somers class Rename: public DefaultPermissions {
196ff4fbdf5SAlan Somers public:
197ff4fbdf5SAlan Somers 	/*
198ff4fbdf5SAlan Somers 	 * Expect a rename and respond with the given error.  Don't both to
199ff4fbdf5SAlan Somers 	 * validate arguments; the tests in rename.cc do that.
200ff4fbdf5SAlan Somers 	 */
201ff4fbdf5SAlan Somers 	void expect_rename(int error)
202ff4fbdf5SAlan Somers 	{
203ff4fbdf5SAlan Somers 		EXPECT_CALL(*m_mock, process(
204ff4fbdf5SAlan Somers 			ResultOf([=](auto in) {
205ff4fbdf5SAlan Somers 				return (in->header.opcode == FUSE_RENAME);
206ff4fbdf5SAlan Somers 			}, Eq(true)),
207ff4fbdf5SAlan Somers 			_)
208ff4fbdf5SAlan Somers 		).WillOnce(Invoke(ReturnErrno(error)));
209ff4fbdf5SAlan Somers 	}
210ff4fbdf5SAlan Somers };
211ff4fbdf5SAlan Somers 
212ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions {
213ff4fbdf5SAlan Somers public:
214ff4fbdf5SAlan Somers void expect_setxattr(int error)
215ff4fbdf5SAlan Somers {
216ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
217ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
218ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETXATTR);
219ff4fbdf5SAlan Somers 		}, Eq(true)),
220ff4fbdf5SAlan Somers 		_)
221ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
222ff4fbdf5SAlan Somers }
223ff4fbdf5SAlan Somers };
224ff4fbdf5SAlan Somers 
2258cfb4431SAlan Somers /* Return a group to which this user does not belong */
2268cfb4431SAlan Somers static gid_t excluded_group()
2278cfb4431SAlan Somers {
2288cfb4431SAlan Somers 	int i, ngroups = 64;
2298cfb4431SAlan Somers 	gid_t newgid, groups[ngroups];
2308cfb4431SAlan Somers 
2318cfb4431SAlan Somers 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
2328cfb4431SAlan Somers 	for (newgid = 0; newgid >= 0; newgid++) {
2338cfb4431SAlan Somers 		bool belongs = false;
2348cfb4431SAlan Somers 
2358cfb4431SAlan Somers 		for (i = 0; i < ngroups; i++) {
2368cfb4431SAlan Somers 			if (groups[i] == newgid)
2378cfb4431SAlan Somers 				belongs = true;
2388cfb4431SAlan Somers 		}
2398cfb4431SAlan Somers 		if (!belongs)
2408cfb4431SAlan Somers 			break;
2418cfb4431SAlan Somers 	}
2428cfb4431SAlan Somers 	/* newgid is now a group to which the current user does not belong */
2438cfb4431SAlan Somers 	return newgid;
2448cfb4431SAlan Somers }
2458cfb4431SAlan Somers 
246ff4fbdf5SAlan Somers TEST_F(Access, eacces)
2479821f1d3SAlan Somers {
2489821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2499821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2509821f1d3SAlan Somers 	uint64_t ino = 42;
2519821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
2529821f1d3SAlan Somers 
253ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
254ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
255ff4fbdf5SAlan Somers 
256ff4fbdf5SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
257ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
258ff4fbdf5SAlan Somers }
259ff4fbdf5SAlan Somers 
260ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs)
261ff4fbdf5SAlan Somers {
262ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
263ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
264ff4fbdf5SAlan Somers 	uint64_t ino = 42;
265ff4fbdf5SAlan Somers 	mode_t	access_mode = X_OK;
266ff4fbdf5SAlan Somers 
267ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, 0, 1);
268ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
269ff4fbdf5SAlan Somers 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
2709821f1d3SAlan Somers 	/*
2719821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
2729821f1d3SAlan Somers 	 * another FUSE_GETATTR or something in here.  But there should not be
2739821f1d3SAlan Somers 	 * a FUSE_ACCESS
2749821f1d3SAlan Somers 	 */
2759821f1d3SAlan Somers 
2769821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
2779821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
2789821f1d3SAlan Somers }
2799821f1d3SAlan Somers 
280ff4fbdf5SAlan Somers TEST_F(Access, ok)
2819821f1d3SAlan Somers {
2829821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2839821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2849821f1d3SAlan Somers 	uint64_t ino = 42;
2859821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
2869821f1d3SAlan Somers 
287ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
288ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
2899821f1d3SAlan Somers 	/*
2909821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
29191ff3a0dSAlan Somers 	 * another FUSE_GETATTR or something in here.
2929821f1d3SAlan Somers 	 */
2939821f1d3SAlan Somers 
2949821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
2959821f1d3SAlan Somers }
2969821f1d3SAlan Somers 
297474ba6faSAlan Somers /* Only root may change a file's owner */
298474ba6faSAlan Somers TEST_F(Chown, eperm)
299474ba6faSAlan Somers {
300474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
301474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
302474ba6faSAlan Somers 	const uint64_t ino = 42;
303474ba6faSAlan Somers 	const mode_t mode = 0755;
304474ba6faSAlan Somers 
305474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
306474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
307474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
308474ba6faSAlan Somers 		ResultOf([](auto in) {
309474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
310474ba6faSAlan Somers 		}, Eq(true)),
311474ba6faSAlan Somers 		_)
312474ba6faSAlan Somers 	).Times(0);
313474ba6faSAlan Somers 
314474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
315474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
316474ba6faSAlan Somers }
317474ba6faSAlan Somers 
318474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */
319474ba6faSAlan Somers TEST_F(Chgrp, eperm)
320474ba6faSAlan Somers {
321474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
322474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
323474ba6faSAlan Somers 	const uint64_t ino = 42;
324474ba6faSAlan Somers 	const mode_t mode = 0755;
325474ba6faSAlan Somers 	uid_t uid;
326474ba6faSAlan Somers 	gid_t gid, newgid;
327474ba6faSAlan Somers 
328474ba6faSAlan Somers 	uid = geteuid();
329474ba6faSAlan Somers 	gid = getegid();
3308cfb4431SAlan Somers 	newgid = excluded_group();
331474ba6faSAlan Somers 
332474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
333474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
334474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
335474ba6faSAlan Somers 		ResultOf([](auto in) {
336474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
337474ba6faSAlan Somers 		}, Eq(true)),
338474ba6faSAlan Somers 		_)
339474ba6faSAlan Somers 	).Times(0);
340474ba6faSAlan Somers 
341474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
342474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
343474ba6faSAlan Somers }
344474ba6faSAlan Somers 
345474ba6faSAlan Somers TEST_F(Chgrp, ok)
346474ba6faSAlan Somers {
347474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
348474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
349474ba6faSAlan Somers 	const uint64_t ino = 42;
350474ba6faSAlan Somers 	const mode_t mode = 0755;
351474ba6faSAlan Somers 	uid_t uid;
352474ba6faSAlan Somers 	gid_t gid, newgid;
353474ba6faSAlan Somers 
354474ba6faSAlan Somers 	uid = geteuid();
355474ba6faSAlan Somers 	gid = 0;
356474ba6faSAlan Somers 	newgid = getegid();
357474ba6faSAlan Somers 
358474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
359474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
360474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
361474ba6faSAlan Somers 		ResultOf([](auto in) {
362474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
363474ba6faSAlan Somers 		}, Eq(true)),
364474ba6faSAlan Somers 		_)
365474ba6faSAlan Somers 	).Times(0);
366474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
367474ba6faSAlan Somers 		ResultOf([](auto in) {
368474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
369474ba6faSAlan Somers 				in->header.nodeid == ino);
370474ba6faSAlan Somers 		}, Eq(true)),
371474ba6faSAlan Somers 		_)
372474ba6faSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
373474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
374474ba6faSAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
375474ba6faSAlan Somers 		out->body.attr.attr.uid = uid;
376474ba6faSAlan Somers 		out->body.attr.attr.gid = newgid;
377474ba6faSAlan Somers 	})));
378474ba6faSAlan Somers 
379474ba6faSAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
380474ba6faSAlan Somers }
381474ba6faSAlan Somers 
382ff4fbdf5SAlan Somers TEST_F(Create, ok)
383ff4fbdf5SAlan Somers {
384ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
385ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
386ff4fbdf5SAlan Somers 	uint64_t ino = 42;
387ff4fbdf5SAlan Somers 	int fd;
388ff4fbdf5SAlan Somers 
389ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
390ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
391ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
392ff4fbdf5SAlan Somers 
393ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
394ff4fbdf5SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
395ff4fbdf5SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
396ff4fbdf5SAlan Somers }
397ff4fbdf5SAlan Somers 
398ff4fbdf5SAlan Somers TEST_F(Create, eacces)
399ff4fbdf5SAlan Somers {
400ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
401ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
402ff4fbdf5SAlan Somers 
403ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
404ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
405ff4fbdf5SAlan Somers 
406ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
407ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
408ff4fbdf5SAlan Somers }
409ff4fbdf5SAlan Somers 
410ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
411ff4fbdf5SAlan Somers {
412ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
413ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
414ff4fbdf5SAlan Somers 	uint64_t ino = 42;
415ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
416ff4fbdf5SAlan Somers 
417ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
418ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
419ff4fbdf5SAlan Somers 
420ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
421ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
422ff4fbdf5SAlan Somers }
423ff4fbdf5SAlan Somers 
424ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
425ff4fbdf5SAlan Somers {
426ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
427ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
428ff4fbdf5SAlan Somers 	uint64_t ino = 42;
429ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
430ff4fbdf5SAlan Somers 
431ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
432ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
433ff4fbdf5SAlan Somers 	expect_removexattr();
434ff4fbdf5SAlan Somers 
435ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
436ff4fbdf5SAlan Somers 		<< strerror(errno);
437ff4fbdf5SAlan Somers }
438ff4fbdf5SAlan Somers 
439ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
440ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
441ff4fbdf5SAlan Somers {
442ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
443ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
444ff4fbdf5SAlan Somers 	uint64_t ino = 42;
445ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
446ff4fbdf5SAlan Somers 
447ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
448ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
449ff4fbdf5SAlan Somers 
450ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
451ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
452ff4fbdf5SAlan Somers }
453ff4fbdf5SAlan Somers 
454ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
455ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
456ff4fbdf5SAlan Somers {
457ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
458ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
459ff4fbdf5SAlan Somers 	uint64_t ino = 42;
460ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
461ff4fbdf5SAlan Somers 
462ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
463ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
464ff4fbdf5SAlan Somers 	expect_removexattr();
465ff4fbdf5SAlan Somers 
466ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
467ff4fbdf5SAlan Somers 		<< strerror(errno);
468ff4fbdf5SAlan Somers }
469ff4fbdf5SAlan Somers 
470ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
471ff4fbdf5SAlan Somers {
472ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
473ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
474ff4fbdf5SAlan Somers 	uint64_t ino = 42;
475ff4fbdf5SAlan Somers 	char data[80];
476ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
477ff4fbdf5SAlan Somers 
478ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
479ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
480ff4fbdf5SAlan Somers 
481ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
482ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
483ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
484ff4fbdf5SAlan Somers }
485ff4fbdf5SAlan Somers 
486ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
487ff4fbdf5SAlan Somers {
488ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
489ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
490ff4fbdf5SAlan Somers 	uint64_t ino = 42;
491ff4fbdf5SAlan Somers 	char data[80];
492ff4fbdf5SAlan Somers 	const char value[] = "whatever";
493ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
494ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
495ff4fbdf5SAlan Somers 	ssize_t r;
496ff4fbdf5SAlan Somers 
497ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
498ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
499ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
500ff4fbdf5SAlan Somers 	expect_getxattr(
501ff4fbdf5SAlan Somers 		ReturnImmediate([&](auto in __unused, auto out) {
502ff4fbdf5SAlan Somers 			memcpy((void*)out->body.bytes, value, value_len);
503ff4fbdf5SAlan Somers 			out->header.len = sizeof(out->header) + value_len;
504ff4fbdf5SAlan Somers 		})
505ff4fbdf5SAlan Somers 	);
506ff4fbdf5SAlan Somers 
507ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
508ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
509ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
510ff4fbdf5SAlan Somers }
511ff4fbdf5SAlan Somers 
512ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
513ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
514ff4fbdf5SAlan Somers {
515ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
516ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
517ff4fbdf5SAlan Somers 	uint64_t ino = 42;
518ff4fbdf5SAlan Somers 	char data[80];
519ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
520ff4fbdf5SAlan Somers 
521ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
522ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
523ff4fbdf5SAlan Somers 
524ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
525ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
526ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
527ff4fbdf5SAlan Somers }
528ff4fbdf5SAlan Somers 
529ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
530ff4fbdf5SAlan Somers {
531ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
532ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
533ff4fbdf5SAlan Somers 	uint64_t ino = 42;
534ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
535ff4fbdf5SAlan Somers 
536ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
537ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
538ff4fbdf5SAlan Somers 
539ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
540ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
541ff4fbdf5SAlan Somers }
542ff4fbdf5SAlan Somers 
543ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
544ff4fbdf5SAlan Somers {
545ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
546ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
547ff4fbdf5SAlan Somers 	uint64_t ino = 42;
548ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
549ff4fbdf5SAlan Somers 
550ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
551ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
552ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
553ff4fbdf5SAlan Somers 	expect_listxattr();
554ff4fbdf5SAlan Somers 
555ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
556ff4fbdf5SAlan Somers 		<< strerror(errno);
557ff4fbdf5SAlan Somers }
558ff4fbdf5SAlan Somers 
559ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
560ff4fbdf5SAlan Somers TEST_F(Listextattr, system)
561ff4fbdf5SAlan Somers {
562ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
563ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
564ff4fbdf5SAlan Somers 	uint64_t ino = 42;
565ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
566ff4fbdf5SAlan Somers 
567ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
568ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
569ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
570ff4fbdf5SAlan Somers 
571ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
572ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
573ff4fbdf5SAlan Somers }
574ff4fbdf5SAlan Somers 
575ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
576ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
577ff4fbdf5SAlan Somers {
578ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
579ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
580ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
581ff4fbdf5SAlan Somers 
582ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
583ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
584ff4fbdf5SAlan Somers 
585ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
586ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
587ff4fbdf5SAlan Somers }
588ff4fbdf5SAlan Somers 
589ff4fbdf5SAlan Somers TEST_F(Open, eacces)
590ff4fbdf5SAlan Somers {
591ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
592ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
593ff4fbdf5SAlan Somers 	uint64_t ino = 42;
594ff4fbdf5SAlan Somers 
595ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
596ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
597ff4fbdf5SAlan Somers 
598ff4fbdf5SAlan Somers 	EXPECT_NE(0, open(FULLPATH, O_RDWR));
599ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
600ff4fbdf5SAlan Somers }
601ff4fbdf5SAlan Somers 
6029821f1d3SAlan Somers TEST_F(Open, ok)
6039821f1d3SAlan Somers {
6049821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
6059821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
6069821f1d3SAlan Somers 	uint64_t ino = 42;
6079821f1d3SAlan Somers 	int fd;
6089821f1d3SAlan Somers 
609ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
610ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
6119821f1d3SAlan Somers 	expect_open(ino, 0, 1);
6129821f1d3SAlan Somers 
6139821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
6149821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
6159821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
6169821f1d3SAlan Somers }
6179821f1d3SAlan Somers 
618ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
619ff4fbdf5SAlan Somers {
620ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
621ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
622ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
623ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
624ff4fbdf5SAlan Somers 	uint64_t ino = 42;
625ff4fbdf5SAlan Somers 
626ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
627ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
628ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELDST)
629ff4fbdf5SAlan Somers 		.Times(AnyNumber())
630ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
631ff4fbdf5SAlan Somers 
632ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
633ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
634ff4fbdf5SAlan Somers }
635ff4fbdf5SAlan Somers 
636ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
637ff4fbdf5SAlan Somers {
638ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
639ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
640ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
641ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
642ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
643ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
644ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
645ff4fbdf5SAlan Somers 
646ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
647ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
648ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
649ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
650ff4fbdf5SAlan Somers 
651ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
652ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
653ff4fbdf5SAlan Somers }
654ff4fbdf5SAlan Somers 
655ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
656ff4fbdf5SAlan Somers {
657ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
658ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
659ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
660ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
661ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
662ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
663ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
664ff4fbdf5SAlan Somers 
665ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
666ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
667ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
668ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
669ff4fbdf5SAlan Somers 
670ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
671ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
672ff4fbdf5SAlan Somers }
673ff4fbdf5SAlan Somers 
6746124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
675ff4fbdf5SAlan Somers {
676ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
677ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
678ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
679ff4fbdf5SAlan Somers 	uint64_t ino = 42;
680ff4fbdf5SAlan Somers 
681ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
682ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
683ff4fbdf5SAlan Somers 
684ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
685ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
686ff4fbdf5SAlan Somers }
687ff4fbdf5SAlan Somers 
6886124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
689ff4fbdf5SAlan Somers {
690ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
691ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
6926124fd71SAlan Somers 	const char RELDST[] = "dst";
693ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
694ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
695ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
696ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
697ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
698ff4fbdf5SAlan Somers 
699ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
700ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
701ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
7026124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
7036124fd71SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
7046124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
7056124fd71SAlan Somers 		out->body.entry.attr.mode = S_IFREG | 0644;
7066124fd71SAlan Somers 		out->body.entry.nodeid = dst_ino;
7076124fd71SAlan Somers 		out->body.entry.attr_valid = UINT64_MAX;
7086124fd71SAlan Somers 		out->body.entry.entry_valid = UINT64_MAX;
7096124fd71SAlan Somers 		out->body.entry.attr.uid = 0;
7106124fd71SAlan Somers 	})));
711ff4fbdf5SAlan Somers 
712ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
713ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
714ff4fbdf5SAlan Somers }
715ff4fbdf5SAlan Somers 
716ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
717ff4fbdf5SAlan Somers TEST_F(Rename, ok)
718ff4fbdf5SAlan Somers {
719ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
720ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
721ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
722ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
723ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
724ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
725ff4fbdf5SAlan Somers 	uint64_t ino = 42;
726ff4fbdf5SAlan Somers 
727ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
728ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
729ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
730ff4fbdf5SAlan Somers 	expect_rename(0);
731ff4fbdf5SAlan Somers 
732ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
733ff4fbdf5SAlan Somers }
734ff4fbdf5SAlan Somers 
735ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
736ff4fbdf5SAlan Somers {
737ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
738ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
739ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
740ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
741ff4fbdf5SAlan Somers 	uint64_t ino = 42;
742ff4fbdf5SAlan Somers 
743ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
744ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
745ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
746ff4fbdf5SAlan Somers 	expect_rename(0);
747ff4fbdf5SAlan Somers 
748ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
749ff4fbdf5SAlan Somers }
750ff4fbdf5SAlan Somers 
751ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
752ff4fbdf5SAlan Somers {
753ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
754ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
755ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
756ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
757ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
758ff4fbdf5SAlan Somers 
759ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
760ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
761ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
762ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
763ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
764ff4fbdf5SAlan Somers 				in->header.nodeid == ino &&
765ff4fbdf5SAlan Somers 				in->body.setattr.mode == newmode);
766ff4fbdf5SAlan Somers 		}, Eq(true)),
767ff4fbdf5SAlan Somers 		_)
768ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
769ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
770ff4fbdf5SAlan Somers 		out->body.attr.attr.mode = S_IFREG | newmode;
771ff4fbdf5SAlan Somers 	})));
772ff4fbdf5SAlan Somers 
773ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
774ff4fbdf5SAlan Somers }
775ff4fbdf5SAlan Somers 
776ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
777ff4fbdf5SAlan Somers {
778ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
779ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
780ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
781ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
782ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
783ff4fbdf5SAlan Somers 
784ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
785ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
786ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
787ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
788ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
789ff4fbdf5SAlan Somers 		}, Eq(true)),
790ff4fbdf5SAlan Somers 		_)
791ff4fbdf5SAlan Somers 	).Times(0);
792ff4fbdf5SAlan Somers 
793ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
794ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
795ff4fbdf5SAlan Somers }
796ff4fbdf5SAlan Somers 
7978cfb4431SAlan Somers /*
798*3fa12789SAlan Somers  * ftruncate() of a file without writable permissions should succeed as long as
799*3fa12789SAlan Somers  * the file descriptor is writable.  This is important when combined with
800*3fa12789SAlan Somers  * O_CREAT
801*3fa12789SAlan Somers  */
802*3fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file)
803*3fa12789SAlan Somers {
804*3fa12789SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
805*3fa12789SAlan Somers 	const char RELPATH[] = "some_file.txt";
806*3fa12789SAlan Somers 	const uint64_t ino = 42;
807*3fa12789SAlan Somers 	const mode_t mode = 0000;
808*3fa12789SAlan Somers 	int fd;
809*3fa12789SAlan Somers 
810*3fa12789SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
811*3fa12789SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
812*3fa12789SAlan Somers 	expect_create(RELPATH, ino);
813*3fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
814*3fa12789SAlan Somers 		ResultOf([](auto in) {
815*3fa12789SAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
816*3fa12789SAlan Somers 				in->header.nodeid == ino &&
817*3fa12789SAlan Somers 				(in->body.setattr.valid & FATTR_SIZE));
818*3fa12789SAlan Somers 		}, Eq(true)),
819*3fa12789SAlan Somers 		_)
820*3fa12789SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
821*3fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
822*3fa12789SAlan Somers 		out->body.attr.attr.ino = ino;
823*3fa12789SAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
824*3fa12789SAlan Somers 		out->body.attr.attr_valid = UINT64_MAX;
825*3fa12789SAlan Somers 	})));
826*3fa12789SAlan Somers 
827*3fa12789SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
828*3fa12789SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
829*3fa12789SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
830*3fa12789SAlan Somers 	/* Deliberately leak fd */
831*3fa12789SAlan Somers }
832*3fa12789SAlan Somers 
833*3fa12789SAlan Somers /*
8348cfb4431SAlan Somers  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
8358cfb4431SAlan Somers  * to the file's group
8368cfb4431SAlan Somers  */
8378cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member)
8388cfb4431SAlan Somers {
8398cfb4431SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
8408cfb4431SAlan Somers 	const char RELPATH[] = "some_file.txt";
8418cfb4431SAlan Somers 	const uint64_t ino = 42;
8428cfb4431SAlan Somers 	const mode_t oldmode = 0755;
8438cfb4431SAlan Somers 	const mode_t newmode = 02755;
8448cfb4431SAlan Somers 	uid_t uid = geteuid();
8458cfb4431SAlan Somers 	gid_t gid = excluded_group();
8468cfb4431SAlan Somers 
8478cfb4431SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
8488cfb4431SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
8498cfb4431SAlan Somers 	EXPECT_CALL(*m_mock, process(
8508cfb4431SAlan Somers 		ResultOf([](auto in) {
8518cfb4431SAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
8528cfb4431SAlan Somers 		}, Eq(true)),
8538cfb4431SAlan Somers 		_)
8548cfb4431SAlan Somers 	).Times(0);
8558cfb4431SAlan Somers 
8568cfb4431SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
8578cfb4431SAlan Somers 	EXPECT_EQ(EPERM, errno);
8588cfb4431SAlan Somers }
8598cfb4431SAlan Somers 
860e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
861e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
862e5ff3a7eSAlan Somers {
863e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
864e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
865e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
866e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
867e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
868e5ff3a7eSAlan Somers 
869e5ff3a7eSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
870e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
871e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
872e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
873e5ff3a7eSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
874e5ff3a7eSAlan Somers 		}, Eq(true)),
875e5ff3a7eSAlan Somers 		_)
876e5ff3a7eSAlan Somers 	).Times(0);
877e5ff3a7eSAlan Somers 
878e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
879e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
880e5ff3a7eSAlan Somers }
881e5ff3a7eSAlan Somers 
882ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
883ff4fbdf5SAlan Somers {
884ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
885ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
886ff4fbdf5SAlan Somers 	uint64_t ino = 42;
887ff4fbdf5SAlan Somers 	const char value[] = "whatever";
888ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
889ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
890ff4fbdf5SAlan Somers 	ssize_t r;
891ff4fbdf5SAlan Somers 
892ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
893ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
894ff4fbdf5SAlan Somers 	expect_setxattr(0);
895ff4fbdf5SAlan Somers 
896ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
897ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
898ff4fbdf5SAlan Somers }
899ff4fbdf5SAlan Somers 
900ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
901ff4fbdf5SAlan Somers {
902ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
903ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
904ff4fbdf5SAlan Somers 	uint64_t ino = 42;
905ff4fbdf5SAlan Somers 	const char value[] = "whatever";
906ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
907ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
908ff4fbdf5SAlan Somers 
909ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
910ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
911ff4fbdf5SAlan Somers 
912ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
913ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
914ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
915ff4fbdf5SAlan Somers }
916ff4fbdf5SAlan Somers 
917ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
918ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
919ff4fbdf5SAlan Somers {
920ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
921ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
922ff4fbdf5SAlan Somers 	uint64_t ino = 42;
923ff4fbdf5SAlan Somers 	const char value[] = "whatever";
924ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
925ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
926ff4fbdf5SAlan Somers 
927ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
928ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
929ff4fbdf5SAlan Somers 
930ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
931ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
932ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
933ff4fbdf5SAlan Somers }
934ff4fbdf5SAlan Somers 
935ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
936ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
937ff4fbdf5SAlan Somers {
938ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
939ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
940ff4fbdf5SAlan Somers 	uint64_t ino = 42;
941ff4fbdf5SAlan Somers 	const char value[] = "whatever";
942ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
943ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
944ff4fbdf5SAlan Somers 	ssize_t r;
945ff4fbdf5SAlan Somers 
946ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
947ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
948ff4fbdf5SAlan Somers 	expect_setxattr(0);
949ff4fbdf5SAlan Somers 
950ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
951ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
952ff4fbdf5SAlan Somers }
953ff4fbdf5SAlan Somers 
954ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
9559821f1d3SAlan Somers {
9569821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
9579821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
9589821f1d3SAlan Somers 	uint64_t ino = 42;
9599821f1d3SAlan Somers 
960ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
961ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
962ff4fbdf5SAlan Somers 	expect_unlink(1, RELPATH, 0);
9639821f1d3SAlan Somers 
964ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
965ff4fbdf5SAlan Somers }
966ff4fbdf5SAlan Somers 
9676124fd71SAlan Somers /*
9686124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
9696124fd71SAlan Somers  * in VOP_LOOKUP.
9706124fd71SAlan Somers  *
9716124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
9726124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
9736124fd71SAlan Somers  */
9746124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
9756124fd71SAlan Somers {
9766124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
9776124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
9786124fd71SAlan Somers 	uint64_t ino = 42;
9796124fd71SAlan Somers 
9806124fd71SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
9816124fd71SAlan Somers 	EXPECT_LOOKUP(1, RELPATH)
9826124fd71SAlan Somers 	.Times(AnyNumber())
9836124fd71SAlan Somers 	.WillRepeatedly(Invoke(
9846124fd71SAlan Somers 		ReturnImmediate([=](auto i __unused, auto out) {
9856124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
9866124fd71SAlan Somers 			out->body.entry.attr.mode = S_IFREG | 0644;
9876124fd71SAlan Somers 			out->body.entry.nodeid = ino;
9886124fd71SAlan Somers 			out->body.entry.entry_valid = UINT64_MAX;
9896124fd71SAlan Somers 		}))
9906124fd71SAlan Somers 	);
9916124fd71SAlan Somers 
9926124fd71SAlan Somers 	/* Fill name cache */
9936124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
9946124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
9956124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
9966124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
9976124fd71SAlan Somers }
9986124fd71SAlan Somers 
999ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
1000ff4fbdf5SAlan Somers {
1001ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1002ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1003ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1004ff4fbdf5SAlan Somers 
1005ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1006ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1007ff4fbdf5SAlan Somers 
1008ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1009ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1010ff4fbdf5SAlan Somers }
1011ff4fbdf5SAlan Somers 
10126124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
1013ff4fbdf5SAlan Somers {
1014ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1015ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1016ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1017ff4fbdf5SAlan Somers 
1018ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
1019ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1020ff4fbdf5SAlan Somers 
1021ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1022ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
10239821f1d3SAlan Somers }
1024a90e32deSAlan Somers 
1025a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
1026a90e32deSAlan Somers TEST_F(Write, clear_suid)
1027a90e32deSAlan Somers {
1028a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1029a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1030a90e32deSAlan Somers 	struct stat sb;
1031a90e32deSAlan Somers 	uint64_t ino = 42;
1032a90e32deSAlan Somers 	mode_t oldmode = 04777;
1033a90e32deSAlan Somers 	mode_t newmode = 0777;
1034a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1035a90e32deSAlan Somers 	int fd;
1036a90e32deSAlan Somers 
1037a90e32deSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1038a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1039a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1040a90e32deSAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1041a90e32deSAlan Somers 	expect_chmod(ino, newmode);
1042a90e32deSAlan Somers 
1043a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1044a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1045a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1046a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1047a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1048a90e32deSAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1049a90e32deSAlan Somers }
1050a90e32deSAlan Somers 
1051a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
1052a90e32deSAlan Somers TEST_F(Write, clear_sgid)
1053a90e32deSAlan Somers {
1054a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1055a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1056a90e32deSAlan Somers 	struct stat sb;
1057a90e32deSAlan Somers 	uint64_t ino = 42;
1058a90e32deSAlan Somers 	mode_t oldmode = 02777;
1059a90e32deSAlan Somers 	mode_t newmode = 0777;
1060a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1061a90e32deSAlan Somers 	int fd;
1062a90e32deSAlan Somers 
1063a90e32deSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1064a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1065a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1066a90e32deSAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1067a90e32deSAlan Somers 	expect_chmod(ino, newmode);
1068a90e32deSAlan Somers 
1069a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1070a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1071a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1072a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1073a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1074a90e32deSAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1075a90e32deSAlan Somers }
1076