diff options
author | Sven Gothel <[email protected]> | 2022-06-25 06:23:35 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2022-06-25 06:23:35 +0200 |
commit | 65d0347f69163fbdda9b2d0339564e7afe7f097c (patch) | |
tree | ad1a6c5113ca0ad05b6ad44057f36bae8f909a5f /test/testsudo_fileutils02.cpp | |
parent | 510421f8c796adb2c6046b175de23901a027ebeb (diff) |
testsudo_fileutils02: Run w/ capsh `cap_sys_admin,cap_setuid,cap_setgid+eip` as user or as root via sudo; After setup mount_image() and umount() as well as copy-test can be called as user
Diffstat (limited to 'test/testsudo_fileutils02.cpp')
-rw-r--r-- | test/testsudo_fileutils02.cpp | 312 |
1 files changed, 295 insertions, 17 deletions
diff --git a/test/testsudo_fileutils02.cpp b/test/testsudo_fileutils02.cpp index 51d8b69..c03b36c 100644 --- a/test/testsudo_fileutils02.cpp +++ b/test/testsudo_fileutils02.cpp @@ -24,36 +24,314 @@ #include "test_fileutils_copy_r_p.hpp" +extern "C" { + #include <unistd.h> + #include <grp.h> + #include <pwd.h> + #include <sys/types.h> + #include <sys/mount.h> + #include <sys/capability.h> + #include <sys/prctl.h> +} + class TestFileUtil02 : TestFileUtilBase { + private: + static constexpr const bool change_caps = false; + + static void print_creds(const std::string& title) { + jau::fprintf_td(stderr, "%s: uid %" PRIu32 ", euid %" PRIu32 ", gid %" PRIu32 ", egid %" PRIu32 "\n", + title.c_str(), ::getuid(), ::geteuid(), ::getgid(), ::getegid()); + + gid_t gid_list[64]; + int count = ::getgroups(sizeof(gid_list)/sizeof(*gid_list), gid_list); + if( 0 > count ) { + ERR_PRINT("getgroups() failed"); + } else { + jau::fprintf_td(stderr, "%s: groups[%d]: ", title.c_str(), count); + for(int i=0; i<count; ++i) { + fprintf(stderr, "%" PRIu32 ", ", gid_list[i]); + } + fprintf(stderr, "\n"); + } + + } + static bool set_groups(size_t size, const gid_t *list) { + if( 0 > ::setgroups(size, list) ) { + ERR_PRINT("setgroups failed"); + return false; + } + return true; + } + + static bool set_effective_gid(::gid_t group_id) { + if( 0 != ::setegid(group_id) ) { + ERR_PRINT("setegid(%" PRIu32 ") failed", group_id); + return false; + } + return true; + } + + static bool set_effective_uid(::uid_t user_id) { + if( 0 != ::seteuid(user_id) ) { + ERR_PRINT("seteuid(%" PRIu32 ") failed", user_id); + return false; + } + return true; + } + public: + static bool get_env_uid(::uid_t& res_uid, const bool try_sudo) noexcept { + char *env_str = nullptr; + long long env_val = 0; + if( try_sudo ) { + env_str = ::getenv("SUDO_UID"); + if( nullptr != env_str ) { + if( jau::to_integer(env_val, env_str, strlen(env_str)) ) { + res_uid = (::uid_t) env_val; + return true; + } + } + } + env_str = ::getenv("UID"); + if( nullptr != env_str ) { + if( jau::to_integer(env_val, env_str, strlen(env_str)) ) { + res_uid = (::uid_t) env_val; + return true; + } + } + return false; + } + + static bool get_env_username(std::string& username, const bool try_sudo) noexcept { + char *env_str = nullptr; + if( try_sudo ) { + env_str = ::getenv("SUDO_USER"); + if( nullptr != env_str ) { + username = std::string(env_str); + return true; + } + } + env_str = ::getenv("USER"); + if( nullptr != env_str ) { + username = std::string(env_str); + return true; + } + return false; + } + + static bool get_env_creds(::uid_t& res_uid, ::gid_t& res_gid) noexcept { + std::string username; + struct passwd pwd; + char buffer[1024]; + const bool is_root = 0 == res_uid; + + if( !is_root || get_env_uid(res_uid, is_root) ) { + struct passwd* pwd_res = nullptr; + if( 0 != ::getpwuid_r(res_uid, &pwd, buffer, sizeof(buffer), &pwd_res) || nullptr == pwd_res ) { + ERR_PRINT("getpwuid(%" PRIu32 ") failed", res_uid); + return false; + } + jau::fprintf_td(stderr, "getpwuid(%" PRIu32 "): name '%s', uid %" PRIu32 ", gid %" PRIu32 "\n", res_uid, pwd_res->pw_name, pwd_res->pw_uid, pwd_res->pw_gid); + res_gid = pwd_res->pw_gid; + return true; + } else if( get_env_username(username, is_root) ) { + struct passwd* pwd_res = nullptr; + if( 0 != ::getpwnam_r(username.c_str(), &pwd, buffer, sizeof(buffer), &pwd_res) || nullptr == pwd_res ) { + ERR_PRINT("getpwnam(%s) failed\n", username.c_str()); + return false; + } + jau::fprintf_td(stderr, "getpwnam(%s): name '%s', uid %" PRIu32 ", gid %" PRIu32 "\n", username.c_str(), pwd_res->pw_name, pwd_res->pw_uid, pwd_res->pw_gid); + res_gid = pwd_res->pw_gid; + return true; + } + return false; + } + + static bool cap_get_flag(cap_t cap_p, cap_value_t cap, cap_flag_t flag, cap_flag_value_t *value_p) noexcept { + if( 0 != ::cap_get_flag(cap_p, cap, flag, value_p) ) { + ERR_PRINT("cap_get_flag() failed"); + return false; + } + return true; + } + static bool cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap, const cap_value_t *caps, cap_flag_value_t value) noexcept { + if( 0 != ::cap_set_flag(cap_p, flag, ncap, caps, value) ) { + ERR_PRINT("cap_set_flag() failed"); + return false; + } + return true; + } + + static bool cap_set_proc_flag(const std::string& title, cap_flag_t flag, int ncap, const cap_value_t *cap_list) noexcept { + ::cap_t cap_p; + cap_p = ::cap_get_proc(); + if( nullptr == cap_p ) { + ERR_PRINT("cap_get_proc() failed"); + return false; + } + if( !cap_set_flag(cap_p, flag, ncap, cap_list, CAP_SET) ) { return false; } + ::cap_set_proc(cap_p); + { + char* c_str = ::cap_to_text(cap_p, nullptr); + jau::fprintf_td(stderr, "%s: set caps %s\n", title.c_str(), c_str); + ::cap_free(c_str); + } + ::cap_free(cap_p); + return true; + } + + static void print_caps(const std::string& title) { + ::cap_t caps; + caps = ::cap_get_proc(); + if( nullptr == caps ) { + ERR_PRINT("cap_get_proc() failed"); + return; + } + { + char* c_str = ::cap_to_text(caps, nullptr); + jau::fprintf_td(stderr, "%s: caps %s\n", title.c_str(), c_str); + ::cap_free(c_str); + } + ::cap_free(caps); + } + void test50_mount_copy_r_p() { INFO_STR("\n\ntest50_mount_copy_r_p\n"); + // ::cap_value_t cap_list[] = { CAP_SYS_ADMIN, CAP_SETUID, CAP_SETGID, CAP_CHOWN, CAP_FOWNER }; + ::cap_value_t cap_list[] = { CAP_SYS_ADMIN, CAP_SETUID, CAP_SETGID }; + const size_t cap_list_size = sizeof(cap_list) / sizeof(*cap_list); - jau::fs::file_stats root_orig_stats(project_root1); - if( !root_orig_stats.exists() ) { - root_orig_stats = jau::fs::file_stats(project_root2); + const ::uid_t super_uid = 0; + ::uid_t caller_uid = ::getuid(); + + ::uid_t user_id = caller_uid; + ::gid_t group_id = 1000; + if( !get_env_creds(user_id, group_id) ) { + ERR_PRINT("couldn't fetch [SUDO_]UID"); + return; } - REQUIRE( true == root_orig_stats.exists() ); + ::gid_t group_list[] = { user_id, group_id, 60 }; + + const bool setuid_user_to_root = super_uid != caller_uid; + if( setuid_user_to_root ) { + print_creds("user level - setuid user -> root"); + + ::cap_t caps; + caps = ::cap_get_proc(); + if( nullptr == caps ) { + ERR_PRINT("cap_get_proc() failed"); + return; + } + { + char* c_str = ::cap_to_text(caps, nullptr); + jau::fprintf_td(stderr, "user level: caps %s\n", c_str); + ::cap_free(c_str); + } + cap_flag_value_t cap_sys_admin, cap_setuid, cap_setgid; + if( !cap_get_flag(caps, CAP_SYS_ADMIN, ::CAP_EFFECTIVE, &cap_sys_admin) ) { return; } + if( !cap_get_flag(caps, CAP_SETUID, ::CAP_EFFECTIVE, &cap_setuid) ) { return; } + if( !cap_get_flag(caps, CAP_SETGID, ::CAP_EFFECTIVE, &cap_setgid) ) { return; } + jau::fprintf_td(stderr, "Caps: sys_admin %d, setuid %d, setgid %d\n", + cap_sys_admin, cap_setuid, cap_setgid); + + // Not required as mount/umount uses fork(), then seteuid(0) + if( 0 > ::prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) ) { ERR_PRINT("prctl() failed"); } + + ::cap_free(caps); + + if( !cap_sys_admin || !cap_setuid || !cap_setgid ) { + ERR_PRINT("capabilities incomplete, needs: cap_sys_admin, cap_setuid, cap_setgid, uid is % " PRIu32 "", caller_uid); + return; + } + } else { + print_creds("root level - setuid root -> user"); + + if constexpr ( change_caps ) { + ::cap_t caps; + caps = ::cap_get_proc(); + if( nullptr == caps ) { + ERR_PRINT("cap_get_proc() failed"); + return; + } + + if( !cap_set_flag(caps, ::CAP_EFFECTIVE, cap_list_size, cap_list, ::CAP_SET) ) { return; } + if( !cap_set_flag(caps, ::CAP_INHERITABLE, cap_list_size, cap_list, ::CAP_SET) ) { return; } + if( !cap_set_flag(caps, ::CAP_PERMITTED, cap_list_size, cap_list, ::CAP_SET) ) { return; } + if( !cap_set_proc(caps) ) { return; } + { + char* c_str = ::cap_to_text(caps, nullptr); + jau::fprintf_td(stderr, "root level: caps %s\n", c_str); + ::cap_free(c_str); + } + if( 0 > ::prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) ) { + ERR_PRINT("prctl() failed"); + } + ::cap_free(caps); + } else { + jau::fprintf_td(stderr, "using: changing caps disabled\n"); + } + + if( !set_groups(sizeof(group_list)/sizeof(*group_list), group_list) ) { + return; + } + if( !set_effective_gid(group_id) ) { + return; + } + if( !set_effective_uid(user_id) ) { + return; + } + print_creds("user level"); + + if constexpr ( change_caps ) { + if( !cap_set_proc_flag("user level", CAP_EFFECTIVE, cap_list_size, cap_list) ) { return; } + } + } + REQUIRE( user_id == ::geteuid() ); + + { + std::string image_file = root + ".sqfs"; + jau::fs::file_stats image_stats(image_file); + REQUIRE( true == image_stats.exists() ); + + const std::string mount_point = root+"_mount"; + jau::fs::remove(mount_point, jau::fs::traverse_options::recursive); // start fresh + REQUIRE( true == jau::fs::mkdir(mount_point, jau::fs::fmode_t::def_dir_prot) ); + + jau::fs::mount_ctx mctx; + { + REQUIRE( user_id == ::geteuid() ); + print_creds("pre-mount"); + print_caps("pre-mount"); + + mctx = jau::fs::mount_image(image_stats.path(), mount_point, "squashfs", /* MS_LAZYTIME | MS_NOATIME | */ MS_RDONLY, ""); - std::string image_file = root_orig_stats.path() + ".sqfs"; - jau::fs::file_stats image_stats(image_file); - REQUIRE( true == image_stats.exists() ); + print_creds("post-mount"); + print_caps("post-mount"); + REQUIRE( user_id == ::geteuid() ); + } + REQUIRE( true == mctx.mounted ); - const std::string mount_point = root+"_mount"; - jau::fs::remove(mount_point, jau::fs::traverse_options::recursive); // start fresh - REQUIRE( true == jau::fs::mkdir(mount_point, jau::fs::fmode_t::def_dir_prot) ); + const std::string root_copy = root+"_copy_test50"; + testxx_copy_r_p("test50_mount_copy_r_p", mount_point, 1 /* source_added_dead_links */, root_copy); - jau::fs::mount_ctx mctx = jau::fs::mount_image(image_stats.path(), mount_point, "squashfs", /* MS_LAZYTIME | MS_NOATIME | */ MS_RDONLY, ""); - REQUIRE( true == mctx.mounted ); + bool umount_ok; + { + REQUIRE( user_id == ::geteuid() ); + print_creds("pre-umount"); + print_caps("pre-umount"); - const std::string root_copy = root+"_copy_test50"; - testxx_copy_r_p("test50_mount_copy_r_p", mount_point, 1 /* source_added_dead_links */, root_copy); + umount_ok = jau::fs::umount(mctx); - REQUIRE( true == jau::fs::umount(mctx) ); + print_creds("post-umount"); + print_caps("post-umount"); + REQUIRE( user_id == ::geteuid() ); + } + REQUIRE( true == umount_ok ); - if constexpr ( _remove_target_test_dir ) { - REQUIRE( true == jau::fs::remove(mount_point, jau::fs::traverse_options::recursive) ); + if constexpr ( _remove_target_test_dir ) { + REQUIRE( true == jau::fs::remove(mount_point, jau::fs::traverse_options::recursive) ); + } } } }; |