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); 8142d50d16SAlan Somers expect_release(ino, FH); 8291ff3a0dSAlan Somers expect_getattr(ino, 0); 8309c01e67SAlan Somers }, []() { 8409c01e67SAlan Somers int fd; 8591ff3a0dSAlan Somers 8609c01e67SAlan Somers fd = open(FULLPATH, O_RDONLY); 8709c01e67SAlan Somers if (fd < 0) { 8809c01e67SAlan Somers perror("open"); 8909c01e67SAlan Somers return(1); 9091ff3a0dSAlan Somers } 9109c01e67SAlan Somers return 0; 9209c01e67SAlan Somers } 9309c01e67SAlan Somers ); 9491ff3a0dSAlan Somers } 9591ff3a0dSAlan Somers 9620807058SAlan Somers /* 9720807058SAlan Somers * A variation of the Open.multiple_creds test showing how the bug can lead to a 9820807058SAlan Somers * privilege elevation. The first process is privileged and opens a file only 9920807058SAlan Somers * visible to root. The second process is unprivileged and shouldn't be able 10020807058SAlan Somers * to open the file, but does thanks to the bug 10120807058SAlan Somers */ 102*f8d4af10SAlan Somers TEST_F(AllowOther, privilege_escalation) 10320807058SAlan Somers { 10420807058SAlan Somers const static char FULLPATH[] = "mountpoint/some_file.txt"; 10520807058SAlan Somers const static char RELPATH[] = "some_file.txt"; 10620807058SAlan Somers int fd1; 10720807058SAlan Somers const static uint64_t ino = 42; 10820807058SAlan Somers const static uint64_t fh = 100; 10920807058SAlan Somers 11020807058SAlan Somers /* Fork a child to open the file with different credentials */ 11120807058SAlan Somers fork(true, [&] { 11220807058SAlan Somers 11320807058SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2); 11420807058SAlan Somers EXPECT_CALL(*m_mock, process( 11520807058SAlan Somers ResultOf([=](auto in) { 11620807058SAlan Somers return (in->header.opcode == FUSE_OPEN && 11720807058SAlan Somers in->header.pid == (uint32_t)getpid() && 11820807058SAlan Somers in->header.uid == (uint32_t)geteuid() && 11920807058SAlan Somers in->header.nodeid == ino); 12020807058SAlan Somers }, Eq(true)), 12120807058SAlan Somers _) 12220807058SAlan Somers ).WillOnce(Invoke( 12320807058SAlan Somers ReturnImmediate([](auto in __unused, auto out) { 12420807058SAlan Somers out->body.open.fh = fh; 12520807058SAlan Somers out->header.len = sizeof(out->header); 12620807058SAlan Somers SET_OUT_HEADER_LEN(out, open); 12720807058SAlan Somers }))); 12820807058SAlan Somers 12920807058SAlan Somers EXPECT_CALL(*m_mock, process( 13020807058SAlan Somers ResultOf([=](auto in) { 13120807058SAlan Somers return (in->header.opcode == FUSE_OPEN && 13220807058SAlan Somers in->header.pid != (uint32_t)getpid() && 13320807058SAlan Somers in->header.uid != (uint32_t)geteuid() && 13420807058SAlan Somers in->header.nodeid == ino); 13520807058SAlan Somers }, Eq(true)), 13620807058SAlan Somers _) 13720807058SAlan Somers ).Times(AnyNumber()) 13820807058SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(EPERM))); 13920807058SAlan Somers expect_getattr(ino, 0); 14020807058SAlan Somers 14120807058SAlan Somers fd1 = open(FULLPATH, O_RDONLY); 14220807058SAlan Somers EXPECT_LE(0, fd1) << strerror(errno); 14320807058SAlan Somers }, [] { 14420807058SAlan Somers int fd0; 14520807058SAlan Somers 14620807058SAlan Somers fd0 = open(FULLPATH, O_RDONLY); 14720807058SAlan Somers if (fd0 >= 0) { 14820807058SAlan Somers fprintf(stderr, "Privilege escalation!\n"); 14920807058SAlan Somers return 1; 15020807058SAlan Somers } 15120807058SAlan Somers if (errno != EPERM) { 15220807058SAlan Somers fprintf(stderr, "Unexpected error %s\n", 15320807058SAlan Somers strerror(errno)); 15420807058SAlan Somers return 1; 15520807058SAlan Somers } 15620807058SAlan Somers return 0; 15720807058SAlan Somers } 15820807058SAlan Somers ); 15920807058SAlan Somers /* Deliberately leak fd1. close(2) will be tested in release.cc */ 16020807058SAlan Somers } 16120807058SAlan Somers 16291ff3a0dSAlan Somers TEST_F(NoAllowOther, disallowed) 16391ff3a0dSAlan Somers { 16409c01e67SAlan Somers fork(true, [] { 16509c01e67SAlan Somers }, []() { 16691ff3a0dSAlan Somers int fd; 16791ff3a0dSAlan Somers 16891ff3a0dSAlan Somers fd = open(FULLPATH, O_RDONLY); 16991ff3a0dSAlan Somers if (fd >= 0) { 17091ff3a0dSAlan Somers fprintf(stderr, "open should've failed\n"); 17109c01e67SAlan Somers return(1); 17291ff3a0dSAlan Somers } else if (errno != EPERM) { 17309c01e67SAlan Somers fprintf(stderr, "Unexpected error: %s\n", 17409c01e67SAlan Somers strerror(errno)); 17509c01e67SAlan Somers return(1); 17691ff3a0dSAlan Somers } 17709c01e67SAlan Somers return 0; 17891ff3a0dSAlan Somers } 17909c01e67SAlan Somers ); 18091ff3a0dSAlan Somers } 181