xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision a90e32de25869ee579ad6b793e2e58c635ad9ac2)
19821f1d3SAlan Somers /*-
29821f1d3SAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
39821f1d3SAlan Somers  *
49821f1d3SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers  *
69821f1d3SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers  * from the FreeBSD Foundation.
89821f1d3SAlan Somers  *
99821f1d3SAlan Somers  * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers  * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers  * are met:
129821f1d3SAlan Somers  * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers  *    documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers  *
189821f1d3SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers  * SUCH DAMAGE.
299821f1d3SAlan Somers  */
309821f1d3SAlan Somers 
319821f1d3SAlan Somers /*
329821f1d3SAlan Somers  * Tests for the "default_permissions" mount option.  They must be in their own
339821f1d3SAlan Somers  * file so they can be run as an unprivileged user
349821f1d3SAlan Somers  */
359821f1d3SAlan Somers 
369821f1d3SAlan Somers extern "C" {
37ff4fbdf5SAlan Somers #include <sys/types.h>
38ff4fbdf5SAlan Somers #include <sys/extattr.h>
39ff4fbdf5SAlan Somers 
409821f1d3SAlan Somers #include <fcntl.h>
419821f1d3SAlan Somers #include <unistd.h>
429821f1d3SAlan Somers }
439821f1d3SAlan Somers 
449821f1d3SAlan Somers #include "mockfs.hh"
459821f1d3SAlan Somers #include "utils.hh"
469821f1d3SAlan Somers 
479821f1d3SAlan Somers using namespace testing;
489821f1d3SAlan Somers 
499821f1d3SAlan Somers class DefaultPermissions: public FuseTest {
509821f1d3SAlan Somers 
519821f1d3SAlan Somers virtual void SetUp() {
52ff4fbdf5SAlan Somers 	m_default_permissions = true;
539821f1d3SAlan Somers 	FuseTest::SetUp();
54ff4fbdf5SAlan Somers 	if (HasFatalFailure() || IsSkipped())
55ff4fbdf5SAlan Somers 		return;
569821f1d3SAlan Somers 
579821f1d3SAlan Somers 	if (geteuid() == 0) {
589821f1d3SAlan Somers 		GTEST_SKIP() << "This test requires an unprivileged user";
599821f1d3SAlan Somers 	}
60ff4fbdf5SAlan Somers 
61ff4fbdf5SAlan Somers 	/* With -o default_permissions, FUSE_ACCESS should never be called */
62ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
63ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
64ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_ACCESS);
65ff4fbdf5SAlan Somers 		}, Eq(true)),
66ff4fbdf5SAlan Somers 		_)
67ff4fbdf5SAlan Somers 	).Times(0);
689821f1d3SAlan Somers }
699821f1d3SAlan Somers 
709821f1d3SAlan Somers public:
71*a90e32deSAlan Somers void expect_chmod(uint64_t ino, mode_t mode)
72*a90e32deSAlan Somers {
73*a90e32deSAlan Somers 	EXPECT_CALL(*m_mock, process(
74*a90e32deSAlan Somers 		ResultOf([=](auto in) {
75*a90e32deSAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
76*a90e32deSAlan Somers 				in->header.nodeid == ino &&
77*a90e32deSAlan Somers 				in->body.setattr.valid == FATTR_MODE &&
78*a90e32deSAlan Somers 				in->body.setattr.mode == mode);
79*a90e32deSAlan Somers 		}, Eq(true)),
80*a90e32deSAlan Somers 		_)
81*a90e32deSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
82*a90e32deSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
83*a90e32deSAlan Somers 		out->body.attr.attr.ino = ino;	// Must match nodeid
84*a90e32deSAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
85*a90e32deSAlan Somers 		out->body.attr.attr_valid = UINT64_MAX;
86*a90e32deSAlan Somers 	})));
87*a90e32deSAlan Somers }
88*a90e32deSAlan Somers 
89ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
90474ba6faSAlan Somers 	uid_t uid = 0, gid_t gid = 0)
919821f1d3SAlan Somers {
92ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
93ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
94ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_GETATTR &&
95ff4fbdf5SAlan Somers 				in->header.nodeid == ino);
96ff4fbdf5SAlan Somers 		}, Eq(true)),
97ff4fbdf5SAlan Somers 		_)
98ff4fbdf5SAlan Somers 	).Times(times)
99ff4fbdf5SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
100ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
101ff4fbdf5SAlan Somers 		out->body.attr.attr.ino = ino;	// Must match nodeid
102ff4fbdf5SAlan Somers 		out->body.attr.attr.mode = mode;
103ff4fbdf5SAlan Somers 		out->body.attr.attr.size = 0;
104ff4fbdf5SAlan Somers 		out->body.attr.attr.uid = uid;
105474ba6faSAlan Somers 		out->body.attr.attr.uid = gid;
106ff4fbdf5SAlan Somers 		out->body.attr.attr_valid = attr_valid;
107ff4fbdf5SAlan Somers 	})));
108ff4fbdf5SAlan Somers }
109ff4fbdf5SAlan Somers 
110ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
111474ba6faSAlan Somers 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
112ff4fbdf5SAlan Somers {
113474ba6faSAlan Somers 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
1149821f1d3SAlan Somers }
1159821f1d3SAlan Somers 
1169821f1d3SAlan Somers };
1179821f1d3SAlan Somers 
1189821f1d3SAlan Somers class Access: public DefaultPermissions {};
119474ba6faSAlan Somers class Chown: public DefaultPermissions {};
120474ba6faSAlan Somers class Chgrp: public DefaultPermissions {};
121ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1229821f1d3SAlan Somers class Open: public DefaultPermissions {};
123ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {};
124ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {};
125*a90e32deSAlan Somers class Write: public DefaultPermissions {};
1269821f1d3SAlan Somers 
127ff4fbdf5SAlan Somers /*
128ff4fbdf5SAlan Somers  * Test permission handling during create, mkdir, mknod, link, symlink, and
129ff4fbdf5SAlan Somers  * rename vops (they all share a common path for permission checks in
130ff4fbdf5SAlan Somers  * VOP_LOOKUP)
131ff4fbdf5SAlan Somers  */
132ff4fbdf5SAlan Somers class Create: public DefaultPermissions {
133ff4fbdf5SAlan Somers public:
134ff4fbdf5SAlan Somers 
135ff4fbdf5SAlan Somers void expect_create(const char *relpath, uint64_t ino)
136ff4fbdf5SAlan Somers {
137ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
138ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
139ff4fbdf5SAlan Somers 			const char *name = (const char*)in->body.bytes +
140ff4fbdf5SAlan Somers 				sizeof(fuse_open_in);
141ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_CREATE &&
142ff4fbdf5SAlan Somers 				(0 == strcmp(relpath, name)));
143ff4fbdf5SAlan Somers 		}, Eq(true)),
144ff4fbdf5SAlan Somers 		_)
145ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
146ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
147ff4fbdf5SAlan Somers 		out->body.create.entry.attr.mode = S_IFREG | 0644;
148ff4fbdf5SAlan Somers 		out->body.create.entry.nodeid = ino;
149ff4fbdf5SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
150ff4fbdf5SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
151ff4fbdf5SAlan Somers 	})));
152ff4fbdf5SAlan Somers }
153ff4fbdf5SAlan Somers 
154ff4fbdf5SAlan Somers };
155ff4fbdf5SAlan Somers 
156ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions {
157ff4fbdf5SAlan Somers public:
158ff4fbdf5SAlan Somers void expect_removexattr()
159ff4fbdf5SAlan Somers {
160ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
161ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
162ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_REMOVEXATTR);
163ff4fbdf5SAlan Somers 		}, Eq(true)),
164ff4fbdf5SAlan Somers 		_)
165ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(0)));
166ff4fbdf5SAlan Somers }
167ff4fbdf5SAlan Somers };
168ff4fbdf5SAlan Somers 
169ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions {
170ff4fbdf5SAlan Somers public:
171ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r)
172ff4fbdf5SAlan Somers {
173ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
174ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
175ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_GETXATTR);
176ff4fbdf5SAlan Somers 		}, Eq(true)),
177ff4fbdf5SAlan Somers 		_)
178ff4fbdf5SAlan Somers 	).WillOnce(Invoke(r));
179ff4fbdf5SAlan Somers }
180ff4fbdf5SAlan Somers };
181ff4fbdf5SAlan Somers 
182ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions {
183ff4fbdf5SAlan Somers public:
184ff4fbdf5SAlan Somers void expect_listxattr()
185ff4fbdf5SAlan Somers {
186ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
187ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
188ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_LISTXATTR);
189ff4fbdf5SAlan Somers 		}, Eq(true)),
190ff4fbdf5SAlan Somers 		_)
191ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) {
192ff4fbdf5SAlan Somers 		out->body.listxattr.size = 0;
193ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
194ff4fbdf5SAlan Somers 	})));
195ff4fbdf5SAlan Somers }
196ff4fbdf5SAlan Somers };
197ff4fbdf5SAlan Somers 
198ff4fbdf5SAlan Somers class Rename: public DefaultPermissions {
199ff4fbdf5SAlan Somers public:
200ff4fbdf5SAlan Somers 	/*
201ff4fbdf5SAlan Somers 	 * Expect a rename and respond with the given error.  Don't both to
202ff4fbdf5SAlan Somers 	 * validate arguments; the tests in rename.cc do that.
203ff4fbdf5SAlan Somers 	 */
204ff4fbdf5SAlan Somers 	void expect_rename(int error)
205ff4fbdf5SAlan Somers 	{
206ff4fbdf5SAlan Somers 		EXPECT_CALL(*m_mock, process(
207ff4fbdf5SAlan Somers 			ResultOf([=](auto in) {
208ff4fbdf5SAlan Somers 				return (in->header.opcode == FUSE_RENAME);
209ff4fbdf5SAlan Somers 			}, Eq(true)),
210ff4fbdf5SAlan Somers 			_)
211ff4fbdf5SAlan Somers 		).WillOnce(Invoke(ReturnErrno(error)));
212ff4fbdf5SAlan Somers 	}
213ff4fbdf5SAlan Somers };
214ff4fbdf5SAlan Somers 
215ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions {
216ff4fbdf5SAlan Somers public:
217ff4fbdf5SAlan Somers void expect_setxattr(int error)
218ff4fbdf5SAlan Somers {
219ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
220ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
221ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETXATTR);
222ff4fbdf5SAlan Somers 		}, Eq(true)),
223ff4fbdf5SAlan Somers 		_)
224ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
225ff4fbdf5SAlan Somers }
226ff4fbdf5SAlan Somers };
227ff4fbdf5SAlan Somers 
228ff4fbdf5SAlan Somers TEST_F(Access, eacces)
2299821f1d3SAlan Somers {
2309821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2319821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2329821f1d3SAlan Somers 	uint64_t ino = 42;
2339821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
2349821f1d3SAlan Somers 
235ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
236ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
237ff4fbdf5SAlan Somers 
238ff4fbdf5SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
239ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
240ff4fbdf5SAlan Somers }
241ff4fbdf5SAlan Somers 
242ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs)
243ff4fbdf5SAlan Somers {
244ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
245ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
246ff4fbdf5SAlan Somers 	uint64_t ino = 42;
247ff4fbdf5SAlan Somers 	mode_t	access_mode = X_OK;
248ff4fbdf5SAlan Somers 
249ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, 0, 1);
250ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
251ff4fbdf5SAlan Somers 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
2529821f1d3SAlan Somers 	/*
2539821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
2549821f1d3SAlan Somers 	 * another FUSE_GETATTR or something in here.  But there should not be
2559821f1d3SAlan Somers 	 * a FUSE_ACCESS
2569821f1d3SAlan Somers 	 */
2579821f1d3SAlan Somers 
2589821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
2599821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
2609821f1d3SAlan Somers }
2619821f1d3SAlan Somers 
262ff4fbdf5SAlan Somers TEST_F(Access, ok)
2639821f1d3SAlan Somers {
2649821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2659821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2669821f1d3SAlan Somers 	uint64_t ino = 42;
2679821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
2689821f1d3SAlan Somers 
269ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
270ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
2719821f1d3SAlan Somers 	/*
2729821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
27391ff3a0dSAlan Somers 	 * another FUSE_GETATTR or something in here.
2749821f1d3SAlan Somers 	 */
2759821f1d3SAlan Somers 
2769821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
2779821f1d3SAlan Somers }
2789821f1d3SAlan Somers 
279474ba6faSAlan Somers /* Only root may change a file's owner */
280474ba6faSAlan Somers TEST_F(Chown, eperm)
281474ba6faSAlan Somers {
282474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
283474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
284474ba6faSAlan Somers 	const uint64_t ino = 42;
285474ba6faSAlan Somers 	const mode_t mode = 0755;
286474ba6faSAlan Somers 
287474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
288474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
289474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
290474ba6faSAlan Somers 		ResultOf([](auto in) {
291474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
292474ba6faSAlan Somers 		}, Eq(true)),
293474ba6faSAlan Somers 		_)
294474ba6faSAlan Somers 	).Times(0);
295474ba6faSAlan Somers 
296474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
297474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
298474ba6faSAlan Somers }
299474ba6faSAlan Somers 
300474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */
301474ba6faSAlan Somers TEST_F(Chgrp, eperm)
302474ba6faSAlan Somers {
303474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
304474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
305474ba6faSAlan Somers 	const uint64_t ino = 42;
306474ba6faSAlan Somers 	const mode_t mode = 0755;
307474ba6faSAlan Somers 	int ngroups = 64;
308474ba6faSAlan Somers 	gid_t groups[ngroups];
309474ba6faSAlan Somers 	uid_t uid;
310474ba6faSAlan Somers 	gid_t gid, newgid;
311474ba6faSAlan Somers 	int i;
312474ba6faSAlan Somers 
313474ba6faSAlan Somers 	uid = geteuid();
314474ba6faSAlan Somers 	gid = getegid();
315474ba6faSAlan Somers 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
316474ba6faSAlan Somers 	for (newgid = 0; newgid >= 0; newgid++) {
317474ba6faSAlan Somers 		bool belongs = false;
318474ba6faSAlan Somers 
319474ba6faSAlan Somers 		for (i = 0; i < ngroups; i++) {
320474ba6faSAlan Somers 			if (groups[i] == newgid)
321474ba6faSAlan Somers 				belongs = true;
322474ba6faSAlan Somers 		}
323474ba6faSAlan Somers 		if (!belongs)
324474ba6faSAlan Somers 			break;
325474ba6faSAlan Somers 	}
326474ba6faSAlan Somers 	/* newgid is now a group to which the current user does not belong */
327474ba6faSAlan Somers 
328474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
329474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
330474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
331474ba6faSAlan Somers 		ResultOf([](auto in) {
332474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
333474ba6faSAlan Somers 		}, Eq(true)),
334474ba6faSAlan Somers 		_)
335474ba6faSAlan Somers 	).Times(0);
336474ba6faSAlan Somers 
337474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
338474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
339474ba6faSAlan Somers }
340474ba6faSAlan Somers 
341474ba6faSAlan Somers TEST_F(Chgrp, ok)
342474ba6faSAlan Somers {
343474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
344474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
345474ba6faSAlan Somers 	const uint64_t ino = 42;
346474ba6faSAlan Somers 	const mode_t mode = 0755;
347474ba6faSAlan Somers 	uid_t uid;
348474ba6faSAlan Somers 	gid_t gid, newgid;
349474ba6faSAlan Somers 
350474ba6faSAlan Somers 	uid = geteuid();
351474ba6faSAlan Somers 	gid = 0;
352474ba6faSAlan Somers 	newgid = getegid();
353474ba6faSAlan Somers 
354474ba6faSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
355474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
356474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
357474ba6faSAlan Somers 		ResultOf([](auto in) {
358474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
359474ba6faSAlan Somers 		}, Eq(true)),
360474ba6faSAlan Somers 		_)
361474ba6faSAlan Somers 	).Times(0);
362474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
363474ba6faSAlan Somers 		ResultOf([](auto in) {
364474ba6faSAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
365474ba6faSAlan Somers 				in->header.nodeid == ino);
366474ba6faSAlan Somers 		}, Eq(true)),
367474ba6faSAlan Somers 		_)
368474ba6faSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
369474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
370474ba6faSAlan Somers 		out->body.attr.attr.mode = S_IFREG | mode;
371474ba6faSAlan Somers 		out->body.attr.attr.uid = uid;
372474ba6faSAlan Somers 		out->body.attr.attr.gid = newgid;
373474ba6faSAlan Somers 	})));
374474ba6faSAlan Somers 
375474ba6faSAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
376474ba6faSAlan Somers }
377474ba6faSAlan Somers 
378ff4fbdf5SAlan Somers TEST_F(Create, ok)
379ff4fbdf5SAlan Somers {
380ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
381ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
382ff4fbdf5SAlan Somers 	uint64_t ino = 42;
383ff4fbdf5SAlan Somers 	int fd;
384ff4fbdf5SAlan Somers 
385ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
386ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
387ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
388ff4fbdf5SAlan Somers 
389ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
390ff4fbdf5SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
391ff4fbdf5SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
392ff4fbdf5SAlan Somers }
393ff4fbdf5SAlan Somers 
394ff4fbdf5SAlan Somers TEST_F(Create, eacces)
395ff4fbdf5SAlan Somers {
396ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
397ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
398ff4fbdf5SAlan Somers 
399ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
400ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
401ff4fbdf5SAlan Somers 
402ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
403ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
404ff4fbdf5SAlan Somers }
405ff4fbdf5SAlan Somers 
406ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
407ff4fbdf5SAlan Somers {
408ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
409ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
410ff4fbdf5SAlan Somers 	uint64_t ino = 42;
411ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
412ff4fbdf5SAlan Somers 
413ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
414ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
415ff4fbdf5SAlan Somers 
416ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
417ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
418ff4fbdf5SAlan Somers }
419ff4fbdf5SAlan Somers 
420ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
421ff4fbdf5SAlan Somers {
422ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
423ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
424ff4fbdf5SAlan Somers 	uint64_t ino = 42;
425ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
426ff4fbdf5SAlan Somers 
427ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
428ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
429ff4fbdf5SAlan Somers 	expect_removexattr();
430ff4fbdf5SAlan Somers 
431ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
432ff4fbdf5SAlan Somers 		<< strerror(errno);
433ff4fbdf5SAlan Somers }
434ff4fbdf5SAlan Somers 
435ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
436ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
437ff4fbdf5SAlan Somers {
438ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
439ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
440ff4fbdf5SAlan Somers 	uint64_t ino = 42;
441ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
442ff4fbdf5SAlan Somers 
443ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
444ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
445ff4fbdf5SAlan Somers 
446ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
447ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
448ff4fbdf5SAlan Somers }
449ff4fbdf5SAlan Somers 
450ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
451ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
452ff4fbdf5SAlan Somers {
453ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
454ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
455ff4fbdf5SAlan Somers 	uint64_t ino = 42;
456ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
457ff4fbdf5SAlan Somers 
458ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
459ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
460ff4fbdf5SAlan Somers 	expect_removexattr();
461ff4fbdf5SAlan Somers 
462ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
463ff4fbdf5SAlan Somers 		<< strerror(errno);
464ff4fbdf5SAlan Somers }
465ff4fbdf5SAlan Somers 
466ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
467ff4fbdf5SAlan Somers {
468ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
469ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
470ff4fbdf5SAlan Somers 	uint64_t ino = 42;
471ff4fbdf5SAlan Somers 	char data[80];
472ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
473ff4fbdf5SAlan Somers 
474ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
475ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
476ff4fbdf5SAlan Somers 
477ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
478ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
479ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
480ff4fbdf5SAlan Somers }
481ff4fbdf5SAlan Somers 
482ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
483ff4fbdf5SAlan Somers {
484ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
485ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
486ff4fbdf5SAlan Somers 	uint64_t ino = 42;
487ff4fbdf5SAlan Somers 	char data[80];
488ff4fbdf5SAlan Somers 	const char value[] = "whatever";
489ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
490ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
491ff4fbdf5SAlan Somers 	ssize_t r;
492ff4fbdf5SAlan Somers 
493ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
494ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
495ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
496ff4fbdf5SAlan Somers 	expect_getxattr(
497ff4fbdf5SAlan Somers 		ReturnImmediate([&](auto in __unused, auto out) {
498ff4fbdf5SAlan Somers 			memcpy((void*)out->body.bytes, value, value_len);
499ff4fbdf5SAlan Somers 			out->header.len = sizeof(out->header) + value_len;
500ff4fbdf5SAlan Somers 		})
501ff4fbdf5SAlan Somers 	);
502ff4fbdf5SAlan Somers 
503ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
504ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
505ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
506ff4fbdf5SAlan Somers }
507ff4fbdf5SAlan Somers 
508ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
509ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
510ff4fbdf5SAlan Somers {
511ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
512ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
513ff4fbdf5SAlan Somers 	uint64_t ino = 42;
514ff4fbdf5SAlan Somers 	char data[80];
515ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
516ff4fbdf5SAlan Somers 
517ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
518ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
519ff4fbdf5SAlan Somers 
520ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
521ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
522ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
523ff4fbdf5SAlan Somers }
524ff4fbdf5SAlan Somers 
525ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
526ff4fbdf5SAlan Somers {
527ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
528ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
529ff4fbdf5SAlan Somers 	uint64_t ino = 42;
530ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
531ff4fbdf5SAlan Somers 
532ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
533ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
534ff4fbdf5SAlan Somers 
535ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
536ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
537ff4fbdf5SAlan Somers }
538ff4fbdf5SAlan Somers 
539ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
540ff4fbdf5SAlan Somers {
541ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
542ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
543ff4fbdf5SAlan Somers 	uint64_t ino = 42;
544ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
545ff4fbdf5SAlan Somers 
546ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
547ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
548ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
549ff4fbdf5SAlan Somers 	expect_listxattr();
550ff4fbdf5SAlan Somers 
551ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
552ff4fbdf5SAlan Somers 		<< strerror(errno);
553ff4fbdf5SAlan Somers }
554ff4fbdf5SAlan Somers 
555ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
556ff4fbdf5SAlan Somers TEST_F(Listextattr, system)
557ff4fbdf5SAlan Somers {
558ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
559ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
560ff4fbdf5SAlan Somers 	uint64_t ino = 42;
561ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
562ff4fbdf5SAlan Somers 
563ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
564ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
565ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
566ff4fbdf5SAlan Somers 
567ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
568ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
569ff4fbdf5SAlan Somers }
570ff4fbdf5SAlan Somers 
571ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
572ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
573ff4fbdf5SAlan Somers {
574ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
575ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
576ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
577ff4fbdf5SAlan Somers 
578ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
579ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
580ff4fbdf5SAlan Somers 
581ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
582ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
583ff4fbdf5SAlan Somers }
584ff4fbdf5SAlan Somers 
585ff4fbdf5SAlan Somers TEST_F(Open, eacces)
586ff4fbdf5SAlan Somers {
587ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
588ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
589ff4fbdf5SAlan Somers 	uint64_t ino = 42;
590ff4fbdf5SAlan Somers 
591ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
592ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
593ff4fbdf5SAlan Somers 
594ff4fbdf5SAlan Somers 	EXPECT_NE(0, open(FULLPATH, O_RDWR));
595ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
596ff4fbdf5SAlan Somers }
597ff4fbdf5SAlan Somers 
5989821f1d3SAlan Somers TEST_F(Open, ok)
5999821f1d3SAlan Somers {
6009821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
6019821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
6029821f1d3SAlan Somers 	uint64_t ino = 42;
6039821f1d3SAlan Somers 	int fd;
6049821f1d3SAlan Somers 
605ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
606ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
6079821f1d3SAlan Somers 	expect_open(ino, 0, 1);
6089821f1d3SAlan Somers 
6099821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
6109821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
6119821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
6129821f1d3SAlan Somers }
6139821f1d3SAlan Somers 
614ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
615ff4fbdf5SAlan Somers {
616ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
617ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
618ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
619ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
620ff4fbdf5SAlan Somers 	uint64_t ino = 42;
621ff4fbdf5SAlan Somers 
622ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
623ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
624ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELDST)
625ff4fbdf5SAlan Somers 		.Times(AnyNumber())
626ff4fbdf5SAlan Somers 		.WillRepeatedly(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_creating)
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 
651ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
652ff4fbdf5SAlan Somers {
653ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
654ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
655ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
656ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
657ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
658ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
659ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
660ff4fbdf5SAlan Somers 
661ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
662ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
663ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
664ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
665ff4fbdf5SAlan Somers 
666ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
667ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
668ff4fbdf5SAlan Somers }
669ff4fbdf5SAlan Somers 
6706124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
671ff4fbdf5SAlan Somers {
672ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
673ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
674ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
675ff4fbdf5SAlan Somers 	uint64_t ino = 42;
676ff4fbdf5SAlan Somers 
677ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
678ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
679ff4fbdf5SAlan Somers 
680ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
681ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
682ff4fbdf5SAlan Somers }
683ff4fbdf5SAlan Somers 
6846124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
685ff4fbdf5SAlan Somers {
686ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
687ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
6886124fd71SAlan Somers 	const char RELDST[] = "dst";
689ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
690ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
691ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
692ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
693ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
694ff4fbdf5SAlan Somers 
695ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
696ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
697ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
6986124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
6996124fd71SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
7006124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
7016124fd71SAlan Somers 		out->body.entry.attr.mode = S_IFREG | 0644;
7026124fd71SAlan Somers 		out->body.entry.nodeid = dst_ino;
7036124fd71SAlan Somers 		out->body.entry.attr_valid = UINT64_MAX;
7046124fd71SAlan Somers 		out->body.entry.entry_valid = UINT64_MAX;
7056124fd71SAlan Somers 		out->body.entry.attr.uid = 0;
7066124fd71SAlan Somers 	})));
707ff4fbdf5SAlan Somers 
708ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
709ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
710ff4fbdf5SAlan Somers }
711ff4fbdf5SAlan Somers 
712ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
713ff4fbdf5SAlan Somers TEST_F(Rename, ok)
714ff4fbdf5SAlan Somers {
715ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
716ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
717ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
718ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
719ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
720ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
721ff4fbdf5SAlan Somers 	uint64_t ino = 42;
722ff4fbdf5SAlan Somers 
723ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
724ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
725ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
726ff4fbdf5SAlan Somers 	expect_rename(0);
727ff4fbdf5SAlan Somers 
728ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
729ff4fbdf5SAlan Somers }
730ff4fbdf5SAlan Somers 
731ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
732ff4fbdf5SAlan Somers {
733ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
734ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
735ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
736ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
737ff4fbdf5SAlan Somers 	uint64_t ino = 42;
738ff4fbdf5SAlan Somers 
739ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
740ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
741ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
742ff4fbdf5SAlan Somers 	expect_rename(0);
743ff4fbdf5SAlan Somers 
744ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
745ff4fbdf5SAlan Somers }
746ff4fbdf5SAlan Somers 
747ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
748ff4fbdf5SAlan Somers {
749ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
750ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
751ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
752ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
753ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
754ff4fbdf5SAlan Somers 
755ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
756ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
757ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
758ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
759ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETATTR &&
760ff4fbdf5SAlan Somers 				in->header.nodeid == ino &&
761ff4fbdf5SAlan Somers 				in->body.setattr.mode == newmode);
762ff4fbdf5SAlan Somers 		}, Eq(true)),
763ff4fbdf5SAlan Somers 		_)
764ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
765ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
766ff4fbdf5SAlan Somers 		out->body.attr.attr.mode = S_IFREG | newmode;
767ff4fbdf5SAlan Somers 	})));
768ff4fbdf5SAlan Somers 
769ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
770ff4fbdf5SAlan Somers }
771ff4fbdf5SAlan Somers 
772ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
773ff4fbdf5SAlan Somers {
774ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
775ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
776ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
777ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
778ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
779ff4fbdf5SAlan Somers 
780ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
781ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
782ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
783ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
784ff4fbdf5SAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
785ff4fbdf5SAlan Somers 		}, Eq(true)),
786ff4fbdf5SAlan Somers 		_)
787ff4fbdf5SAlan Somers 	).Times(0);
788ff4fbdf5SAlan Somers 
789ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
790ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
791ff4fbdf5SAlan Somers }
792ff4fbdf5SAlan Somers 
793e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
794e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
795e5ff3a7eSAlan Somers {
796e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
797e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
798e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
799e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
800e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
801e5ff3a7eSAlan Somers 
802e5ff3a7eSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
803e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
804e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
805e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
806e5ff3a7eSAlan Somers 			return (in->header.opcode == FUSE_SETATTR);
807e5ff3a7eSAlan Somers 		}, Eq(true)),
808e5ff3a7eSAlan Somers 		_)
809e5ff3a7eSAlan Somers 	).Times(0);
810e5ff3a7eSAlan Somers 
811e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
812e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
813e5ff3a7eSAlan Somers }
814e5ff3a7eSAlan Somers 
815ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
816ff4fbdf5SAlan Somers {
817ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
818ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
819ff4fbdf5SAlan Somers 	uint64_t ino = 42;
820ff4fbdf5SAlan Somers 	const char value[] = "whatever";
821ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
822ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
823ff4fbdf5SAlan Somers 	ssize_t r;
824ff4fbdf5SAlan Somers 
825ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
826ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
827ff4fbdf5SAlan Somers 	expect_setxattr(0);
828ff4fbdf5SAlan Somers 
829ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
830ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
831ff4fbdf5SAlan Somers }
832ff4fbdf5SAlan Somers 
833ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
834ff4fbdf5SAlan Somers {
835ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
836ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
837ff4fbdf5SAlan Somers 	uint64_t ino = 42;
838ff4fbdf5SAlan Somers 	const char value[] = "whatever";
839ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
840ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
841ff4fbdf5SAlan Somers 
842ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
843ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
844ff4fbdf5SAlan Somers 
845ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
846ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
847ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
848ff4fbdf5SAlan Somers }
849ff4fbdf5SAlan Somers 
850ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
851ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
852ff4fbdf5SAlan Somers {
853ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
854ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
855ff4fbdf5SAlan Somers 	uint64_t ino = 42;
856ff4fbdf5SAlan Somers 	const char value[] = "whatever";
857ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
858ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
859ff4fbdf5SAlan Somers 
860ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
861ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
862ff4fbdf5SAlan Somers 
863ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
864ff4fbdf5SAlan Somers 		extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
865ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
866ff4fbdf5SAlan Somers }
867ff4fbdf5SAlan Somers 
868ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
869ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
870ff4fbdf5SAlan Somers {
871ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
872ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
873ff4fbdf5SAlan Somers 	uint64_t ino = 42;
874ff4fbdf5SAlan Somers 	const char value[] = "whatever";
875ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
876ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
877ff4fbdf5SAlan Somers 	ssize_t r;
878ff4fbdf5SAlan Somers 
879ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
880ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
881ff4fbdf5SAlan Somers 	expect_setxattr(0);
882ff4fbdf5SAlan Somers 
883ff4fbdf5SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
884ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
885ff4fbdf5SAlan Somers }
886ff4fbdf5SAlan Somers 
887ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
8889821f1d3SAlan Somers {
8899821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
8909821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
8919821f1d3SAlan Somers 	uint64_t ino = 42;
8929821f1d3SAlan Somers 
893ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
894ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
895ff4fbdf5SAlan Somers 	expect_unlink(1, RELPATH, 0);
8969821f1d3SAlan Somers 
897ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
898ff4fbdf5SAlan Somers }
899ff4fbdf5SAlan Somers 
9006124fd71SAlan Somers /*
9016124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
9026124fd71SAlan Somers  * in VOP_LOOKUP.
9036124fd71SAlan Somers  *
9046124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
9056124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
9066124fd71SAlan Somers  */
9076124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
9086124fd71SAlan Somers {
9096124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
9106124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
9116124fd71SAlan Somers 	uint64_t ino = 42;
9126124fd71SAlan Somers 
9136124fd71SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
9146124fd71SAlan Somers 	EXPECT_LOOKUP(1, RELPATH)
9156124fd71SAlan Somers 	.Times(AnyNumber())
9166124fd71SAlan Somers 	.WillRepeatedly(Invoke(
9176124fd71SAlan Somers 		ReturnImmediate([=](auto i __unused, auto out) {
9186124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
9196124fd71SAlan Somers 			out->body.entry.attr.mode = S_IFREG | 0644;
9206124fd71SAlan Somers 			out->body.entry.nodeid = ino;
9216124fd71SAlan Somers 			out->body.entry.entry_valid = UINT64_MAX;
9226124fd71SAlan Somers 		}))
9236124fd71SAlan Somers 	);
9246124fd71SAlan Somers 
9256124fd71SAlan Somers 	/* Fill name cache */
9266124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
9276124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
9286124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
9296124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
9306124fd71SAlan Somers }
9316124fd71SAlan Somers 
932ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
933ff4fbdf5SAlan Somers {
934ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
935ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
936ff4fbdf5SAlan Somers 	uint64_t ino = 42;
937ff4fbdf5SAlan Somers 
938ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
939ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
940ff4fbdf5SAlan Somers 
941ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
942ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
943ff4fbdf5SAlan Somers }
944ff4fbdf5SAlan Somers 
9456124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
946ff4fbdf5SAlan Somers {
947ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
948ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
949ff4fbdf5SAlan Somers 	uint64_t ino = 42;
950ff4fbdf5SAlan Somers 
951ff4fbdf5SAlan Somers 	expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
952ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
953ff4fbdf5SAlan Somers 
954ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
955ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
9569821f1d3SAlan Somers }
957*a90e32deSAlan Somers 
958*a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
959*a90e32deSAlan Somers TEST_F(Write, clear_suid)
960*a90e32deSAlan Somers {
961*a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
962*a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
963*a90e32deSAlan Somers 	struct stat sb;
964*a90e32deSAlan Somers 	uint64_t ino = 42;
965*a90e32deSAlan Somers 	mode_t oldmode = 04777;
966*a90e32deSAlan Somers 	mode_t newmode = 0777;
967*a90e32deSAlan Somers 	char wbuf[1] = {'x'};
968*a90e32deSAlan Somers 	int fd;
969*a90e32deSAlan Somers 
970*a90e32deSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
971*a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
972*a90e32deSAlan Somers 	expect_open(ino, 0, 1);
973*a90e32deSAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
974*a90e32deSAlan Somers 	expect_chmod(ino, newmode);
975*a90e32deSAlan Somers 
976*a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
977*a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
978*a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
979*a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
980*a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
981*a90e32deSAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
982*a90e32deSAlan Somers }
983*a90e32deSAlan Somers 
984*a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
985*a90e32deSAlan Somers TEST_F(Write, clear_sgid)
986*a90e32deSAlan Somers {
987*a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
988*a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
989*a90e32deSAlan Somers 	struct stat sb;
990*a90e32deSAlan Somers 	uint64_t ino = 42;
991*a90e32deSAlan Somers 	mode_t oldmode = 02777;
992*a90e32deSAlan Somers 	mode_t newmode = 0777;
993*a90e32deSAlan Somers 	char wbuf[1] = {'x'};
994*a90e32deSAlan Somers 	int fd;
995*a90e32deSAlan Somers 
996*a90e32deSAlan Somers 	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
997*a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
998*a90e32deSAlan Somers 	expect_open(ino, 0, 1);
999*a90e32deSAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1000*a90e32deSAlan Somers 	expect_chmod(ino, newmode);
1001*a90e32deSAlan Somers 
1002*a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1003*a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1004*a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1005*a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1006*a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1007*a90e32deSAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1008*a90e32deSAlan Somers }
1009