xref: /freebsd/tests/sys/fs/fusefs/allow_other.cc (revision 4d846d260e2b9a3d4d0a701462568268cbfe7a5b)
191ff3a0dSAlan Somers /*-
2*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
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.
291fa8ebfbSAlan Somers  *
301fa8ebfbSAlan Somers  * $FreeBSD$
3191ff3a0dSAlan Somers  */
3291ff3a0dSAlan Somers 
3391ff3a0dSAlan Somers /*
3491ff3a0dSAlan Somers  * Tests for the "allow_other" mount option.  They must be in their own
3591ff3a0dSAlan Somers  * file so they can be run as root
3691ff3a0dSAlan Somers  */
3791ff3a0dSAlan Somers 
3891ff3a0dSAlan Somers extern "C" {
3991ff3a0dSAlan Somers #include <sys/types.h>
40666f8543SAlan Somers #include <sys/extattr.h>
41a1542146SAlan Somers #include <sys/wait.h>
4291ff3a0dSAlan Somers #include <fcntl.h>
4309c01e67SAlan Somers #include <unistd.h>
4491ff3a0dSAlan Somers }
4591ff3a0dSAlan Somers 
4691ff3a0dSAlan Somers #include "mockfs.hh"
4791ff3a0dSAlan Somers #include "utils.hh"
4891ff3a0dSAlan Somers 
4991ff3a0dSAlan Somers using namespace testing;
5091ff3a0dSAlan Somers 
5109c01e67SAlan Somers const static char FULLPATH[] = "mountpoint/some_file.txt";
5209c01e67SAlan Somers const static char RELPATH[] = "some_file.txt";
5391ff3a0dSAlan Somers 
5491ff3a0dSAlan Somers class NoAllowOther: public FuseTest {
5591ff3a0dSAlan Somers 
5691ff3a0dSAlan Somers public:
5791ff3a0dSAlan Somers /* Unprivileged user id */
5891ff3a0dSAlan Somers int m_uid;
5991ff3a0dSAlan Somers 
6091ff3a0dSAlan Somers virtual void SetUp() {
6191ff3a0dSAlan Somers 	if (geteuid() != 0) {
6291ff3a0dSAlan Somers 		GTEST_SKIP() << "This test must be run as root";
6391ff3a0dSAlan Somers 	}
6491ff3a0dSAlan Somers 
6591ff3a0dSAlan Somers 	FuseTest::SetUp();
6691ff3a0dSAlan Somers }
6791ff3a0dSAlan Somers };
6891ff3a0dSAlan Somers 
6991ff3a0dSAlan Somers class AllowOther: public NoAllowOther {
7091ff3a0dSAlan Somers 
7191ff3a0dSAlan Somers public:
7291ff3a0dSAlan Somers virtual void SetUp() {
7391ff3a0dSAlan Somers 	m_allow_other = true;
7491ff3a0dSAlan Somers 	NoAllowOther::SetUp();
7591ff3a0dSAlan Somers }
7691ff3a0dSAlan Somers };
7791ff3a0dSAlan Somers 
7891ff3a0dSAlan Somers TEST_F(AllowOther, allowed)
7991ff3a0dSAlan Somers {
80a1542146SAlan Somers 	int status;
81a1542146SAlan Somers 
82a1542146SAlan Somers 	fork(true, &status, [&] {
8391ff3a0dSAlan Somers 			uint64_t ino = 42;
8491ff3a0dSAlan Somers 
8591ff3a0dSAlan Somers 			expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
8691ff3a0dSAlan Somers 			expect_open(ino, 0, 1);
879f10f423SAlan Somers 			expect_flush(ino, 1, ReturnErrno(0));
8842d50d16SAlan Somers 			expect_release(ino, FH);
8909c01e67SAlan Somers 		}, []() {
9009c01e67SAlan Somers 			int fd;
9191ff3a0dSAlan Somers 
9209c01e67SAlan Somers 			fd = open(FULLPATH, O_RDONLY);
9309c01e67SAlan Somers 			if (fd < 0) {
9409c01e67SAlan Somers 				perror("open");
9509c01e67SAlan Somers 				return(1);
9691ff3a0dSAlan Somers 			}
974ac4b126SAlan Somers 
984ac4b126SAlan Somers 			leak(fd);
9909c01e67SAlan Somers 			return 0;
10009c01e67SAlan Somers 		}
10109c01e67SAlan Somers 	);
102a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
10391ff3a0dSAlan Somers }
10491ff3a0dSAlan Somers 
10561b0a927SAlan Somers /* Check that fusefs uses the correct credentials for FUSE operations */
10661b0a927SAlan Somers TEST_F(AllowOther, creds)
10761b0a927SAlan Somers {
10861b0a927SAlan Somers 	int status;
10961b0a927SAlan Somers 	uid_t uid;
11061b0a927SAlan Somers 	gid_t gid;
11161b0a927SAlan Somers 
11261b0a927SAlan Somers 	get_unprivileged_id(&uid, &gid);
11361b0a927SAlan Somers 	fork(true, &status, [=] {
11461b0a927SAlan Somers 			EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) {
11529edc611SAlan Somers 				return (in.header.opcode == FUSE_LOOKUP &&
11629edc611SAlan Somers 					in.header.uid == uid &&
11729edc611SAlan Somers 					in.header.gid == gid);
11861b0a927SAlan Somers 				}, Eq(true)),
11961b0a927SAlan Somers 				_)
12061b0a927SAlan Somers 			).Times(1)
12161b0a927SAlan Somers 			.WillOnce(Invoke(ReturnErrno(ENOENT)));
12261b0a927SAlan Somers 		}, []() {
12361b0a927SAlan Somers 			eaccess(FULLPATH, F_OK);
12461b0a927SAlan Somers 			return 0;
12561b0a927SAlan Somers 		}
12661b0a927SAlan Somers 	);
12761b0a927SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
12861b0a927SAlan Somers }
12961b0a927SAlan Somers 
13020807058SAlan Somers /*
13120807058SAlan Somers  * A variation of the Open.multiple_creds test showing how the bug can lead to a
13220807058SAlan Somers  * privilege elevation.  The first process is privileged and opens a file only
13320807058SAlan Somers  * visible to root.  The second process is unprivileged and shouldn't be able
13420807058SAlan Somers  * to open the file, but does thanks to the bug
13520807058SAlan Somers  */
136f8d4af10SAlan Somers TEST_F(AllowOther, privilege_escalation)
13720807058SAlan Somers {
138a1542146SAlan Somers 	int fd1, status;
13920807058SAlan Somers 	const static uint64_t ino = 42;
14020807058SAlan Somers 	const static uint64_t fh = 100;
14120807058SAlan Somers 
14220807058SAlan Somers 	/* Fork a child to open the file with different credentials */
143a1542146SAlan Somers 	fork(true, &status, [&] {
14420807058SAlan Somers 
14520807058SAlan Somers 		expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2);
14620807058SAlan Somers 		EXPECT_CALL(*m_mock, process(
14720807058SAlan Somers 			ResultOf([=](auto in) {
14829edc611SAlan Somers 				return (in.header.opcode == FUSE_OPEN &&
14929edc611SAlan Somers 					in.header.pid == (uint32_t)getpid() &&
15029edc611SAlan Somers 					in.header.uid == (uint32_t)geteuid() &&
15129edc611SAlan Somers 					in.header.nodeid == ino);
15220807058SAlan Somers 			}, Eq(true)),
15320807058SAlan Somers 			_)
15420807058SAlan Somers 		).WillOnce(Invoke(
15529edc611SAlan Somers 			ReturnImmediate([](auto in __unused, auto& out) {
15629edc611SAlan Somers 			out.body.open.fh = fh;
15729edc611SAlan Somers 			out.header.len = sizeof(out.header);
15820807058SAlan Somers 			SET_OUT_HEADER_LEN(out, open);
15920807058SAlan Somers 		})));
16020807058SAlan Somers 
16120807058SAlan Somers 		EXPECT_CALL(*m_mock, process(
16220807058SAlan Somers 			ResultOf([=](auto in) {
16329edc611SAlan Somers 				return (in.header.opcode == FUSE_OPEN &&
16429edc611SAlan Somers 					in.header.pid != (uint32_t)getpid() &&
16529edc611SAlan Somers 					in.header.uid != (uint32_t)geteuid() &&
16629edc611SAlan Somers 					in.header.nodeid == ino);
16720807058SAlan Somers 			}, Eq(true)),
16820807058SAlan Somers 			_)
16920807058SAlan Somers 		).Times(AnyNumber())
17020807058SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(EPERM)));
17120807058SAlan Somers 
17220807058SAlan Somers 		fd1 = open(FULLPATH, O_RDONLY);
173d2621689SAlan Somers 		ASSERT_LE(0, fd1) << strerror(errno);
17420807058SAlan Somers 	}, [] {
17520807058SAlan Somers 		int fd0;
17620807058SAlan Somers 
17720807058SAlan Somers 		fd0 = open(FULLPATH, O_RDONLY);
17820807058SAlan Somers 		if (fd0 >= 0) {
17920807058SAlan Somers 			fprintf(stderr, "Privilege escalation!\n");
18020807058SAlan Somers 			return 1;
18120807058SAlan Somers 		}
18220807058SAlan Somers 		if (errno != EPERM) {
18320807058SAlan Somers 			fprintf(stderr, "Unexpected error %s\n",
18420807058SAlan Somers 				strerror(errno));
18520807058SAlan Somers 			return 1;
18620807058SAlan Somers 		}
1877fc0921dSAlan Somers 		leak(fd0);
18820807058SAlan Somers 		return 0;
18920807058SAlan Somers 	}
19020807058SAlan Somers 	);
191a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
1927fc0921dSAlan Somers 	leak(fd1);
19320807058SAlan Somers }
19420807058SAlan Somers 
19591ff3a0dSAlan Somers TEST_F(NoAllowOther, disallowed)
19691ff3a0dSAlan Somers {
197a1542146SAlan Somers 	int status;
198a1542146SAlan Somers 
199a1542146SAlan Somers 	fork(true, &status, [] {
20009c01e67SAlan Somers 		}, []() {
20191ff3a0dSAlan Somers 			int fd;
20291ff3a0dSAlan Somers 
20391ff3a0dSAlan Somers 			fd = open(FULLPATH, O_RDONLY);
20491ff3a0dSAlan Somers 			if (fd >= 0) {
20591ff3a0dSAlan Somers 				fprintf(stderr, "open should've failed\n");
2064ac4b126SAlan Somers 				leak(fd);
20709c01e67SAlan Somers 				return(1);
20891ff3a0dSAlan Somers 			} else if (errno != EPERM) {
20909c01e67SAlan Somers 				fprintf(stderr, "Unexpected error: %s\n",
21009c01e67SAlan Somers 					strerror(errno));
21109c01e67SAlan Somers 				return(1);
21291ff3a0dSAlan Somers 			}
21309c01e67SAlan Somers 			return 0;
21491ff3a0dSAlan Somers 		}
21509c01e67SAlan Somers 	);
216a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
21791ff3a0dSAlan Somers }
218efa23d97SAlan Somers 
219efa23d97SAlan Somers /*
220efa23d97SAlan Somers  * When -o allow_other is not used, users other than the owner aren't allowed
221efa23d97SAlan Somers  * to open anything inside of the mount point, not just the mountpoint itself
222efa23d97SAlan Somers  * This is a regression test for bug 237052
223efa23d97SAlan Somers  */
224efa23d97SAlan Somers TEST_F(NoAllowOther, disallowed_beneath_root)
225efa23d97SAlan Somers {
226efa23d97SAlan Somers 	const static char RELPATH2[] = "other_dir";
227efa23d97SAlan Somers 	const static uint64_t ino = 42;
228efa23d97SAlan Somers 	const static uint64_t ino2 = 43;
229a1542146SAlan Somers 	int dfd, status;
230efa23d97SAlan Somers 
231efa23d97SAlan Somers 	expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1);
232efa23d97SAlan Somers 	EXPECT_LOOKUP(ino, RELPATH2)
23329edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
234efa23d97SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
23529edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
23629edc611SAlan Somers 		out.body.entry.nodeid = ino2;
23729edc611SAlan Somers 		out.body.entry.attr.nlink = 1;
23829edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
239efa23d97SAlan Somers 	})));
240efa23d97SAlan Somers 	expect_opendir(ino);
241efa23d97SAlan Somers 	dfd = open(FULLPATH, O_DIRECTORY);
242efa23d97SAlan Somers 	ASSERT_LE(0, dfd) << strerror(errno);
243efa23d97SAlan Somers 
244a1542146SAlan Somers 	fork(true, &status, [] {
245efa23d97SAlan Somers 		}, [&]() {
246efa23d97SAlan Somers 			int fd;
247efa23d97SAlan Somers 
248efa23d97SAlan Somers 			fd = openat(dfd, RELPATH2, O_RDONLY);
249efa23d97SAlan Somers 			if (fd >= 0) {
250efa23d97SAlan Somers 				fprintf(stderr, "openat should've failed\n");
2514ac4b126SAlan Somers 				leak(fd);
252efa23d97SAlan Somers 				return(1);
253efa23d97SAlan Somers 			} else if (errno != EPERM) {
254efa23d97SAlan Somers 				fprintf(stderr, "Unexpected error: %s\n",
255efa23d97SAlan Somers 					strerror(errno));
256efa23d97SAlan Somers 				return(1);
257efa23d97SAlan Somers 			}
258efa23d97SAlan Somers 			return 0;
259efa23d97SAlan Somers 		}
260efa23d97SAlan Somers 	);
261a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
2628e765737SAlan Somers 
2638e765737SAlan Somers 	leak(dfd);
264efa23d97SAlan Somers }
265666f8543SAlan Somers 
266666f8543SAlan Somers /*
267666f8543SAlan Somers  * Provide coverage for the extattr methods, which have a slightly different
268666f8543SAlan Somers  * code path
269666f8543SAlan Somers  */
270666f8543SAlan Somers TEST_F(NoAllowOther, setextattr)
271666f8543SAlan Somers {
272a1542146SAlan Somers 	int ino = 42, status;
273666f8543SAlan Somers 
274a1542146SAlan Somers 	fork(true, &status, [&] {
275a34cdd26SAlan Somers 			EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
276666f8543SAlan Somers 			.WillOnce(Invoke(
27729edc611SAlan Somers 			ReturnImmediate([=](auto in __unused, auto& out) {
278666f8543SAlan Somers 				SET_OUT_HEADER_LEN(out, entry);
27929edc611SAlan Somers 				out.body.entry.attr_valid = UINT64_MAX;
28029edc611SAlan Somers 				out.body.entry.entry_valid = UINT64_MAX;
28129edc611SAlan Somers 				out.body.entry.attr.mode = S_IFREG | 0644;
28229edc611SAlan Somers 				out.body.entry.nodeid = ino;
283666f8543SAlan Somers 			})));
284666f8543SAlan Somers 
285666f8543SAlan Somers 			/*
286666f8543SAlan Somers 			 * lookup the file to get it into the cache.
287666f8543SAlan Somers 			 * Otherwise, the unprivileged lookup will fail with
288666f8543SAlan Somers 			 * EACCES
289666f8543SAlan Somers 			 */
290666f8543SAlan Somers 			ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
291666f8543SAlan Somers 		}, [&]() {
292666f8543SAlan Somers 			const char value[] = "whatever";
293666f8543SAlan Somers 			ssize_t value_len = strlen(value) + 1;
294666f8543SAlan Somers 			int ns = EXTATTR_NAMESPACE_USER;
295666f8543SAlan Somers 			ssize_t r;
296666f8543SAlan Somers 
297666f8543SAlan Somers 			r = extattr_set_file(FULLPATH, ns, "foo",
2985a0b9a27SAlan Somers 				(const void*)value, value_len);
299666f8543SAlan Somers 			if (r >= 0) {
300666f8543SAlan Somers 				fprintf(stderr, "should've failed\n");
301666f8543SAlan Somers 				return(1);
302666f8543SAlan Somers 			} else if (errno != EPERM) {
303666f8543SAlan Somers 				fprintf(stderr, "Unexpected error: %s\n",
304666f8543SAlan Somers 					strerror(errno));
305666f8543SAlan Somers 				return(1);
306666f8543SAlan Somers 			}
307666f8543SAlan Somers 			return 0;
308666f8543SAlan Somers 		}
309666f8543SAlan Somers 	);
310a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
311666f8543SAlan Somers }
312