xref: /freebsd/tests/sys/fs/fusefs/allow_other.cc (revision efa23d97843bca76459a4aa9ffd35c55870014be)
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>
3891ff3a0dSAlan Somers #include <fcntl.h>
3909c01e67SAlan Somers #include <unistd.h>
4091ff3a0dSAlan Somers }
4191ff3a0dSAlan Somers 
4291ff3a0dSAlan Somers #include "mockfs.hh"
4391ff3a0dSAlan Somers #include "utils.hh"
4491ff3a0dSAlan Somers 
4591ff3a0dSAlan Somers using namespace testing;
4691ff3a0dSAlan Somers 
4709c01e67SAlan Somers const static char FULLPATH[] = "mountpoint/some_file.txt";
4809c01e67SAlan Somers const static char RELPATH[] = "some_file.txt";
4991ff3a0dSAlan Somers 
5091ff3a0dSAlan Somers class NoAllowOther: public FuseTest {
5191ff3a0dSAlan Somers 
5291ff3a0dSAlan Somers public:
5391ff3a0dSAlan Somers /* Unprivileged user id */
5491ff3a0dSAlan Somers int m_uid;
5591ff3a0dSAlan Somers 
5691ff3a0dSAlan Somers virtual void SetUp() {
5791ff3a0dSAlan Somers 	if (geteuid() != 0) {
5891ff3a0dSAlan Somers 		GTEST_SKIP() << "This test must be run as root";
5991ff3a0dSAlan Somers 	}
6091ff3a0dSAlan Somers 
6191ff3a0dSAlan Somers 	FuseTest::SetUp();
6291ff3a0dSAlan Somers }
6391ff3a0dSAlan Somers };
6491ff3a0dSAlan Somers 
6591ff3a0dSAlan Somers class AllowOther: public NoAllowOther {
6691ff3a0dSAlan Somers 
6791ff3a0dSAlan Somers public:
6891ff3a0dSAlan Somers virtual void SetUp() {
6991ff3a0dSAlan Somers 	m_allow_other = true;
7091ff3a0dSAlan Somers 	NoAllowOther::SetUp();
7191ff3a0dSAlan Somers }
7291ff3a0dSAlan Somers };
7391ff3a0dSAlan Somers 
7491ff3a0dSAlan Somers TEST_F(AllowOther, allowed)
7591ff3a0dSAlan Somers {
7609c01e67SAlan Somers 	fork(true, [&] {
7791ff3a0dSAlan Somers 			uint64_t ino = 42;
7891ff3a0dSAlan Somers 
7991ff3a0dSAlan Somers 			expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
8091ff3a0dSAlan Somers 			expect_open(ino, 0, 1);
819f10f423SAlan Somers 			expect_flush(ino, 1, ReturnErrno(0));
8242d50d16SAlan Somers 			expect_release(ino, FH);
8391ff3a0dSAlan Somers 			expect_getattr(ino, 0);
8409c01e67SAlan Somers 		}, []() {
8509c01e67SAlan Somers 			int fd;
8691ff3a0dSAlan Somers 
8709c01e67SAlan Somers 			fd = open(FULLPATH, O_RDONLY);
8809c01e67SAlan Somers 			if (fd < 0) {
8909c01e67SAlan Somers 				perror("open");
9009c01e67SAlan Somers 				return(1);
9191ff3a0dSAlan Somers 			}
9209c01e67SAlan Somers 			return 0;
9309c01e67SAlan Somers 		}
9409c01e67SAlan Somers 	);
9591ff3a0dSAlan Somers }
9691ff3a0dSAlan Somers 
9720807058SAlan Somers /*
9820807058SAlan Somers  * A variation of the Open.multiple_creds test showing how the bug can lead to a
9920807058SAlan Somers  * privilege elevation.  The first process is privileged and opens a file only
10020807058SAlan Somers  * visible to root.  The second process is unprivileged and shouldn't be able
10120807058SAlan Somers  * to open the file, but does thanks to the bug
10220807058SAlan Somers  */
103f8d4af10SAlan Somers TEST_F(AllowOther, privilege_escalation)
10420807058SAlan Somers {
10520807058SAlan Somers 	const static char FULLPATH[] = "mountpoint/some_file.txt";
10620807058SAlan Somers 	const static char RELPATH[] = "some_file.txt";
10720807058SAlan Somers 	int fd1;
10820807058SAlan Somers 	const static uint64_t ino = 42;
10920807058SAlan Somers 	const static uint64_t fh = 100;
11020807058SAlan Somers 
11120807058SAlan Somers 	/* Fork a child to open the file with different credentials */
11220807058SAlan Somers 	fork(true, [&] {
11320807058SAlan Somers 
11420807058SAlan Somers 		expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2);
11520807058SAlan Somers 		EXPECT_CALL(*m_mock, process(
11620807058SAlan Somers 			ResultOf([=](auto in) {
11720807058SAlan Somers 				return (in->header.opcode == FUSE_OPEN &&
11820807058SAlan Somers 					in->header.pid == (uint32_t)getpid() &&
11920807058SAlan Somers 					in->header.uid == (uint32_t)geteuid() &&
12020807058SAlan Somers 					in->header.nodeid == ino);
12120807058SAlan Somers 			}, Eq(true)),
12220807058SAlan Somers 			_)
12320807058SAlan Somers 		).WillOnce(Invoke(
12420807058SAlan Somers 			ReturnImmediate([](auto in __unused, auto out) {
12520807058SAlan Somers 			out->body.open.fh = fh;
12620807058SAlan Somers 			out->header.len = sizeof(out->header);
12720807058SAlan Somers 			SET_OUT_HEADER_LEN(out, open);
12820807058SAlan Somers 		})));
12920807058SAlan Somers 
13020807058SAlan Somers 		EXPECT_CALL(*m_mock, process(
13120807058SAlan Somers 			ResultOf([=](auto in) {
13220807058SAlan Somers 				return (in->header.opcode == FUSE_OPEN &&
13320807058SAlan Somers 					in->header.pid != (uint32_t)getpid() &&
13420807058SAlan Somers 					in->header.uid != (uint32_t)geteuid() &&
13520807058SAlan Somers 					in->header.nodeid == ino);
13620807058SAlan Somers 			}, Eq(true)),
13720807058SAlan Somers 			_)
13820807058SAlan Somers 		).Times(AnyNumber())
13920807058SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(EPERM)));
14020807058SAlan Somers 		expect_getattr(ino, 0);
14120807058SAlan Somers 
14220807058SAlan Somers 		fd1 = open(FULLPATH, O_RDONLY);
14320807058SAlan Somers 		EXPECT_LE(0, fd1) << strerror(errno);
14420807058SAlan Somers 	}, [] {
14520807058SAlan Somers 		int fd0;
14620807058SAlan Somers 
14720807058SAlan Somers 		fd0 = open(FULLPATH, O_RDONLY);
14820807058SAlan Somers 		if (fd0 >= 0) {
14920807058SAlan Somers 			fprintf(stderr, "Privilege escalation!\n");
15020807058SAlan Somers 			return 1;
15120807058SAlan Somers 		}
15220807058SAlan Somers 		if (errno != EPERM) {
15320807058SAlan Somers 			fprintf(stderr, "Unexpected error %s\n",
15420807058SAlan Somers 				strerror(errno));
15520807058SAlan Somers 			return 1;
15620807058SAlan Somers 		}
15720807058SAlan Somers 		return 0;
15820807058SAlan Somers 	}
15920807058SAlan Somers 	);
16020807058SAlan Somers 	/* Deliberately leak fd1.  close(2) will be tested in release.cc */
16120807058SAlan Somers }
16220807058SAlan Somers 
16391ff3a0dSAlan Somers TEST_F(NoAllowOther, disallowed)
16491ff3a0dSAlan Somers {
16509c01e67SAlan Somers 	fork(true, [] {
16609c01e67SAlan Somers 		}, []() {
16791ff3a0dSAlan Somers 			int fd;
16891ff3a0dSAlan Somers 
16991ff3a0dSAlan Somers 			fd = open(FULLPATH, O_RDONLY);
17091ff3a0dSAlan Somers 			if (fd >= 0) {
17191ff3a0dSAlan Somers 				fprintf(stderr, "open should've failed\n");
17209c01e67SAlan Somers 				return(1);
17391ff3a0dSAlan Somers 			} else if (errno != EPERM) {
17409c01e67SAlan Somers 				fprintf(stderr, "Unexpected error: %s\n",
17509c01e67SAlan Somers 					strerror(errno));
17609c01e67SAlan Somers 				return(1);
17791ff3a0dSAlan Somers 			}
17809c01e67SAlan Somers 			return 0;
17991ff3a0dSAlan Somers 		}
18009c01e67SAlan Somers 	);
18191ff3a0dSAlan Somers }
182*efa23d97SAlan Somers 
183*efa23d97SAlan Somers /*
184*efa23d97SAlan Somers  * When -o allow_other is not used, users other than the owner aren't allowed
185*efa23d97SAlan Somers  * to open anything inside of the mount point, not just the mountpoint itself
186*efa23d97SAlan Somers  * This is a regression test for bug 237052
187*efa23d97SAlan Somers  */
188*efa23d97SAlan Somers TEST_F(NoAllowOther, disallowed_beneath_root)
189*efa23d97SAlan Somers {
190*efa23d97SAlan Somers 	const static char FULLPATH[] = "mountpoint/some_dir";
191*efa23d97SAlan Somers 	const static char RELPATH[] = "some_dir";
192*efa23d97SAlan Somers 	const static char RELPATH2[] = "other_dir";
193*efa23d97SAlan Somers 	const static uint64_t ino = 42;
194*efa23d97SAlan Somers 	const static uint64_t ino2 = 43;
195*efa23d97SAlan Somers 	int dfd;
196*efa23d97SAlan Somers 
197*efa23d97SAlan Somers 	expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1);
198*efa23d97SAlan Somers 	EXPECT_LOOKUP(ino, RELPATH2)
199*efa23d97SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
200*efa23d97SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
201*efa23d97SAlan Somers 		out->body.entry.attr.mode = S_IFREG | 0644;
202*efa23d97SAlan Somers 		out->body.entry.nodeid = ino2;
203*efa23d97SAlan Somers 		out->body.entry.attr.nlink = 1;
204*efa23d97SAlan Somers 		out->body.entry.attr_valid = UINT64_MAX;
205*efa23d97SAlan Somers 	})));
206*efa23d97SAlan Somers 	expect_opendir(ino);
207*efa23d97SAlan Somers 	dfd = open(FULLPATH, O_DIRECTORY);
208*efa23d97SAlan Somers 	ASSERT_LE(0, dfd) << strerror(errno);
209*efa23d97SAlan Somers 
210*efa23d97SAlan Somers 	fork(true, [] {
211*efa23d97SAlan Somers 		}, [&]() {
212*efa23d97SAlan Somers 			int fd;
213*efa23d97SAlan Somers 
214*efa23d97SAlan Somers 			fd = openat(dfd, RELPATH2, O_RDONLY);
215*efa23d97SAlan Somers 			if (fd >= 0) {
216*efa23d97SAlan Somers 				fprintf(stderr, "openat should've failed\n");
217*efa23d97SAlan Somers 				return(1);
218*efa23d97SAlan Somers 			} else if (errno != EPERM) {
219*efa23d97SAlan Somers 				fprintf(stderr, "Unexpected error: %s\n",
220*efa23d97SAlan Somers 					strerror(errno));
221*efa23d97SAlan Somers 				return(1);
222*efa23d97SAlan Somers 			}
223*efa23d97SAlan Somers 			return 0;
224*efa23d97SAlan Somers 		}
225*efa23d97SAlan Somers 	);
226*efa23d97SAlan Somers }
227