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.
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:
SetUp()5591ff3a0dSAlan Somers virtual void SetUp() {
5691ff3a0dSAlan Somers if (geteuid() != 0) {
5791ff3a0dSAlan Somers GTEST_SKIP() << "This test must be run as root";
5891ff3a0dSAlan Somers }
5991ff3a0dSAlan Somers
6091ff3a0dSAlan Somers FuseTest::SetUp();
6191ff3a0dSAlan Somers }
6291ff3a0dSAlan Somers };
6391ff3a0dSAlan Somers
6491ff3a0dSAlan Somers class AllowOther: public NoAllowOther {
6591ff3a0dSAlan Somers
6691ff3a0dSAlan Somers public:
SetUp()6791ff3a0dSAlan Somers virtual void SetUp() {
6891ff3a0dSAlan Somers m_allow_other = true;
6991ff3a0dSAlan Somers NoAllowOther::SetUp();
7091ff3a0dSAlan Somers }
7191ff3a0dSAlan Somers };
7291ff3a0dSAlan Somers
TEST_F(AllowOther,allowed)7391ff3a0dSAlan Somers TEST_F(AllowOther, allowed)
7491ff3a0dSAlan Somers {
75a1542146SAlan Somers int status;
76a1542146SAlan Somers
77a1542146SAlan Somers fork(true, &status, [&] {
7891ff3a0dSAlan Somers uint64_t ino = 42;
7991ff3a0dSAlan Somers
8091ff3a0dSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
8191ff3a0dSAlan Somers expect_open(ino, 0, 1);
829f10f423SAlan Somers expect_flush(ino, 1, ReturnErrno(0));
8342d50d16SAlan Somers expect_release(ino, FH);
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 }
924ac4b126SAlan Somers
934ac4b126SAlan Somers leak(fd);
9409c01e67SAlan Somers return 0;
9509c01e67SAlan Somers }
9609c01e67SAlan Somers );
97a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status));
9891ff3a0dSAlan Somers }
9991ff3a0dSAlan Somers
10061b0a927SAlan Somers /* Check that fusefs uses the correct credentials for FUSE operations */
TEST_F(AllowOther,creds)10161b0a927SAlan Somers TEST_F(AllowOther, creds)
10261b0a927SAlan Somers {
10361b0a927SAlan Somers int status;
10461b0a927SAlan Somers uid_t uid;
10561b0a927SAlan Somers gid_t gid;
10661b0a927SAlan Somers
10761b0a927SAlan Somers get_unprivileged_id(&uid, &gid);
10861b0a927SAlan Somers fork(true, &status, [=] {
10961b0a927SAlan Somers EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) {
11029edc611SAlan Somers return (in.header.opcode == FUSE_LOOKUP &&
11129edc611SAlan Somers in.header.uid == uid &&
11229edc611SAlan Somers in.header.gid == gid);
11361b0a927SAlan Somers }, Eq(true)),
11461b0a927SAlan Somers _)
11561b0a927SAlan Somers ).Times(1)
11661b0a927SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT)));
11761b0a927SAlan Somers }, []() {
11861b0a927SAlan Somers eaccess(FULLPATH, F_OK);
11961b0a927SAlan Somers return 0;
12061b0a927SAlan Somers }
12161b0a927SAlan Somers );
12261b0a927SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status));
12361b0a927SAlan Somers }
12461b0a927SAlan Somers
12520807058SAlan Somers /*
12620807058SAlan Somers * A variation of the Open.multiple_creds test showing how the bug can lead to a
12720807058SAlan Somers * privilege elevation. The first process is privileged and opens a file only
12820807058SAlan Somers * visible to root. The second process is unprivileged and shouldn't be able
12920807058SAlan Somers * to open the file, but does thanks to the bug
13020807058SAlan Somers */
TEST_F(AllowOther,privilege_escalation)131f8d4af10SAlan Somers TEST_F(AllowOther, privilege_escalation)
13220807058SAlan Somers {
133a1542146SAlan Somers int fd1, status;
13420807058SAlan Somers const static uint64_t ino = 42;
13520807058SAlan Somers const static uint64_t fh = 100;
13620807058SAlan Somers
13720807058SAlan Somers /* Fork a child to open the file with different credentials */
138a1542146SAlan Somers fork(true, &status, [&] {
13920807058SAlan Somers
14020807058SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2);
14120807058SAlan Somers EXPECT_CALL(*m_mock, process(
14220807058SAlan Somers ResultOf([=](auto in) {
14329edc611SAlan Somers return (in.header.opcode == FUSE_OPEN &&
14429edc611SAlan Somers in.header.pid == (uint32_t)getpid() &&
14529edc611SAlan Somers in.header.uid == (uint32_t)geteuid() &&
14629edc611SAlan Somers in.header.nodeid == ino);
14720807058SAlan Somers }, Eq(true)),
14820807058SAlan Somers _)
14920807058SAlan Somers ).WillOnce(Invoke(
15029edc611SAlan Somers ReturnImmediate([](auto in __unused, auto& out) {
15129edc611SAlan Somers out.body.open.fh = fh;
15229edc611SAlan Somers out.header.len = sizeof(out.header);
15320807058SAlan Somers SET_OUT_HEADER_LEN(out, open);
15420807058SAlan Somers })));
15520807058SAlan Somers
15620807058SAlan Somers EXPECT_CALL(*m_mock, process(
15720807058SAlan Somers ResultOf([=](auto in) {
15829edc611SAlan Somers return (in.header.opcode == FUSE_OPEN &&
15929edc611SAlan Somers in.header.pid != (uint32_t)getpid() &&
16029edc611SAlan Somers in.header.uid != (uint32_t)geteuid() &&
16129edc611SAlan Somers in.header.nodeid == ino);
16220807058SAlan Somers }, Eq(true)),
16320807058SAlan Somers _)
16420807058SAlan Somers ).Times(AnyNumber())
16520807058SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(EPERM)));
16620807058SAlan Somers
16720807058SAlan Somers fd1 = open(FULLPATH, O_RDONLY);
168d2621689SAlan Somers ASSERT_LE(0, fd1) << strerror(errno);
16920807058SAlan Somers }, [] {
17020807058SAlan Somers int fd0;
17120807058SAlan Somers
17220807058SAlan Somers fd0 = open(FULLPATH, O_RDONLY);
17320807058SAlan Somers if (fd0 >= 0) {
17420807058SAlan Somers fprintf(stderr, "Privilege escalation!\n");
17520807058SAlan Somers return 1;
17620807058SAlan Somers }
17720807058SAlan Somers if (errno != EPERM) {
17820807058SAlan Somers fprintf(stderr, "Unexpected error %s\n",
17920807058SAlan Somers strerror(errno));
18020807058SAlan Somers return 1;
18120807058SAlan Somers }
1827fc0921dSAlan Somers leak(fd0);
18320807058SAlan Somers return 0;
18420807058SAlan Somers }
18520807058SAlan Somers );
186a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status));
1877fc0921dSAlan Somers leak(fd1);
18820807058SAlan Somers }
18920807058SAlan Somers
TEST_F(NoAllowOther,disallowed)19091ff3a0dSAlan Somers TEST_F(NoAllowOther, disallowed)
19191ff3a0dSAlan Somers {
192a1542146SAlan Somers int status;
193a1542146SAlan Somers
194a1542146SAlan Somers fork(true, &status, [] {
19509c01e67SAlan Somers }, []() {
19691ff3a0dSAlan Somers int fd;
19791ff3a0dSAlan Somers
19891ff3a0dSAlan Somers fd = open(FULLPATH, O_RDONLY);
19991ff3a0dSAlan Somers if (fd >= 0) {
20091ff3a0dSAlan Somers fprintf(stderr, "open should've failed\n");
2014ac4b126SAlan Somers leak(fd);
20209c01e67SAlan Somers return(1);
20391ff3a0dSAlan Somers } else if (errno != EPERM) {
20409c01e67SAlan Somers fprintf(stderr, "Unexpected error: %s\n",
20509c01e67SAlan Somers strerror(errno));
20609c01e67SAlan Somers return(1);
20791ff3a0dSAlan Somers }
20809c01e67SAlan Somers return 0;
20991ff3a0dSAlan Somers }
21009c01e67SAlan Somers );
211a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status));
21291ff3a0dSAlan Somers }
213efa23d97SAlan Somers
214efa23d97SAlan Somers /*
215efa23d97SAlan Somers * When -o allow_other is not used, users other than the owner aren't allowed
216efa23d97SAlan Somers * to open anything inside of the mount point, not just the mountpoint itself
217efa23d97SAlan Somers * This is a regression test for bug 237052
218efa23d97SAlan Somers */
TEST_F(NoAllowOther,disallowed_beneath_root)219efa23d97SAlan Somers TEST_F(NoAllowOther, disallowed_beneath_root)
220efa23d97SAlan Somers {
221efa23d97SAlan Somers const static char RELPATH2[] = "other_dir";
222efa23d97SAlan Somers const static uint64_t ino = 42;
223efa23d97SAlan Somers const static uint64_t ino2 = 43;
224a1542146SAlan Somers int dfd, status;
225efa23d97SAlan Somers
226efa23d97SAlan Somers expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1);
227efa23d97SAlan Somers EXPECT_LOOKUP(ino, RELPATH2)
22829edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
229efa23d97SAlan Somers SET_OUT_HEADER_LEN(out, entry);
23029edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
23129edc611SAlan Somers out.body.entry.nodeid = ino2;
23229edc611SAlan Somers out.body.entry.attr.nlink = 1;
23329edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
234efa23d97SAlan Somers })));
235efa23d97SAlan Somers expect_opendir(ino);
236efa23d97SAlan Somers dfd = open(FULLPATH, O_DIRECTORY);
237efa23d97SAlan Somers ASSERT_LE(0, dfd) << strerror(errno);
238efa23d97SAlan Somers
239a1542146SAlan Somers fork(true, &status, [] {
240efa23d97SAlan Somers }, [&]() {
241efa23d97SAlan Somers int fd;
242efa23d97SAlan Somers
243efa23d97SAlan Somers fd = openat(dfd, RELPATH2, O_RDONLY);
244efa23d97SAlan Somers if (fd >= 0) {
245efa23d97SAlan Somers fprintf(stderr, "openat should've failed\n");
2464ac4b126SAlan Somers leak(fd);
247efa23d97SAlan Somers return(1);
248efa23d97SAlan Somers } else if (errno != EPERM) {
249efa23d97SAlan Somers fprintf(stderr, "Unexpected error: %s\n",
250efa23d97SAlan Somers strerror(errno));
251efa23d97SAlan Somers return(1);
252efa23d97SAlan Somers }
253efa23d97SAlan Somers return 0;
254efa23d97SAlan Somers }
255efa23d97SAlan Somers );
256a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status));
2578e765737SAlan Somers
2588e765737SAlan Somers leak(dfd);
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 */
TEST_F(NoAllowOther,setextattr)265666f8543SAlan Somers TEST_F(NoAllowOther, setextattr)
266666f8543SAlan Somers {
267a1542146SAlan Somers int ino = 42, status;
268666f8543SAlan Somers
269a1542146SAlan Somers fork(true, &status, [&] {
270a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
271666f8543SAlan Somers .WillOnce(Invoke(
27229edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) {
273666f8543SAlan Somers SET_OUT_HEADER_LEN(out, entry);
27429edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
27529edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
27629edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
27729edc611SAlan 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",
2935a0b9a27SAlan Somers (const 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