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