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> 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: 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 { 78a1542146SAlan Somers int status; 79a1542146SAlan Somers 80a1542146SAlan 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 ); 98a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 9991ff3a0dSAlan Somers } 10091ff3a0dSAlan Somers 10161b0a927SAlan Somers /* Check that fusefs uses the correct credentials for FUSE operations */ 10261b0a927SAlan Somers TEST_F(AllowOther, creds) 10361b0a927SAlan Somers { 10461b0a927SAlan Somers int status; 10561b0a927SAlan Somers uid_t uid; 10661b0a927SAlan Somers gid_t gid; 10761b0a927SAlan Somers 10861b0a927SAlan Somers get_unprivileged_id(&uid, &gid); 10961b0a927SAlan Somers fork(true, &status, [=] { 11061b0a927SAlan Somers EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { 111*29edc611SAlan Somers return (in.header.opcode == FUSE_LOOKUP && 112*29edc611SAlan Somers in.header.uid == uid && 113*29edc611SAlan Somers in.header.gid == gid); 11461b0a927SAlan Somers }, Eq(true)), 11561b0a927SAlan Somers _) 11661b0a927SAlan Somers ).Times(1) 11761b0a927SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 11861b0a927SAlan Somers }, []() { 11961b0a927SAlan Somers eaccess(FULLPATH, F_OK); 12061b0a927SAlan Somers return 0; 12161b0a927SAlan Somers } 12261b0a927SAlan Somers ); 12361b0a927SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 12461b0a927SAlan Somers } 12561b0a927SAlan Somers 12620807058SAlan Somers /* 12720807058SAlan Somers * A variation of the Open.multiple_creds test showing how the bug can lead to a 12820807058SAlan Somers * privilege elevation. The first process is privileged and opens a file only 12920807058SAlan Somers * visible to root. The second process is unprivileged and shouldn't be able 13020807058SAlan Somers * to open the file, but does thanks to the bug 13120807058SAlan Somers */ 132f8d4af10SAlan Somers TEST_F(AllowOther, privilege_escalation) 13320807058SAlan Somers { 13420807058SAlan Somers const static char FULLPATH[] = "mountpoint/some_file.txt"; 13520807058SAlan Somers const static char RELPATH[] = "some_file.txt"; 136a1542146SAlan Somers int fd1, status; 13720807058SAlan Somers const static uint64_t ino = 42; 13820807058SAlan Somers const static uint64_t fh = 100; 13920807058SAlan Somers 14020807058SAlan Somers /* Fork a child to open the file with different credentials */ 141a1542146SAlan Somers fork(true, &status, [&] { 14220807058SAlan Somers 14320807058SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2); 14420807058SAlan Somers EXPECT_CALL(*m_mock, process( 14520807058SAlan Somers ResultOf([=](auto in) { 146*29edc611SAlan Somers return (in.header.opcode == FUSE_OPEN && 147*29edc611SAlan Somers in.header.pid == (uint32_t)getpid() && 148*29edc611SAlan Somers in.header.uid == (uint32_t)geteuid() && 149*29edc611SAlan Somers in.header.nodeid == ino); 15020807058SAlan Somers }, Eq(true)), 15120807058SAlan Somers _) 15220807058SAlan Somers ).WillOnce(Invoke( 153*29edc611SAlan Somers ReturnImmediate([](auto in __unused, auto& out) { 154*29edc611SAlan Somers out.body.open.fh = fh; 155*29edc611SAlan Somers out.header.len = sizeof(out.header); 15620807058SAlan Somers SET_OUT_HEADER_LEN(out, open); 15720807058SAlan Somers }))); 15820807058SAlan Somers 15920807058SAlan Somers EXPECT_CALL(*m_mock, process( 16020807058SAlan Somers ResultOf([=](auto in) { 161*29edc611SAlan Somers return (in.header.opcode == FUSE_OPEN && 162*29edc611SAlan Somers in.header.pid != (uint32_t)getpid() && 163*29edc611SAlan Somers in.header.uid != (uint32_t)geteuid() && 164*29edc611SAlan Somers in.header.nodeid == ino); 16520807058SAlan Somers }, Eq(true)), 16620807058SAlan Somers _) 16720807058SAlan Somers ).Times(AnyNumber()) 16820807058SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(EPERM))); 16920807058SAlan Somers 17020807058SAlan Somers fd1 = open(FULLPATH, O_RDONLY); 17120807058SAlan Somers EXPECT_LE(0, fd1) << strerror(errno); 17220807058SAlan Somers }, [] { 17320807058SAlan Somers int fd0; 17420807058SAlan Somers 17520807058SAlan Somers fd0 = open(FULLPATH, O_RDONLY); 17620807058SAlan Somers if (fd0 >= 0) { 17720807058SAlan Somers fprintf(stderr, "Privilege escalation!\n"); 17820807058SAlan Somers return 1; 17920807058SAlan Somers } 18020807058SAlan Somers if (errno != EPERM) { 18120807058SAlan Somers fprintf(stderr, "Unexpected error %s\n", 18220807058SAlan Somers strerror(errno)); 18320807058SAlan Somers return 1; 18420807058SAlan Somers } 18520807058SAlan Somers return 0; 18620807058SAlan Somers } 18720807058SAlan Somers ); 188a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 18920807058SAlan Somers /* Deliberately leak fd1. close(2) will be tested in release.cc */ 19020807058SAlan Somers } 19120807058SAlan Somers 19291ff3a0dSAlan Somers TEST_F(NoAllowOther, disallowed) 19391ff3a0dSAlan Somers { 194a1542146SAlan Somers int status; 195a1542146SAlan Somers 196a1542146SAlan Somers fork(true, &status, [] { 19709c01e67SAlan Somers }, []() { 19891ff3a0dSAlan Somers int fd; 19991ff3a0dSAlan Somers 20091ff3a0dSAlan Somers fd = open(FULLPATH, O_RDONLY); 20191ff3a0dSAlan Somers if (fd >= 0) { 20291ff3a0dSAlan Somers fprintf(stderr, "open should've failed\n"); 20309c01e67SAlan Somers return(1); 20491ff3a0dSAlan Somers } else if (errno != EPERM) { 20509c01e67SAlan Somers fprintf(stderr, "Unexpected error: %s\n", 20609c01e67SAlan Somers strerror(errno)); 20709c01e67SAlan Somers return(1); 20891ff3a0dSAlan Somers } 20909c01e67SAlan Somers return 0; 21091ff3a0dSAlan Somers } 21109c01e67SAlan Somers ); 212a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 21391ff3a0dSAlan Somers } 214efa23d97SAlan Somers 215efa23d97SAlan Somers /* 216efa23d97SAlan Somers * When -o allow_other is not used, users other than the owner aren't allowed 217efa23d97SAlan Somers * to open anything inside of the mount point, not just the mountpoint itself 218efa23d97SAlan Somers * This is a regression test for bug 237052 219efa23d97SAlan Somers */ 220efa23d97SAlan Somers TEST_F(NoAllowOther, disallowed_beneath_root) 221efa23d97SAlan Somers { 222efa23d97SAlan Somers const static char FULLPATH[] = "mountpoint/some_dir"; 223efa23d97SAlan Somers const static char RELPATH[] = "some_dir"; 224efa23d97SAlan Somers const static char RELPATH2[] = "other_dir"; 225efa23d97SAlan Somers const static uint64_t ino = 42; 226efa23d97SAlan Somers const static uint64_t ino2 = 43; 227a1542146SAlan Somers int dfd, status; 228efa23d97SAlan Somers 229efa23d97SAlan Somers expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1); 230efa23d97SAlan Somers EXPECT_LOOKUP(ino, RELPATH2) 231*29edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 232efa23d97SAlan Somers SET_OUT_HEADER_LEN(out, entry); 233*29edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 234*29edc611SAlan Somers out.body.entry.nodeid = ino2; 235*29edc611SAlan Somers out.body.entry.attr.nlink = 1; 236*29edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 237efa23d97SAlan Somers }))); 238efa23d97SAlan Somers expect_opendir(ino); 239efa23d97SAlan Somers dfd = open(FULLPATH, O_DIRECTORY); 240efa23d97SAlan Somers ASSERT_LE(0, dfd) << strerror(errno); 241efa23d97SAlan Somers 242a1542146SAlan Somers fork(true, &status, [] { 243efa23d97SAlan Somers }, [&]() { 244efa23d97SAlan Somers int fd; 245efa23d97SAlan Somers 246efa23d97SAlan Somers fd = openat(dfd, RELPATH2, O_RDONLY); 247efa23d97SAlan Somers if (fd >= 0) { 248efa23d97SAlan Somers fprintf(stderr, "openat should've failed\n"); 249efa23d97SAlan Somers return(1); 250efa23d97SAlan Somers } else if (errno != EPERM) { 251efa23d97SAlan Somers fprintf(stderr, "Unexpected error: %s\n", 252efa23d97SAlan Somers strerror(errno)); 253efa23d97SAlan Somers return(1); 254efa23d97SAlan Somers } 255efa23d97SAlan Somers return 0; 256efa23d97SAlan Somers } 257efa23d97SAlan Somers ); 258a1542146SAlan Somers ASSERT_EQ(0, WEXITSTATUS(status)); 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 */ 265666f8543SAlan Somers TEST_F(NoAllowOther, setextattr) 266666f8543SAlan Somers { 267a1542146SAlan Somers int ino = 42, status; 268666f8543SAlan Somers 269a1542146SAlan Somers fork(true, &status, [&] { 270666f8543SAlan Somers EXPECT_LOOKUP(1, RELPATH) 271666f8543SAlan Somers .WillOnce(Invoke( 272*29edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 273666f8543SAlan Somers SET_OUT_HEADER_LEN(out, entry); 274*29edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 275*29edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 276*29edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 277*29edc611SAlan 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", 293666f8543SAlan Somers (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