From 841d57cf1385e7dea6eeed8c30057f1a2ac1f6d2 Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Mon, 21 Oct 2024 11:52:21 -0500 Subject: [PATCH] fuse permission fixes --- .cspell/words.txt | 5 +- .../src/drives/fuse/fuse_drive.cpp | 20 +-- .../include/fixtures/fuse_fixture.hpp | 40 +++++- .../repertory_test/src/fuse_drive_test.cpp | 119 ++++++++++++++---- 4 files changed, 149 insertions(+), 35 deletions(-) diff --git a/.cspell/words.txt b/.cspell/words.txt index 4b5b2425..ec97152c 100644 --- a/.cspell/words.txt +++ b/.cspell/words.txt @@ -170,6 +170,9 @@ renterd richtext rocksdb_library rpcrt4 +s_igid +s_isvtx +s_iuid sddl_revision_1 secp256k1 secur32 @@ -218,4 +221,4 @@ wsign-conversion wunused wuseless wxwidgets_version -xattr +xattr \ No newline at end of file diff --git a/repertory/librepertory/src/drives/fuse/fuse_drive.cpp b/repertory/librepertory/src/drives/fuse/fuse_drive.cpp index 786556b6..36fdfeb9 100644 --- a/repertory/librepertory/src/drives/fuse/fuse_drive.cpp +++ b/repertory/librepertory/src/drives/fuse/fuse_drive.cpp @@ -67,6 +67,11 @@ auto fuse_drive::chmod_impl(std::string api_path, mode_t mode, auto fuse_drive::chmod_impl(std::string api_path, mode_t mode) -> api_error { #endif // FUSE_USE_VERSION >= 30 return check_and_perform(api_path, X_OK, [&](api_meta_map &) -> api_error { + if ((mode & (S_IGID | S_IUID | S_ISVTX) != 0) && + (get_effective_uid() != 0)) { + return api_error::permission_denied; + } + return provider_.set_item_meta(api_path, META_MODE, std::to_string(mode)); }); } @@ -86,23 +91,24 @@ auto fuse_drive::chown_impl(std::string api_path, uid_t uid, gid_t gid) if (get_effective_uid() != 0) { return api_error::permission_denied; } + meta[META_UID] = std::to_string(uid); } if (gid != static_cast(-1)) { - if (get_effective_uid() != 0) { - if (not utils::is_uid_member_of_group(get_effective_uid(), gid)) { - return api_error::permission_denied; - } + if (get_effective_uid() != 0 && + not utils::is_uid_member_of_group(get_effective_uid(), gid)) { + return api_error::permission_denied; } + meta[META_GID] = std::to_string(gid); } - if (not meta.empty()) { - return provider_.set_item_meta(api_path, meta); + if (meta.empty()) { + return api_error::success; } - return api_error::success; + return provider_.set_item_meta(api_path, meta); }); } diff --git a/repertory/repertory_test/include/fixtures/fuse_fixture.hpp b/repertory/repertory_test/include/fixtures/fuse_fixture.hpp index 0849a227..1dbf6e08 100644 --- a/repertory/repertory_test/include/fixtures/fuse_fixture.hpp +++ b/repertory/repertory_test/include/fixtures/fuse_fixture.hpp @@ -32,16 +32,22 @@ #include "providers/encrypt/encrypt_provider.hpp" #include "providers/s3/s3_provider.hpp" #include "providers/sia/sia_provider.hpp" +#include "types/repertory.hpp" +#include "utils/event_capture.hpp" #include "utils/file_utils.hpp" #include "utils/path.hpp" +#include "utils/utils.hpp" #if !defined(ACCESSPERMS) #define ACCESSPERMS (S_IRWXU | S_IRWXG | S_IRWXO) /* 0777 */ #endif -namespace repertory { -inline constexpr const auto SLEEP_SECONDS{1.5s}; +namespace { +std::atomic idx{0U}; +constexpr const auto SLEEP_SECONDS{1.5s}; +} // namespace +namespace repertory { template class fuse_test : public ::testing::Test { public: static std::string cfg_directory; @@ -147,7 +153,10 @@ protected: public: static auto create_file_and_test(std::string name) -> std::string { - auto file_path = utils::path::combine(mount_location, {name}); + auto file_path = + utils::path::combine(mount_location, { + name + std::to_string(++idx), + }); auto fd = open(file_path.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP); @@ -166,6 +175,19 @@ public: return file_path; } + static auto create_root_file(std::string name) -> std::string { + auto file_path = create_file_and_test(name); + + auto api_path = + utils::path::create_api_path(utils::path::strip_to_filename(file_path)); + provider->set_item_meta(api_path, { + {META_UID, "0"}, + {META_GID, "0"}, + }); + + return file_path; + } + static void execute_mount(auto &&drive_args) { auto mount_cmd = "./repertory -dd \"" + config->get_data_directory() + "\"" + " " + utils::string::join(drive_args, ' '); @@ -200,6 +222,18 @@ public: EXPECT_FALSE(utils::file::directory(file_path).exists()); EXPECT_FALSE(utils::file::file(file_path).exists()); } + + static void unlink_root_file(const std::string &file_path) { + auto api_path = + utils::path::create_api_path(utils::path::strip_to_filename(file_path)); + + provider->set_item_meta(api_path, { + {META_UID, std::to_string(getuid())}, + {META_GID, std::to_string(getgid())}, + }); + + unlink_file_and_test(file_path); + } }; template diff --git a/repertory/repertory_test/src/fuse_drive_test.cpp b/repertory/repertory_test/src/fuse_drive_test.cpp index 53a9ef16..11e0029f 100644 --- a/repertory/repertory_test/src/fuse_drive_test.cpp +++ b/repertory/repertory_test/src/fuse_drive_test.cpp @@ -19,22 +19,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#if 0 #if !defined(_WIN32) #include "fixtures/fuse_fixture.hpp" -// #include "app_config.hpp" -// #include "comm/curl/curl_comm.hpp" -// #include "drives/fuse/fuse_drive.hpp" -// #include "platform/platform.hpp" -// #include "providers/s3/s3_provider.hpp" -// #include "providers/sia/sia_provider.hpp" -// #include "types/repertory.hpp" -// #include "utils/event_capture.hpp" -// #include "utils/file_utils.hpp" -// #include "utils/utils.hpp" - namespace repertory { // static void rmdir_and_test(const std::string &directory_path) { // std::cout << __FUNCTION__ << std::endl; @@ -529,36 +517,119 @@ namespace repertory { TYPED_TEST_CASE(fuse_test, fuse_provider_types); -TYPED_TEST(fuse_test, chmod) { +TYPED_TEST(fuse_test, can_chmod_if_owner) { auto file_path = this->create_file_and_test("chmod_test"); EXPECT_EQ(0, chmod(file_path.c_str(), S_IRUSR | S_IWUSR)); std::this_thread::sleep_for(SLEEP_SECONDS); - struct stat64 unix_st {}; + struct stat64 unix_st{}; stat64(file_path.c_str(), &unix_st); EXPECT_EQ(static_cast(S_IRUSR | S_IWUSR), ACCESSPERMS & unix_st.st_mode); + this->unlink_file_and_test(file_path); } -TYPED_TEST(fuse_test, chown) { +TYPED_TEST(fuse_test, can_not_chmod_if_not_owner) { + auto file_path = this->create_root_file("chmod_test"); + EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR)); + EXPECT_EQ(EPERM, errno); + + this->unlink_root_file(file_path); +} + +TYPED_TEST(fuse_test, can_not_chmod_setgid_if_not_root) { auto file_path = this->create_file_and_test("chown_test"); - EXPECT_EQ(0, chown(file_path.c_str(), static_cast(-1), 0)); + EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR | S_IGID)); + EXPECT_EQ(EPERM, errno); + + this->unlink_file_and_test(file_path); +} + +TYPED_TEST(fuse_test, can_not_chmod_setuid_if_not_root) { + auto file_path = this->create_file_and_test("chown_test"); + EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR | S_IUID)); + EXPECT_EQ(EPERM, errno); + + this->unlink_file_and_test(file_path); +} + +TYPED_TEST(fuse_test, can_not_chmod_set_sticky_if_not_root) { + auto file_path = this->create_file_and_test("chown_test"); + EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR | S_ISVTX)); + EXPECT_EQ(EPERM, errno); + + this->unlink_file_and_test(file_path); +} + +TYPED_TEST(fuse_test, can_chown_group_if_owner_and_a_member_of_the_group) { + auto file_path = this->create_file_and_test("chown_test"); + + struct stat64 unix_st{}; + EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st)); + + EXPECT_EQ(0, chown(file_path.c_str(), static_cast(-1), getgid())); std::this_thread::sleep_for(SLEEP_SECONDS); - struct stat64 unix_st {}; - stat64(file_path.c_str(), &unix_st); - EXPECT_EQ(0U, unix_st.st_gid); + struct stat64 unix_st2{}; + stat64(file_path.c_str(), &unix_st2); + EXPECT_EQ(getgid(), unix_st2.st_gid); + EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid); - EXPECT_EQ(0, chown(file_path.c_str(), 0, static_cast(-1))); - std::this_thread::sleep_for(SLEEP_SECONDS); + this->unlink_file_and_test(file_path); +} - stat64(file_path.c_str(), &unix_st); - EXPECT_EQ(0U, unix_st.st_gid); +TYPED_TEST(fuse_test, + can_not_chown_group_if_owner_but_not_a_member_of_the_group) { + auto file_path = this->create_file_and_test("chown_test"); + + struct stat64 unix_st{}; + EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st)); + + EXPECT_EQ(-1, chown(file_path.c_str(), static_cast(-1), 0)); + EXPECT_EQ(EPERM, errno); + + struct stat64 unix_st2{}; + stat64(file_path.c_str(), &unix_st2); + EXPECT_EQ(unix_st.st_gid, unix_st2.st_gid); + EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid); + + this->unlink_file_and_test(file_path); +} + +TYPED_TEST(fuse_test, can_not_chown_group_if_not_the_owner) { + auto file_path = this->create_root_file("chown_test"); + + struct stat64 unix_st{}; + EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st)); + + EXPECT_EQ(-1, chown(file_path.c_str(), static_cast(-1), getgid())); + EXPECT_EQ(EPERM, errno); + + struct stat64 unix_st2{}; + stat64(file_path.c_str(), &unix_st2); + EXPECT_EQ(unix_st.st_gid, unix_st2.st_gid); + EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid); + + this->unlink_root_file(file_path); +} + +TYPED_TEST(fuse_test, can_not_chown_user_if_not_root) { + auto file_path = this->create_file_and_test("chown_test"); + + struct stat64 unix_st{}; + EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st)); + + EXPECT_EQ(-1, chown(file_path.c_str(), 0, static_cast(-1))); + EXPECT_EQ(EPERM, errno); + + struct stat64 unix_st2{}; + stat64(file_path.c_str(), &unix_st2); + EXPECT_EQ(unix_st.st_gid, unix_st2.st_gid); + EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid); this->unlink_file_and_test(file_path); } } // namespace repertory #endif // !defined(_WIN32) -#endif // 0