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