Path: blob/main/tests/sys/fs/fusefs/allow_other.cc
39536 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2019 The FreeBSD Foundation4*5* This software was developed by BFF Storage Systems, LLC under sponsorship6* from the FreeBSD Foundation.7*8* Redistribution and use in source and binary forms, with or without9* modification, are permitted provided that the following conditions10* are met:11* 1. Redistributions of source code must retain the above copyright12* notice, this list of conditions and the following disclaimer.13* 2. Redistributions in binary form must reproduce the above copyright14* notice, this list of conditions and the following disclaimer in the15* documentation and/or other materials provided with the distribution.16*17* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND18* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE19* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE20* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE21* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL22* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS23* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)24* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT25* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY26* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF27* SUCH DAMAGE.28*/2930/*31* Tests for the "allow_other" mount option. They must be in their own32* file so they can be run as root33*/3435extern "C" {36#include <sys/types.h>37#include <sys/extattr.h>38#include <sys/wait.h>39#include <fcntl.h>40#include <unistd.h>41}4243#include "mockfs.hh"44#include "utils.hh"4546using namespace testing;4748const static char FULLPATH[] = "mountpoint/some_file.txt";49const static char RELPATH[] = "some_file.txt";5051class NoAllowOther: public FuseTest {5253public:54virtual void SetUp() {55if (geteuid() != 0) {56GTEST_SKIP() << "This test must be run as root";57}5859FuseTest::SetUp();60}61};6263class AllowOther: public NoAllowOther {6465public:66virtual void SetUp() {67m_allow_other = true;68NoAllowOther::SetUp();69}70};7172TEST_F(AllowOther, allowed)73{74int status;7576fork(true, &status, [&] {77uint64_t ino = 42;7879expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);80expect_open(ino, 0, 1);81expect_flush(ino, 1, ReturnErrno(0));82expect_release(ino, FH);83}, []() {84int fd;8586fd = open(FULLPATH, O_RDONLY);87if (fd < 0) {88perror("open");89return(1);90}9192leak(fd);93return 0;94}95);96ASSERT_EQ(0, WEXITSTATUS(status));97}9899/* Check that fusefs uses the correct credentials for FUSE operations */100TEST_F(AllowOther, creds)101{102int status;103uid_t uid;104gid_t gid;105106get_unprivileged_id(&uid, &gid);107fork(true, &status, [=] {108EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) {109return (in.header.opcode == FUSE_LOOKUP &&110in.header.uid == uid &&111in.header.gid == gid);112}, Eq(true)),113_)114).Times(1)115.WillOnce(Invoke(ReturnErrno(ENOENT)));116}, []() {117eaccess(FULLPATH, F_OK);118return 0;119}120);121ASSERT_EQ(0, WEXITSTATUS(status));122}123124/*125* A variation of the Open.multiple_creds test showing how the bug can lead to a126* privilege elevation. The first process is privileged and opens a file only127* visible to root. The second process is unprivileged and shouldn't be able128* to open the file, but does thanks to the bug129*/130TEST_F(AllowOther, privilege_escalation)131{132int fd1, status;133const static uint64_t ino = 42;134const static uint64_t fh = 100;135136/* Fork a child to open the file with different credentials */137fork(true, &status, [&] {138139expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2);140EXPECT_CALL(*m_mock, process(141ResultOf([=](auto in) {142return (in.header.opcode == FUSE_OPEN &&143in.header.pid == (uint32_t)getpid() &&144in.header.uid == (uint32_t)geteuid() &&145in.header.nodeid == ino);146}, Eq(true)),147_)148).WillOnce(Invoke(149ReturnImmediate([](auto in __unused, auto& out) {150out.body.open.fh = fh;151out.header.len = sizeof(out.header);152SET_OUT_HEADER_LEN(out, open);153})));154155EXPECT_CALL(*m_mock, process(156ResultOf([=](auto in) {157return (in.header.opcode == FUSE_OPEN &&158in.header.pid != (uint32_t)getpid() &&159in.header.uid != (uint32_t)geteuid() &&160in.header.nodeid == ino);161}, Eq(true)),162_)163).Times(AnyNumber())164.WillRepeatedly(Invoke(ReturnErrno(EPERM)));165166fd1 = open(FULLPATH, O_RDONLY);167ASSERT_LE(0, fd1) << strerror(errno);168}, [] {169int fd0;170171fd0 = open(FULLPATH, O_RDONLY);172if (fd0 >= 0) {173fprintf(stderr, "Privilege escalation!\n");174return 1;175}176if (errno != EPERM) {177fprintf(stderr, "Unexpected error %s\n",178strerror(errno));179return 1;180}181leak(fd0);182return 0;183}184);185ASSERT_EQ(0, WEXITSTATUS(status));186leak(fd1);187}188189TEST_F(NoAllowOther, disallowed)190{191int status;192193fork(true, &status, [] {194}, []() {195int fd;196197fd = open(FULLPATH, O_RDONLY);198if (fd >= 0) {199fprintf(stderr, "open should've failed\n");200leak(fd);201return(1);202} else if (errno != EPERM) {203fprintf(stderr, "Unexpected error: %s\n",204strerror(errno));205return(1);206}207return 0;208}209);210ASSERT_EQ(0, WEXITSTATUS(status));211}212213/*214* When -o allow_other is not used, users other than the owner aren't allowed215* to open anything inside of the mount point, not just the mountpoint itself216* This is a regression test for bug 237052217*/218TEST_F(NoAllowOther, disallowed_beneath_root)219{220const static char RELPATH2[] = "other_dir";221const static uint64_t ino = 42;222const static uint64_t ino2 = 43;223int dfd, status;224225expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1);226EXPECT_LOOKUP(ino, RELPATH2)227.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {228SET_OUT_HEADER_LEN(out, entry);229out.body.entry.attr.mode = S_IFREG | 0644;230out.body.entry.nodeid = ino2;231out.body.entry.attr.nlink = 1;232out.body.entry.attr_valid = UINT64_MAX;233})));234expect_opendir(ino);235dfd = open(FULLPATH, O_DIRECTORY);236ASSERT_LE(0, dfd) << strerror(errno);237238fork(true, &status, [] {239}, [&]() {240int fd;241242fd = openat(dfd, RELPATH2, O_RDONLY);243if (fd >= 0) {244fprintf(stderr, "openat should've failed\n");245leak(fd);246return(1);247} else if (errno != EPERM) {248fprintf(stderr, "Unexpected error: %s\n",249strerror(errno));250return(1);251}252return 0;253}254);255ASSERT_EQ(0, WEXITSTATUS(status));256257leak(dfd);258}259260/*261* Provide coverage for the extattr methods, which have a slightly different262* code path263*/264TEST_F(NoAllowOther, setextattr)265{266int ino = 42, status;267268fork(true, &status, [&] {269EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)270.WillOnce(Invoke(271ReturnImmediate([=](auto in __unused, auto& out) {272SET_OUT_HEADER_LEN(out, entry);273out.body.entry.attr_valid = UINT64_MAX;274out.body.entry.entry_valid = UINT64_MAX;275out.body.entry.attr.mode = S_IFREG | 0644;276out.body.entry.nodeid = ino;277})));278279/*280* lookup the file to get it into the cache.281* Otherwise, the unprivileged lookup will fail with282* EACCES283*/284ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);285}, [&]() {286const char value[] = "whatever";287ssize_t value_len = strlen(value) + 1;288int ns = EXTATTR_NAMESPACE_USER;289ssize_t r;290291r = extattr_set_file(FULLPATH, ns, "foo",292(const void*)value, value_len);293if (r >= 0) {294fprintf(stderr, "should've failed\n");295return(1);296} else if (errno != EPERM) {297fprintf(stderr, "Unexpected error: %s\n",298strerror(errno));299return(1);300}301return 0;302}303);304ASSERT_EQ(0, WEXITSTATUS(status));305}306307308