xref: /freebsd/tests/sys/fs/fusefs/allow_other.cc (revision 29edc611c1de5413dc0a28056e26f9ec648dc413)
191ff3a0dSAlan Somers /*-
291ff3a0dSAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
391ff3a0dSAlan Somers  *
491ff3a0dSAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
591ff3a0dSAlan Somers  *
691ff3a0dSAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
791ff3a0dSAlan Somers  * from the FreeBSD Foundation.
891ff3a0dSAlan Somers  *
991ff3a0dSAlan Somers  * Redistribution and use in source and binary forms, with or without
1091ff3a0dSAlan Somers  * modification, are permitted provided that the following conditions
1191ff3a0dSAlan Somers  * are met:
1291ff3a0dSAlan Somers  * 1. Redistributions of source code must retain the above copyright
1391ff3a0dSAlan Somers  *    notice, this list of conditions and the following disclaimer.
1491ff3a0dSAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
1591ff3a0dSAlan Somers  *    notice, this list of conditions and the following disclaimer in the
1691ff3a0dSAlan Somers  *    documentation and/or other materials provided with the distribution.
1791ff3a0dSAlan Somers  *
1891ff3a0dSAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1991ff3a0dSAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2091ff3a0dSAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2191ff3a0dSAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2291ff3a0dSAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2391ff3a0dSAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2491ff3a0dSAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2591ff3a0dSAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2691ff3a0dSAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2791ff3a0dSAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2891ff3a0dSAlan Somers  * SUCH DAMAGE.
2991ff3a0dSAlan Somers  */
3091ff3a0dSAlan Somers 
3191ff3a0dSAlan Somers /*
3291ff3a0dSAlan Somers  * Tests for the "allow_other" mount option.  They must be in their own
3391ff3a0dSAlan Somers  * file so they can be run as root
3491ff3a0dSAlan Somers  */
3591ff3a0dSAlan Somers 
3691ff3a0dSAlan Somers extern "C" {
3791ff3a0dSAlan Somers #include <sys/types.h>
38666f8543SAlan Somers #include <sys/extattr.h>
39a1542146SAlan Somers #include <sys/wait.h>
4091ff3a0dSAlan Somers #include <fcntl.h>
4109c01e67SAlan Somers #include <unistd.h>
4291ff3a0dSAlan Somers }
4391ff3a0dSAlan Somers 
4491ff3a0dSAlan Somers #include "mockfs.hh"
4591ff3a0dSAlan Somers #include "utils.hh"
4691ff3a0dSAlan Somers 
4791ff3a0dSAlan Somers using namespace testing;
4891ff3a0dSAlan Somers 
4909c01e67SAlan Somers const static char FULLPATH[] = "mountpoint/some_file.txt";
5009c01e67SAlan Somers const static char RELPATH[] = "some_file.txt";
5191ff3a0dSAlan Somers 
5291ff3a0dSAlan Somers class NoAllowOther: public FuseTest {
5391ff3a0dSAlan Somers 
5491ff3a0dSAlan Somers public:
5591ff3a0dSAlan Somers /* Unprivileged user id */
5691ff3a0dSAlan Somers int m_uid;
5791ff3a0dSAlan Somers 
5891ff3a0dSAlan Somers virtual void SetUp() {
5991ff3a0dSAlan Somers 	if (geteuid() != 0) {
6091ff3a0dSAlan Somers 		GTEST_SKIP() << "This test must be run as root";
6191ff3a0dSAlan Somers 	}
6291ff3a0dSAlan Somers 
6391ff3a0dSAlan Somers 	FuseTest::SetUp();
6491ff3a0dSAlan Somers }
6591ff3a0dSAlan Somers };
6691ff3a0dSAlan Somers 
6791ff3a0dSAlan Somers class AllowOther: public NoAllowOther {
6891ff3a0dSAlan Somers 
6991ff3a0dSAlan Somers public:
7091ff3a0dSAlan Somers virtual void SetUp() {
7191ff3a0dSAlan Somers 	m_allow_other = true;
7291ff3a0dSAlan Somers 	NoAllowOther::SetUp();
7391ff3a0dSAlan Somers }
7491ff3a0dSAlan Somers };
7591ff3a0dSAlan Somers 
7691ff3a0dSAlan Somers TEST_F(AllowOther, allowed)
7791ff3a0dSAlan Somers {
78a1542146SAlan Somers 	int status;
79a1542146SAlan Somers 
80a1542146SAlan Somers 	fork(true, &status, [&] {
8191ff3a0dSAlan Somers 			uint64_t ino = 42;
8291ff3a0dSAlan Somers 
8391ff3a0dSAlan Somers 			expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
8491ff3a0dSAlan Somers 			expect_open(ino, 0, 1);
859f10f423SAlan Somers 			expect_flush(ino, 1, ReturnErrno(0));
8642d50d16SAlan Somers 			expect_release(ino, FH);
8709c01e67SAlan Somers 		}, []() {
8809c01e67SAlan Somers 			int fd;
8991ff3a0dSAlan Somers 
9009c01e67SAlan Somers 			fd = open(FULLPATH, O_RDONLY);
9109c01e67SAlan Somers 			if (fd < 0) {
9209c01e67SAlan Somers 				perror("open");
9309c01e67SAlan Somers 				return(1);
9491ff3a0dSAlan Somers 			}
9509c01e67SAlan Somers 			return 0;
9609c01e67SAlan Somers 		}
9709c01e67SAlan Somers 	);
98a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
9991ff3a0dSAlan Somers }
10091ff3a0dSAlan Somers 
10161b0a927SAlan Somers /* Check that fusefs uses the correct credentials for FUSE operations */
10261b0a927SAlan Somers TEST_F(AllowOther, creds)
10361b0a927SAlan Somers {
10461b0a927SAlan Somers 	int status;
10561b0a927SAlan Somers 	uid_t uid;
10661b0a927SAlan Somers 	gid_t gid;
10761b0a927SAlan Somers 
10861b0a927SAlan Somers 	get_unprivileged_id(&uid, &gid);
10961b0a927SAlan Somers 	fork(true, &status, [=] {
11061b0a927SAlan Somers 			EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) {
111*29edc611SAlan Somers 				return (in.header.opcode == FUSE_LOOKUP &&
112*29edc611SAlan Somers 					in.header.uid == uid &&
113*29edc611SAlan Somers 					in.header.gid == gid);
11461b0a927SAlan Somers 				}, Eq(true)),
11561b0a927SAlan Somers 				_)
11661b0a927SAlan Somers 			).Times(1)
11761b0a927SAlan Somers 			.WillOnce(Invoke(ReturnErrno(ENOENT)));
11861b0a927SAlan Somers 		}, []() {
11961b0a927SAlan Somers 			eaccess(FULLPATH, F_OK);
12061b0a927SAlan Somers 			return 0;
12161b0a927SAlan Somers 		}
12261b0a927SAlan Somers 	);
12361b0a927SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
12461b0a927SAlan Somers }
12561b0a927SAlan Somers 
12620807058SAlan Somers /*
12720807058SAlan Somers  * A variation of the Open.multiple_creds test showing how the bug can lead to a
12820807058SAlan Somers  * privilege elevation.  The first process is privileged and opens a file only
12920807058SAlan Somers  * visible to root.  The second process is unprivileged and shouldn't be able
13020807058SAlan Somers  * to open the file, but does thanks to the bug
13120807058SAlan Somers  */
132f8d4af10SAlan Somers TEST_F(AllowOther, privilege_escalation)
13320807058SAlan Somers {
13420807058SAlan Somers 	const static char FULLPATH[] = "mountpoint/some_file.txt";
13520807058SAlan Somers 	const static char RELPATH[] = "some_file.txt";
136a1542146SAlan Somers 	int fd1, status;
13720807058SAlan Somers 	const static uint64_t ino = 42;
13820807058SAlan Somers 	const static uint64_t fh = 100;
13920807058SAlan Somers 
14020807058SAlan Somers 	/* Fork a child to open the file with different credentials */
141a1542146SAlan Somers 	fork(true, &status, [&] {
14220807058SAlan Somers 
14320807058SAlan Somers 		expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2);
14420807058SAlan Somers 		EXPECT_CALL(*m_mock, process(
14520807058SAlan Somers 			ResultOf([=](auto in) {
146*29edc611SAlan Somers 				return (in.header.opcode == FUSE_OPEN &&
147*29edc611SAlan Somers 					in.header.pid == (uint32_t)getpid() &&
148*29edc611SAlan Somers 					in.header.uid == (uint32_t)geteuid() &&
149*29edc611SAlan Somers 					in.header.nodeid == ino);
15020807058SAlan Somers 			}, Eq(true)),
15120807058SAlan Somers 			_)
15220807058SAlan Somers 		).WillOnce(Invoke(
153*29edc611SAlan Somers 			ReturnImmediate([](auto in __unused, auto& out) {
154*29edc611SAlan Somers 			out.body.open.fh = fh;
155*29edc611SAlan Somers 			out.header.len = sizeof(out.header);
15620807058SAlan Somers 			SET_OUT_HEADER_LEN(out, open);
15720807058SAlan Somers 		})));
15820807058SAlan Somers 
15920807058SAlan Somers 		EXPECT_CALL(*m_mock, process(
16020807058SAlan Somers 			ResultOf([=](auto in) {
161*29edc611SAlan Somers 				return (in.header.opcode == FUSE_OPEN &&
162*29edc611SAlan Somers 					in.header.pid != (uint32_t)getpid() &&
163*29edc611SAlan Somers 					in.header.uid != (uint32_t)geteuid() &&
164*29edc611SAlan Somers 					in.header.nodeid == ino);
16520807058SAlan Somers 			}, Eq(true)),
16620807058SAlan Somers 			_)
16720807058SAlan Somers 		).Times(AnyNumber())
16820807058SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(EPERM)));
16920807058SAlan Somers 
17020807058SAlan Somers 		fd1 = open(FULLPATH, O_RDONLY);
17120807058SAlan Somers 		EXPECT_LE(0, fd1) << strerror(errno);
17220807058SAlan Somers 	}, [] {
17320807058SAlan Somers 		int fd0;
17420807058SAlan Somers 
17520807058SAlan Somers 		fd0 = open(FULLPATH, O_RDONLY);
17620807058SAlan Somers 		if (fd0 >= 0) {
17720807058SAlan Somers 			fprintf(stderr, "Privilege escalation!\n");
17820807058SAlan Somers 			return 1;
17920807058SAlan Somers 		}
18020807058SAlan Somers 		if (errno != EPERM) {
18120807058SAlan Somers 			fprintf(stderr, "Unexpected error %s\n",
18220807058SAlan Somers 				strerror(errno));
18320807058SAlan Somers 			return 1;
18420807058SAlan Somers 		}
18520807058SAlan Somers 		return 0;
18620807058SAlan Somers 	}
18720807058SAlan Somers 	);
188a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
18920807058SAlan Somers 	/* Deliberately leak fd1.  close(2) will be tested in release.cc */
19020807058SAlan Somers }
19120807058SAlan Somers 
19291ff3a0dSAlan Somers TEST_F(NoAllowOther, disallowed)
19391ff3a0dSAlan Somers {
194a1542146SAlan Somers 	int status;
195a1542146SAlan Somers 
196a1542146SAlan Somers 	fork(true, &status, [] {
19709c01e67SAlan Somers 		}, []() {
19891ff3a0dSAlan Somers 			int fd;
19991ff3a0dSAlan Somers 
20091ff3a0dSAlan Somers 			fd = open(FULLPATH, O_RDONLY);
20191ff3a0dSAlan Somers 			if (fd >= 0) {
20291ff3a0dSAlan Somers 				fprintf(stderr, "open should've failed\n");
20309c01e67SAlan Somers 				return(1);
20491ff3a0dSAlan Somers 			} else if (errno != EPERM) {
20509c01e67SAlan Somers 				fprintf(stderr, "Unexpected error: %s\n",
20609c01e67SAlan Somers 					strerror(errno));
20709c01e67SAlan Somers 				return(1);
20891ff3a0dSAlan Somers 			}
20909c01e67SAlan Somers 			return 0;
21091ff3a0dSAlan Somers 		}
21109c01e67SAlan Somers 	);
212a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
21391ff3a0dSAlan Somers }
214efa23d97SAlan Somers 
215efa23d97SAlan Somers /*
216efa23d97SAlan Somers  * When -o allow_other is not used, users other than the owner aren't allowed
217efa23d97SAlan Somers  * to open anything inside of the mount point, not just the mountpoint itself
218efa23d97SAlan Somers  * This is a regression test for bug 237052
219efa23d97SAlan Somers  */
220efa23d97SAlan Somers TEST_F(NoAllowOther, disallowed_beneath_root)
221efa23d97SAlan Somers {
222efa23d97SAlan Somers 	const static char FULLPATH[] = "mountpoint/some_dir";
223efa23d97SAlan Somers 	const static char RELPATH[] = "some_dir";
224efa23d97SAlan Somers 	const static char RELPATH2[] = "other_dir";
225efa23d97SAlan Somers 	const static uint64_t ino = 42;
226efa23d97SAlan Somers 	const static uint64_t ino2 = 43;
227a1542146SAlan Somers 	int dfd, status;
228efa23d97SAlan Somers 
229efa23d97SAlan Somers 	expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1);
230efa23d97SAlan Somers 	EXPECT_LOOKUP(ino, RELPATH2)
231*29edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
232efa23d97SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
233*29edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
234*29edc611SAlan Somers 		out.body.entry.nodeid = ino2;
235*29edc611SAlan Somers 		out.body.entry.attr.nlink = 1;
236*29edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
237efa23d97SAlan Somers 	})));
238efa23d97SAlan Somers 	expect_opendir(ino);
239efa23d97SAlan Somers 	dfd = open(FULLPATH, O_DIRECTORY);
240efa23d97SAlan Somers 	ASSERT_LE(0, dfd) << strerror(errno);
241efa23d97SAlan Somers 
242a1542146SAlan Somers 	fork(true, &status, [] {
243efa23d97SAlan Somers 		}, [&]() {
244efa23d97SAlan Somers 			int fd;
245efa23d97SAlan Somers 
246efa23d97SAlan Somers 			fd = openat(dfd, RELPATH2, O_RDONLY);
247efa23d97SAlan Somers 			if (fd >= 0) {
248efa23d97SAlan Somers 				fprintf(stderr, "openat should've failed\n");
249efa23d97SAlan Somers 				return(1);
250efa23d97SAlan Somers 			} else if (errno != EPERM) {
251efa23d97SAlan Somers 				fprintf(stderr, "Unexpected error: %s\n",
252efa23d97SAlan Somers 					strerror(errno));
253efa23d97SAlan Somers 				return(1);
254efa23d97SAlan Somers 			}
255efa23d97SAlan Somers 			return 0;
256efa23d97SAlan Somers 		}
257efa23d97SAlan Somers 	);
258a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
259efa23d97SAlan Somers }
260666f8543SAlan Somers 
261666f8543SAlan Somers /*
262666f8543SAlan Somers  * Provide coverage for the extattr methods, which have a slightly different
263666f8543SAlan Somers  * code path
264666f8543SAlan Somers  */
265666f8543SAlan Somers TEST_F(NoAllowOther, setextattr)
266666f8543SAlan Somers {
267a1542146SAlan Somers 	int ino = 42, status;
268666f8543SAlan Somers 
269a1542146SAlan Somers 	fork(true, &status, [&] {
270666f8543SAlan Somers 			EXPECT_LOOKUP(1, RELPATH)
271666f8543SAlan Somers 			.WillOnce(Invoke(
272*29edc611SAlan Somers 			ReturnImmediate([=](auto in __unused, auto& out) {
273666f8543SAlan Somers 				SET_OUT_HEADER_LEN(out, entry);
274*29edc611SAlan Somers 				out.body.entry.attr_valid = UINT64_MAX;
275*29edc611SAlan Somers 				out.body.entry.entry_valid = UINT64_MAX;
276*29edc611SAlan Somers 				out.body.entry.attr.mode = S_IFREG | 0644;
277*29edc611SAlan Somers 				out.body.entry.nodeid = ino;
278666f8543SAlan Somers 			})));
279666f8543SAlan Somers 
280666f8543SAlan Somers 			/*
281666f8543SAlan Somers 			 * lookup the file to get it into the cache.
282666f8543SAlan Somers 			 * Otherwise, the unprivileged lookup will fail with
283666f8543SAlan Somers 			 * EACCES
284666f8543SAlan Somers 			 */
285666f8543SAlan Somers 			ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
286666f8543SAlan Somers 		}, [&]() {
287666f8543SAlan Somers 			const char value[] = "whatever";
288666f8543SAlan Somers 			ssize_t value_len = strlen(value) + 1;
289666f8543SAlan Somers 			int ns = EXTATTR_NAMESPACE_USER;
290666f8543SAlan Somers 			ssize_t r;
291666f8543SAlan Somers 
292666f8543SAlan Somers 			r = extattr_set_file(FULLPATH, ns, "foo",
293666f8543SAlan Somers 				(void*)value, value_len);
294666f8543SAlan Somers 			if (r >= 0) {
295666f8543SAlan Somers 				fprintf(stderr, "should've failed\n");
296666f8543SAlan Somers 				return(1);
297666f8543SAlan Somers 			} else if (errno != EPERM) {
298666f8543SAlan Somers 				fprintf(stderr, "Unexpected error: %s\n",
299666f8543SAlan Somers 					strerror(errno));
300666f8543SAlan Somers 				return(1);
301666f8543SAlan Somers 			}
302666f8543SAlan Somers 			return 0;
303666f8543SAlan Somers 		}
304666f8543SAlan Somers 	);
305a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
306666f8543SAlan Somers }
307