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> 38666f8543SAlan Somers #include <sys/extattr.h> 39*a1542146SAlan 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: 5591ff3a0dSAlan Somers /* Unprivileged user id */ 5691ff3a0dSAlan Somers int m_uid; 5791ff3a0dSAlan Somers 5891ff3a0dSAlan Somers virtual void SetUp() { 5991ff3a0dSAlan Somers if (geteuid() != 0) { 6091ff3a0dSAlan Somers GTEST_SKIP() << "This test must be run as root"; 6191ff3a0dSAlan Somers } 6291ff3a0dSAlan Somers 6391ff3a0dSAlan Somers FuseTest::SetUp(); 6491ff3a0dSAlan Somers } 6591ff3a0dSAlan Somers }; 6691ff3a0dSAlan Somers 6791ff3a0dSAlan Somers class AllowOther: public NoAllowOther { 6891ff3a0dSAlan Somers 6991ff3a0dSAlan Somers public: 7091ff3a0dSAlan Somers virtual void SetUp() { 7191ff3a0dSAlan Somers m_allow_other = true; 7291ff3a0dSAlan Somers NoAllowOther::SetUp(); 7391ff3a0dSAlan Somers } 7491ff3a0dSAlan Somers }; 7591ff3a0dSAlan Somers 7691ff3a0dSAlan Somers TEST_F(AllowOther, allowed) 7791ff3a0dSAlan Somers { 78*a1542146SAlan Somers int status; 79*a1542146SAlan Somers 80*a1542146SAlan Somers fork(true, &status, [&] { 8191ff3a0dSAlan Somers uint64_t ino = 42; 8291ff3a0dSAlan Somers 8391ff3a0dSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 8491ff3a0dSAlan Somers expect_open(ino, 0, 1); 859f10f423SAlan Somers expect_flush(ino, 1, ReturnErrno(0)); 8642d50d16SAlan Somers expect_release(ino, FH); 8709c01e67SAlan Somers }, []() { 8809c01e67SAlan Somers int fd; 8991ff3a0dSAlan Somers 9009c01e67SAlan Somers fd = open(FULLPATH, O_RDONLY); 9109c01e67SAlan Somers if (fd < 0) { 9209c01e67SAlan Somers perror("open"); 9309c01e67SAlan Somers return(1); 9491ff3a0dSAlan Somers } 9509c01e67SAlan Somers return 0; 9609c01e67SAlan Somers } 9709c01e67SAlan Somers ); 98*a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 9991ff3a0dSAlan Somers } 10091ff3a0dSAlan Somers 10120807058SAlan Somers /* 10220807058SAlan Somers * A variation of the Open.multiple_creds test showing how the bug can lead to a 10320807058SAlan Somers * privilege elevation. The first process is privileged and opens a file only 10420807058SAlan Somers * visible to root. The second process is unprivileged and shouldn't be able 10520807058SAlan Somers * to open the file, but does thanks to the bug 10620807058SAlan Somers */ 107f8d4af10SAlan Somers TEST_F(AllowOther, privilege_escalation) 10820807058SAlan Somers { 10920807058SAlan Somers const static char FULLPATH[] = "mountpoint/some_file.txt"; 11020807058SAlan Somers const static char RELPATH[] = "some_file.txt"; 111*a1542146SAlan Somers int fd1, status; 11220807058SAlan Somers const static uint64_t ino = 42; 11320807058SAlan Somers const static uint64_t fh = 100; 11420807058SAlan Somers 11520807058SAlan Somers /* Fork a child to open the file with different credentials */ 116*a1542146SAlan Somers fork(true, &status, [&] { 11720807058SAlan Somers 11820807058SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2); 11920807058SAlan Somers EXPECT_CALL(*m_mock, process( 12020807058SAlan Somers ResultOf([=](auto in) { 12120807058SAlan Somers return (in->header.opcode == FUSE_OPEN && 12220807058SAlan Somers in->header.pid == (uint32_t)getpid() && 12320807058SAlan Somers in->header.uid == (uint32_t)geteuid() && 12420807058SAlan Somers in->header.nodeid == ino); 12520807058SAlan Somers }, Eq(true)), 12620807058SAlan Somers _) 12720807058SAlan Somers ).WillOnce(Invoke( 12820807058SAlan Somers ReturnImmediate([](auto in __unused, auto out) { 12920807058SAlan Somers out->body.open.fh = fh; 13020807058SAlan Somers out->header.len = sizeof(out->header); 13120807058SAlan Somers SET_OUT_HEADER_LEN(out, open); 13220807058SAlan Somers }))); 13320807058SAlan Somers 13420807058SAlan Somers EXPECT_CALL(*m_mock, process( 13520807058SAlan Somers ResultOf([=](auto in) { 13620807058SAlan Somers return (in->header.opcode == FUSE_OPEN && 13720807058SAlan Somers in->header.pid != (uint32_t)getpid() && 13820807058SAlan Somers in->header.uid != (uint32_t)geteuid() && 13920807058SAlan Somers in->header.nodeid == ino); 14020807058SAlan Somers }, Eq(true)), 14120807058SAlan Somers _) 14220807058SAlan Somers ).Times(AnyNumber()) 14320807058SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(EPERM))); 14420807058SAlan Somers 14520807058SAlan Somers fd1 = open(FULLPATH, O_RDONLY); 14620807058SAlan Somers EXPECT_LE(0, fd1) << strerror(errno); 14720807058SAlan Somers }, [] { 14820807058SAlan Somers int fd0; 14920807058SAlan Somers 15020807058SAlan Somers fd0 = open(FULLPATH, O_RDONLY); 15120807058SAlan Somers if (fd0 >= 0) { 15220807058SAlan Somers fprintf(stderr, "Privilege escalation!\n"); 15320807058SAlan Somers return 1; 15420807058SAlan Somers } 15520807058SAlan Somers if (errno != EPERM) { 15620807058SAlan Somers fprintf(stderr, "Unexpected error %s\n", 15720807058SAlan Somers strerror(errno)); 15820807058SAlan Somers return 1; 15920807058SAlan Somers } 16020807058SAlan Somers return 0; 16120807058SAlan Somers } 16220807058SAlan Somers ); 163*a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 16420807058SAlan Somers /* Deliberately leak fd1. close(2) will be tested in release.cc */ 16520807058SAlan Somers } 16620807058SAlan Somers 16791ff3a0dSAlan Somers TEST_F(NoAllowOther, disallowed) 16891ff3a0dSAlan Somers { 169*a1542146SAlan Somers int status; 170*a1542146SAlan Somers 171*a1542146SAlan Somers fork(true, &status, [] { 17209c01e67SAlan Somers }, []() { 17391ff3a0dSAlan Somers int fd; 17491ff3a0dSAlan Somers 17591ff3a0dSAlan Somers fd = open(FULLPATH, O_RDONLY); 17691ff3a0dSAlan Somers if (fd >= 0) { 17791ff3a0dSAlan Somers fprintf(stderr, "open should've failed\n"); 17809c01e67SAlan Somers return(1); 17991ff3a0dSAlan Somers } else if (errno != EPERM) { 18009c01e67SAlan Somers fprintf(stderr, "Unexpected error: %s\n", 18109c01e67SAlan Somers strerror(errno)); 18209c01e67SAlan Somers return(1); 18391ff3a0dSAlan Somers } 18409c01e67SAlan Somers return 0; 18591ff3a0dSAlan Somers } 18609c01e67SAlan Somers ); 187*a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 18891ff3a0dSAlan Somers } 189efa23d97SAlan Somers 190efa23d97SAlan Somers /* 191efa23d97SAlan Somers * When -o allow_other is not used, users other than the owner aren't allowed 192efa23d97SAlan Somers * to open anything inside of the mount point, not just the mountpoint itself 193efa23d97SAlan Somers * This is a regression test for bug 237052 194efa23d97SAlan Somers */ 195efa23d97SAlan Somers TEST_F(NoAllowOther, disallowed_beneath_root) 196efa23d97SAlan Somers { 197efa23d97SAlan Somers const static char FULLPATH[] = "mountpoint/some_dir"; 198efa23d97SAlan Somers const static char RELPATH[] = "some_dir"; 199efa23d97SAlan Somers const static char RELPATH2[] = "other_dir"; 200efa23d97SAlan Somers const static uint64_t ino = 42; 201efa23d97SAlan Somers const static uint64_t ino2 = 43; 202*a1542146SAlan Somers int dfd, status; 203efa23d97SAlan Somers 204efa23d97SAlan Somers expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1); 205efa23d97SAlan Somers EXPECT_LOOKUP(ino, RELPATH2) 206efa23d97SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 207efa23d97SAlan Somers SET_OUT_HEADER_LEN(out, entry); 208efa23d97SAlan Somers out->body.entry.attr.mode = S_IFREG | 0644; 209efa23d97SAlan Somers out->body.entry.nodeid = ino2; 210efa23d97SAlan Somers out->body.entry.attr.nlink = 1; 211efa23d97SAlan Somers out->body.entry.attr_valid = UINT64_MAX; 212efa23d97SAlan Somers }))); 213efa23d97SAlan Somers expect_opendir(ino); 214efa23d97SAlan Somers dfd = open(FULLPATH, O_DIRECTORY); 215efa23d97SAlan Somers ASSERT_LE(0, dfd) << strerror(errno); 216efa23d97SAlan Somers 217*a1542146SAlan Somers fork(true, &status, [] { 218efa23d97SAlan Somers }, [&]() { 219efa23d97SAlan Somers int fd; 220efa23d97SAlan Somers 221efa23d97SAlan Somers fd = openat(dfd, RELPATH2, O_RDONLY); 222efa23d97SAlan Somers if (fd >= 0) { 223efa23d97SAlan Somers fprintf(stderr, "openat should've failed\n"); 224efa23d97SAlan Somers return(1); 225efa23d97SAlan Somers } else if (errno != EPERM) { 226efa23d97SAlan Somers fprintf(stderr, "Unexpected error: %s\n", 227efa23d97SAlan Somers strerror(errno)); 228efa23d97SAlan Somers return(1); 229efa23d97SAlan Somers } 230efa23d97SAlan Somers return 0; 231efa23d97SAlan Somers } 232efa23d97SAlan Somers ); 233*a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 234efa23d97SAlan Somers } 235666f8543SAlan Somers 236666f8543SAlan Somers /* 237666f8543SAlan Somers * Provide coverage for the extattr methods, which have a slightly different 238666f8543SAlan Somers * code path 239666f8543SAlan Somers */ 240666f8543SAlan Somers TEST_F(NoAllowOther, setextattr) 241666f8543SAlan Somers { 242*a1542146SAlan Somers int ino = 42, status; 243666f8543SAlan Somers 244*a1542146SAlan Somers fork(true, &status, [&] { 245666f8543SAlan Somers EXPECT_LOOKUP(1, RELPATH) 246666f8543SAlan Somers .WillOnce(Invoke( 247666f8543SAlan Somers ReturnImmediate([=](auto in __unused, auto out) { 248666f8543SAlan Somers SET_OUT_HEADER_LEN(out, entry); 249666f8543SAlan Somers out->body.entry.attr_valid = UINT64_MAX; 250666f8543SAlan Somers out->body.entry.entry_valid = UINT64_MAX; 251666f8543SAlan Somers out->body.entry.attr.mode = S_IFREG | 0644; 252666f8543SAlan Somers out->body.entry.nodeid = ino; 253666f8543SAlan Somers }))); 254666f8543SAlan Somers 255666f8543SAlan Somers /* 256666f8543SAlan Somers * lookup the file to get it into the cache. 257666f8543SAlan Somers * Otherwise, the unprivileged lookup will fail with 258666f8543SAlan Somers * EACCES 259666f8543SAlan Somers */ 260666f8543SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 261666f8543SAlan Somers }, [&]() { 262666f8543SAlan Somers const char value[] = "whatever"; 263666f8543SAlan Somers ssize_t value_len = strlen(value) + 1; 264666f8543SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 265666f8543SAlan Somers ssize_t r; 266666f8543SAlan Somers 267666f8543SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", 268666f8543SAlan Somers (void*)value, value_len); 269666f8543SAlan Somers if (r >= 0) { 270666f8543SAlan Somers fprintf(stderr, "should've failed\n"); 271666f8543SAlan Somers return(1); 272666f8543SAlan Somers } else if (errno != EPERM) { 273666f8543SAlan Somers fprintf(stderr, "Unexpected error: %s\n", 274666f8543SAlan Somers strerror(errno)); 275666f8543SAlan Somers return(1); 276666f8543SAlan Somers } 277666f8543SAlan Somers return 0; 278666f8543SAlan Somers } 279666f8543SAlan Somers ); 280*a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 281666f8543SAlan Somers } 282