From fc1614ad0970361a55154438080e9056111ecae3 Mon Sep 17 00:00:00 2001 From: Dave Dykstra <2129743+DrDaveD@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:53:53 -0600 Subject: [PATCH] reduce client capabilities to minimum --- cvmfs/CMakeLists.txt | 15 ++- cvmfs/auto_umount.cc | 68 ++++++----- cvmfs/auto_umount.h | 2 +- cvmfs/capabilities.cc | 186 +++++++++++++++++++++++++++++++ cvmfs/capabilities.h | 26 +++++ cvmfs/cvmfs.cc | 2 +- cvmfs/loader.cc | 44 +++++++- cvmfs/monitor.cc | 42 +++++-- cvmfs/monitor.h | 8 +- cvmfs/publish/cmd_abort.cc | 3 +- cvmfs/publish/cmd_commit.cc | 3 +- cvmfs/publish/cmd_transaction.cc | 3 +- cvmfs/quota_posix.cc | 10 ++ cvmfs/swissknife_capabilities.cc | 62 ----------- cvmfs/swissknife_capabilities.h | 14 --- cvmfs/swissknife_ingest.cc | 2 +- cvmfs/swissknife_sync.cc | 3 +- cvmfs/util/logging.h | 2 - cvmfs/util/logging_internal.h | 2 +- cvmfs/util/posix.cc | 18 ++- cvmfs/util/posix.h | 1 + test/unittests/CMakeLists.txt | 3 + 22 files changed, 381 insertions(+), 138 deletions(-) create mode 100644 cvmfs/capabilities.cc create mode 100644 cvmfs/capabilities.h delete mode 100644 cvmfs/swissknife_capabilities.cc delete mode 100644 cvmfs/swissknife_capabilities.h diff --git a/cvmfs/CMakeLists.txt b/cvmfs/CMakeLists.txt index 84540fea3a..df781e3f16 100644 --- a/cvmfs/CMakeLists.txt +++ b/cvmfs/CMakeLists.txt @@ -52,6 +52,7 @@ if (BUILD_CVMFS OR BUILD_LIBCVMFS) cache_stream.cc cache_tiered.cc cache_transport.cc + capabilities.cc catalog.cc catalog_counters.cc catalog_mgr_client.cc @@ -167,6 +168,7 @@ if (BUILD_CVMFS) # add_executable (cvmfs2 fuse_main.cc + capabilities.cc util/exception.cc util/logging.cc util/posix.cc @@ -174,9 +176,8 @@ if (BUILD_CVMFS) ) set_target_properties (cvmfs2 PROPERTIES COMPILE_FLAGS "-DCVMFS_NAMESPACE_GUARD=stub -DCVMFS_FUSE_MODULE" - LINK_FLAGS "-ldl" ) - target_link_libraries(cvmfs2 pthread dl) + target_link_libraries(cvmfs2 pthread dl ${CAP_LIBRARIES}) # # /usr/lib/libcvmfs_fuse_stub[3] @@ -185,6 +186,7 @@ if (BUILD_CVMFS) # libcvmfs-fuse. # set (CVMFS_STUB_SOURCES + capabilities.cc globals.cc loader.cc loader_talk.cc @@ -446,6 +448,7 @@ if (BUILD_LIBCVMFS_CACHE) cache_plugin/libcvmfs_cache_options.cc cache_plugin/channel.cc cache_transport.cc + capabilities.cc monitor.cc options.cc sanitizer.cc @@ -474,6 +477,7 @@ if (BUILD_LIBCVMFS_CACHE) target_link_libraries(cvmfs_cache PUBLIC cvmfs_crypto cvmfs_util + ${CAP_LIBRARIES} PRIVATE ${PROTOBUF_LITE_LIBRARY} ) @@ -483,6 +487,7 @@ if (BUILD_LIBCVMFS_CACHE) add_executable (cvmfs_cache_null cache_plugin/cvmfs_cache_null.cc) target_link_libraries (cvmfs_cache_null cvmfs_cache + ${CAP_LIBRARIES} ${RT_LIBRARY} pthread ) @@ -573,6 +578,7 @@ if (BUILD_SERVER) # set (CVMFS_SWISSKNIFE_SOURCES backoff.cc + capabilities.cc catalog.cc catalog_counters.cc catalog_mgr_ro.cc @@ -640,7 +646,6 @@ if (BUILD_SERVER) supervisor.cc swissknife.cc swissknife_assistant.cc - swissknife_capabilities.cc swissknife_check.cc swissknife_gc.cc swissknife_graft.cc @@ -1013,6 +1018,7 @@ if(BUILD_RECEIVER) receiver/receiver.cc receiver/session_token.cc backoff.cc + capabilities.cc catalog.cc catalog_rw.cc catalog_counters.cc @@ -1085,6 +1091,7 @@ if(BUILD_RECEIVER) ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${RT_LIBRARY} + ${CAP_LIBRARIES} ${LibArchive_LIBRARY} pthread dl @@ -1136,7 +1143,6 @@ if (BUILD_SHRINKWRAP) endif () add_executable (cvmfs_shrinkwrap - monitor.cc shrinkwrap/fs_traversal.cc shrinkwrap/fs_traversal_libcvmfs.cc shrinkwrap/posix/data_dir_mgmt.cc @@ -1156,6 +1162,7 @@ if (BUILD_SHRINKWRAP) cvmfs_client cvmfs_crypto cvmfs_util + ${CAP_LIBRARIES} pthread dl ) diff --git a/cvmfs/auto_umount.cc b/cvmfs/auto_umount.cc index 9a4366ccf6..3f2b319deb 100644 --- a/cvmfs/auto_umount.cc +++ b/cvmfs/auto_umount.cc @@ -15,6 +15,7 @@ #include #include +#include "capabilities.h" #include "util/logging.h" #include "util/platform.h" #include "util/posix.h" @@ -35,21 +36,28 @@ void SetMountpoint(const string &mountpoint) { } -void UmountOnCrash() { +void UmountOnExit(const bool crashed) { + const char *cleanuptype = "exit"; + if (crashed) + cleanuptype = "crash"; + if (!mountpoint_) { - LogCvmfs(kLogCvmfs, kLogSyslogErr, "crash cleanup handler: no mountpoint"); + LogCvmfs(kLogCvmfs, kLogSyslogErr, + "%s cleanup handler: no mountpoint", cleanuptype); return; } std::vector all_mountpoints = platform_mountlist(); if (all_mountpoints.empty()) { - LogCvmfs(kLogCvmfs, kLogSyslogErr, "crash cleanup handler: " - "failed to read mount point list"); + LogCvmfs(kLogCvmfs, kLogSyslogErr, "%s cleanup handler: " + "failed to read mount point list", cleanuptype); return; } - // Mitigate auto-mount - crash - umount - auto-mount loops - SafeSleepMs(2000); + if (crashed) { + // Mitigate auto-mount - crash - umount - auto-mount loops + SafeSleepMs(2000); + } // Check if *mountpoint_ is still mounted // (we don't want to trigger a mount by immediately doing stat *mountpoint_) @@ -61,42 +69,52 @@ void UmountOnCrash() { } } if (!still_mounted) { - LogCvmfs(kLogCvmfs, kLogSyslog, "crash cleanup handler: %s not mounted", - mountpoint_->c_str()); + int logtype = kLogDebug; + if (crashed) + logtype = kLogSyslog; + LogCvmfs(kLogCvmfs, logtype, "%s cleanup handler: %s not mounted", + cleanuptype, mountpoint_->c_str()); return; } - // stat() might be served from caches. Opendir ensures fuse module is called. - int expected_error; + if (crashed) { + // stat() might be served from caches. Opendir ensures fuse module is called. + int expected_error; #ifdef __APPLE__ - expected_error = ENXIO; + expected_error = ENXIO; #else - expected_error = ENOTCONN; + expected_error = ENOTCONN; #endif - DIR *dirp = opendir(mountpoint_->c_str()); - if (dirp || (errno != expected_error)) { - if (dirp) closedir(dirp); - LogCvmfs(kLogCvmfs, kLogSyslog, "crash cleanup handler: " - "%s seems not to be stalled (%d)", mountpoint_->c_str(), errno); - return; + DIR *dirp = opendir(mountpoint_->c_str()); + if (dirp || (errno != expected_error)) { + if (dirp) closedir(dirp); + LogCvmfs(kLogCvmfs, kLogSyslog, "crash cleanup handler: " + "%s seems not to be stalled (%d)", mountpoint_->c_str(), errno); + return; + } } // sudo umount -l *mountpoint_ - if (!SwitchCredentials(0, getegid(), true)) { - LogCvmfs(kLogCvmfs, kLogSyslogErr, "crash cleanup handler: " - "failed to re-gain root privileges"); + if (!ObtainSysAdminCapability()) { + LogCvmfs(kLogCvmfs, kLogSyslogErr, "%s cleanup handler: " + "failed to re-gain sys_admin capability", cleanuptype); return; } const bool lazy = true; bool retval = platform_umount(mountpoint_->c_str(), lazy); if (!retval) { - LogCvmfs(kLogCvmfs, kLogSyslogErr, "crash cleanup handler: " - "failed to unmount %s", mountpoint_->c_str()); + LogCvmfs(kLogCvmfs, kLogSyslogErr, "%s cleanup handler: " + "failed to unmount %s", mountpoint_->c_str(), cleanuptype); return; } - LogCvmfs(kLogCvmfs, kLogSyslog, "crash cleanup handler unmounted stalled %s", - mountpoint_->c_str()); + if (crashed) { + LogCvmfs(kLogCvmfs, kLogSyslog, + "crash cleanup handler unmounted stalled %s", mountpoint_->c_str()); + } else { + LogCvmfs(kLogCvmfs, kLogSyslog, + "exit cleanup handler unmounted %s", mountpoint_->c_str()); + } } } // namespace auto_umount diff --git a/cvmfs/auto_umount.h b/cvmfs/auto_umount.h index a76bd380f5..9c731a69cb 100644 --- a/cvmfs/auto_umount.h +++ b/cvmfs/auto_umount.h @@ -10,7 +10,7 @@ namespace auto_umount { void SetMountpoint(const std::string &mountpoint); -void UmountOnCrash(); +void UmountOnExit(const bool crashed); } // namespace auto_umount diff --git a/cvmfs/capabilities.cc b/cvmfs/capabilities.cc new file mode 100644 index 0000000000..fbd8dd0287 --- /dev/null +++ b/cvmfs/capabilities.cc @@ -0,0 +1,186 @@ +/** + * This file is part of the CernVM File System. + */ + + +#include +#include + +#include + +#include "capabilities.h" +#include "util/logging.h" +#include "util/posix.h" + +#ifdef CVMFS_NAMESPACE_GUARD +namespace CVMFS_NAMESPACE_GUARD { +#endif + + +// Clear all CAP_PERMITTED capabilities except those requested. +// This function requires being run as real uid 0 and ends up switching +// the real uid & gid to match the incoming effective uid & gid. +// Capabilities listed in inheritablecaps will be made inheritable. + +bool ClearPermittedCapabilities(int nreservecaps, + const cap_value_t *reservecaps, + int ninheritablecaps, + const cap_value_t *inheritablecaps) { + int retval; + uid_t uid, gid; + + if (getuid() != 0) { + if (nreservecaps > 0) { + LogCvmfs(kLogCvmfs, kLogDebug, + "Capabilities cannot be reserved because real uid is not root."); + return false; + } + LogCvmfs(kLogCvmfs, kLogDebug, + "Capabilities are already cleared because real uid is not root."); + return true; + } + + uid = geteuid(); + gid = getegid(); + if (uid != 0) { + // Not effective root, so switch to it first to access all capabilities + retval = SwitchCredentials(0, getgid(), true); + if (!retval) { + LogCvmfs(kLogCvmfs, kLogStderr, + "Capabilities cannot be cleared because switch to effective root failed."); + return false; + } + } + if (nreservecaps != 0) { + retval = prctl(PR_SET_KEEPCAPS, 1L); + assert(retval == 0); + } + retval = setgid(gid) || setuid(uid); + if (retval != 0) { + LogCvmfs(kLogCvmfs, kLogStderr, + "Failed to set uid %d gid %d while clearing capabilities (errno: %d)", + uid, gid, errno); + return false; + } + retval = prctl(PR_SET_KEEPCAPS, 0L); + assert(retval == 0); + + cap_t caps_proc = cap_get_proc(); + assert(caps_proc != NULL); + + for (int i = 0; i < nreservecaps; i++) { + cap_value_t cap = reservecaps[i]; + +#ifdef CAP_IS_SUPPORTED + assert(CAP_IS_SUPPORTED(cap)); +#endif + + cap_flag_value_t cap_state; + retval = cap_get_flag(caps_proc, cap, CAP_PERMITTED, &cap_state); + assert(retval == 0); + if (cap_state != CAP_SET) { + LogCvmfs(kLogCvmfs, kLogDebug, + "Warning: cap %d cannot be reserved. " + "It's not in the process's permitted set.", + cap); + cap = 0; + } + } + + retval = cap_clear_flag(caps_proc, CAP_PERMITTED); + assert(retval == 0); + + if (nreservecaps != 0) { + retval = cap_set_flag(caps_proc, CAP_PERMITTED, + nreservecaps, reservecaps, CAP_SET); + assert(retval == 0); + if (ninheritablecaps != 0) { + retval = cap_set_flag(caps_proc, CAP_INHERITABLE, + ninheritablecaps, inheritablecaps, CAP_SET); + assert(retval == 0); + } + } + + retval = cap_set_proc(caps_proc); + int saveerrno = errno; + cap_free(caps_proc); + + if (retval != 0) { + LogCvmfs(kLogCvmfs, kLogDebug, + "Cannot clear permitted capabilities for current process " + "(errno: %d)", + saveerrno); + return false; + } + + if (ninheritablecaps != 0) { + for (int i = 0; i < ninheritablecaps; i++) { + retval = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, + inheritablecaps[i], 0, 0); + assert(retval == 0); + } + } + + return true; +} + +static bool obtainCapability(const cap_value_t cap, const char *capname) { +#ifdef CAP_IS_SUPPORTED + assert(CAP_IS_SUPPORTED(cap)); +#endif + + cap_t caps_proc = cap_get_proc(); + assert(caps_proc != NULL); + + cap_flag_value_t cap_state; + int retval = cap_get_flag(caps_proc, cap, CAP_EFFECTIVE, &cap_state); + assert(retval == 0); + + if (cap_state == CAP_SET) { + cap_free(caps_proc); + return true; + } + + retval = cap_get_flag(caps_proc, cap, CAP_PERMITTED, &cap_state); + assert(retval == 0); + if (cap_state != CAP_SET) { + LogCvmfs(kLogCvmfs, kLogStdout, + "Warning: %s cannot be obtained. " + "It's not in the process's permitted set.", + capname); + cap_free(caps_proc); + return false; + } + + retval = cap_set_flag(caps_proc, CAP_EFFECTIVE, 1, &cap, CAP_SET); + assert(retval == 0); + + retval = cap_set_proc(caps_proc); + cap_free(caps_proc); + + if (retval != 0) { + LogCvmfs(kLogCvmfs, kLogStderr, + "Cannot reset capabilities for current process " + "(errno: %d)", + errno); + return false; + } + + return true; +} + +bool ObtainDacReadSearchCapability() { + return obtainCapability(CAP_DAC_READ_SEARCH, "CAP_DAC_READ_SEARCH"); +} + +bool ObtainSysAdminCapability() { + return obtainCapability(CAP_SYS_ADMIN, "CAP_SYS_ADMIN"); +} + +bool ObtainSysPtraceCapability() { + return obtainCapability(CAP_SYS_PTRACE, "CAP_SYS_PTRACE"); +} + +#ifdef CVMFS_NAMESPACE_GUARD +} // namespace CVMFS_NAMESPACE_GUARD +#endif diff --git a/cvmfs/capabilities.h b/cvmfs/capabilities.h new file mode 100644 index 0000000000..f88c2743b6 --- /dev/null +++ b/cvmfs/capabilities.h @@ -0,0 +1,26 @@ +/** + * This file is part of the CernVM File System. + */ + +#ifndef CVMFS_CAPABILITIES_H_ +#define CVMFS_CAPABILITIES_H_ + +#include + +#ifdef CVMFS_NAMESPACE_GUARD +namespace CVMFS_NAMESPACE_GUARD { +#endif + +bool ObtainDacReadSearchCapability(); +bool ObtainSysAdminCapability(); +bool ObtainSysPtraceCapability(); +bool ClearPermittedCapabilities(int nreservecaps, + const cap_value_t *reservecaps, + int ninheritablecaps, + const cap_value_t *inheritablecaps); + +#ifdef CVMFS_NAMESPACE_GUARD +} // namespace CVMFS_NAMESPACE_GUARD +#endif + +#endif // CVMFS_CAPABILITIES_H_ diff --git a/cvmfs/cvmfs.cc b/cvmfs/cvmfs.cc index 039b610f1d..4ac2a9a57a 100644 --- a/cvmfs/cvmfs.cc +++ b/cvmfs/cvmfs.cc @@ -2323,7 +2323,7 @@ static int Init(const loader::LoaderExports *loader_exports) { // Monitor, check for maximum number of open files if (cvmfs::UseWatchdog()) { auto_umount::SetMountpoint(loader_exports->mount_point); - cvmfs::watchdog_ = Watchdog::Create(auto_umount::UmountOnCrash); + cvmfs::watchdog_ = Watchdog::Create(auto_umount::UmountOnExit); if (cvmfs::watchdog_ == NULL) { *g_boot_error = "failed to initialize watchdog."; return loader::kFailMonitor; diff --git a/cvmfs/loader.cc b/cvmfs/loader.cc index a3205e8788..5cb26e1460 100644 --- a/cvmfs/loader.cc +++ b/cvmfs/loader.cc @@ -36,6 +36,7 @@ #include #include +#include "capabilities.h" #include "duplex_fuse.h" #include "fence.h" #include "fuse_main.h" @@ -881,7 +882,8 @@ int FuseMain(int argc, char *argv[]) { } } - // Drop credentials + // Drop credentials, most likely temporarily since by default there is + // a watchdog if ((uid_ != 0) || (gid_ != 0)) { LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: running with credentials %d:%d", uid_, gid_); @@ -894,7 +896,9 @@ int FuseMain(int argc, char *argv[]) { } if (disable_watchdog_) { LogCvmfs(kLogCvmfs, kLogDebug, "No watchdog, enabling core files"); - prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + retval = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + if (retval != 0) + LogCvmfs(kLogCvmfs, kLogDebug, "Failed to set process dumpable"); } // Only set usyslog now, otherwise file permissions are wrong @@ -1055,8 +1059,8 @@ int FuseMain(int argc, char *argv[]) { } #endif - // drop credentials if (suid_mode_) { + // Drop credentials again for now const bool retrievable = !disable_watchdog_; if (!SwitchCredentials(uid_, gid_, retrievable)) { LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr, @@ -1064,6 +1068,32 @@ int FuseMain(int argc, char *argv[]) { cvmfs_exports_->fnFini(); return kFailPermission; } + } else if ((getuid() == 0) && (geteuid() != 0)) { + LogCvmfs(kLogCvmfs, kLogDebug, "Reducing to minimum capabilities"); + // Have temporarily dropped credentials, now drop credentials permanently. + // CAP_DAC_READ_SEARCH will be sometimes needed for a future feature; + // for now reserve it all the time for testing. + cap_value_t reservecaps[] = {CAP_DAC_READ_SEARCH}; + retval = ClearPermittedCapabilities( + sizeof(reservecaps)/sizeof(cap_value_t), reservecaps, 0, 0); + if (!retval) { + LogCvmfs(kLogCvmfs, kLogStderr, + "Failed to reduce to minimum capabilities"); + return 1; + } + if (!disable_watchdog_) { + // allow ptracing by the watchdog + retval = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + if (retval != 0) + LogCvmfs(kLogCvmfs, kLogDebug, "Failed to set process dumpable"); + // but still disallow core dump + retval = SetLimitCore(0); + if (retval != 0) + LogCvmfs(kLogCvmfs, kLogDebug, "Failed to set core dump limit to 0"); + } + } else { + LogCvmfs(kLogCvmfs, kLogDebug, "Not clearing capabilities, uid %d euid%d", + getuid(), geteuid()); } // Determine device id @@ -1135,6 +1165,11 @@ int FuseMain(int argc, char *argv[]) { loader_talk::Fini(); cvmfs_exports_->fnFini(); + if (!ObtainSysAdminCapability()) { + LogCvmfs(kLogCvmfs, kLogDebug, + "Failed to regain SYS_ADMIN capability"); + } + // Unmount #if CVMFS_USE_LIBFUSE == 2 fuse_remove_signal_handlers(session); @@ -1155,7 +1190,8 @@ int FuseMain(int argc, char *argv[]) { CloseLibrary(); - LogCvmfs(kLogCvmfs, kLogSyslog, "CernVM-FS: unmounted %s (%s)", + LogCvmfs(kLogCvmfs, kLogSyslog, + "CernVM-FS: mount point %s (%s) session ended", mount_point_->c_str(), repository_name_->c_str()); delete fence_reload_; diff --git a/cvmfs/monitor.cc b/cvmfs/monitor.cc index 599bd17618..8509f669f7 100644 --- a/cvmfs/monitor.cc +++ b/cvmfs/monitor.cc @@ -38,6 +38,7 @@ #include #include +#include "capabilities.h" #if defined(CVMFS_FUSE_MODULE) #include "cvmfs.h" #endif @@ -67,9 +68,9 @@ int Watchdog::g_crash_signals[] = { SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGBUS, SIGPIPE, SIGXFSZ }; -Watchdog *Watchdog::Create(FnOnCrash on_crash) { +Watchdog *Watchdog::Create(FnOnExit on_exit) { assert(instance_ == NULL); - instance_ = new Watchdog(on_crash); + instance_ = new Watchdog(on_exit); instance_->Fork(); return instance_; } @@ -83,10 +84,11 @@ string Watchdog::GenerateStackTrace(pid_t pid) { int retval; string result = ""; - // re-gain root permissions to allow for ptrace of died cvmfs2 process - const bool retrievable = true; - if (!SwitchCredentials(0, getgid(), retrievable)) { - result += "failed to re-gain root permissions... still give it a try\n"; + // Get capability to ptrace died cvmfs2 process. + // This is often necessary because the cvmfs2 process can have its own + // elevated capability which would otherwise block ptrace. + if (!ObtainSysPtraceCapability()) { + result += "failed to gain ptrace capability... still give it a try\n"; } // run gdb and attach to the dying process @@ -400,6 +402,25 @@ void Watchdog::Fork() { case 0: { pipe_watchdog_->CloseWriteFd(); Daemonize(); + if ((getuid() == 0) && (geteuid() != 0)) { + if (on_exit_) { + // Reduce to minimum capabilities, which unfortunately is + // still quite powerful. + // CAP_SYS_ADMIN is needed to unmount, and CAP_DAC_READ_SEARCH + // is needed because the main process might have it and ptrace + // is not allowed on a process with more capabilities. + cap_value_t reservecaps[] = {CAP_SYS_ADMIN, CAP_DAC_READ_SEARCH}; + cap_value_t inheritablecaps[] = {CAP_DAC_READ_SEARCH}; + assert(ClearPermittedCapabilities( + sizeof(reservecaps)/sizeof(cap_value_t), reservecaps, + sizeof(inheritablecaps)/sizeof(cap_value_t), inheritablecaps)); + } else { + // Only need to be able to do the stack trace, and the + // main process needs no extra capabilities, so we can + // drop all capabilities. + assert(ClearPermittedCapabilities(0, 0, 0, 0)); + } + } // send the watchdog PID to the supervisee pid_t watchdog_pid = getpid(); pipe_pid.Write(watchdog_pid); @@ -601,15 +622,16 @@ void Watchdog::Supervise() { if (!pipe_watchdog_->TryRead(&control_flow)) { LogEmergency("watchdog: unexpected termination (" + StringifyInt(control_flow) + ")"); - if (on_crash_) on_crash_(); + if (on_exit_) on_exit_(true); } else { switch (control_flow) { case ControlFlow::kProduceStacktrace: LogEmergency(ReportStacktrace()); - if (on_crash_) on_crash_(); + if (on_exit_) on_exit_(true); break; case ControlFlow::kQuit: + if (on_exit_) on_exit_(false); break; default: @@ -620,11 +642,11 @@ void Watchdog::Supervise() { } -Watchdog::Watchdog(FnOnCrash on_crash) +Watchdog::Watchdog(FnOnExit on_exit) : spawned_(false) , exe_path_(string(platform_getexepath())) , watchdog_pid_(0) - , on_crash_(on_crash) + , on_exit_(on_exit) { int retval = platform_spinlock_init(&lock_handler_, 0); assert(retval == 0); diff --git a/cvmfs/monitor.h b/cvmfs/monitor.h index 69a16028e6..e3d17a89d4 100644 --- a/cvmfs/monitor.h +++ b/cvmfs/monitor.h @@ -35,9 +35,9 @@ class Watchdog : SingleCopy { /** * Crash cleanup handler signature. */ - typedef void (*FnOnCrash)(void); + typedef void (*FnOnExit)(const bool crashed); - static Watchdog *Create(FnOnCrash on_crash); + static Watchdog *Create(FnOnExit on_exit); static pid_t GetPid(); ~Watchdog(); void Spawn(const std::string &crash_dump_path); @@ -89,7 +89,7 @@ class Watchdog : SingleCopy { void *context); static void SendTrace(int sig, siginfo_t *siginfo, void *context); - explicit Watchdog(FnOnCrash on_crash); + explicit Watchdog(FnOnExit on_exit); void Fork(); bool WaitForSupervisee(); SigactionMap SetSignalHandlers(const SigactionMap &signal_handlers); @@ -109,7 +109,7 @@ class Watchdog : SingleCopy { /// Send the terminate signal to the listener UniquePtr > pipe_terminate_; pthread_t thread_listener_; - FnOnCrash on_crash_; + FnOnExit on_exit_; platform_spinlock lock_handler_; stack_t sighandler_stack_; SigactionMap old_signal_handlers_; diff --git a/cvmfs/publish/cmd_abort.cc b/cvmfs/publish/cmd_abort.cc index c0b38600ca..aba6019721 100644 --- a/cvmfs/publish/cmd_abort.cc +++ b/cvmfs/publish/cmd_abort.cc @@ -61,8 +61,7 @@ int CmdAbort::Main(const Options &options) { throw; } - if (!SwitchCredentials(settings->owner_uid(), settings->owner_gid(), - false /* temporarily */)) + if (!SwitchCredentials(settings->owner_uid(), settings->owner_gid(), false)) { throw EPublish("No write permission to repository", EPublish::kFailPermission); diff --git a/cvmfs/publish/cmd_commit.cc b/cvmfs/publish/cmd_commit.cc index 17c77e5a32..0481f3c71b 100644 --- a/cvmfs/publish/cmd_commit.cc +++ b/cvmfs/publish/cmd_commit.cc @@ -49,8 +49,7 @@ int CmdCommit::Main(const Options &options) { throw; } - if (!SwitchCredentials(settings->owner_uid(), settings->owner_gid(), - false /* temporarily */)) + if (!SwitchCredentials(settings->owner_uid(), settings->owner_gid(), false)) { throw EPublish("No write permission to repository"); } diff --git a/cvmfs/publish/cmd_transaction.cc b/cvmfs/publish/cmd_transaction.cc index 08fed1ca94..ac1d16c546 100644 --- a/cvmfs/publish/cmd_transaction.cc +++ b/cvmfs/publish/cmd_transaction.cc @@ -72,8 +72,7 @@ int CmdTransaction::Main(const Options &options) { settings->GetTransaction()->SetTemplate(tokens[0], tokens[1]); } - if (!SwitchCredentials(settings->owner_uid(), settings->owner_gid(), - false /* temporarily */)) + if (!SwitchCredentials(settings->owner_uid(), settings->owner_gid(), false)) { throw EPublish("No write permission to repository", EPublish::kFailPermission); diff --git a/cvmfs/quota_posix.cc b/cvmfs/quota_posix.cc index 71adcd6508..d9e16b8d99 100644 --- a/cvmfs/quota_posix.cc +++ b/cvmfs/quota_posix.cc @@ -45,6 +45,7 @@ #include #include +#include "capabilities.h" #include "crypto/hash.h" #include "duplex_sqlite3.h" #include "monitor.h" @@ -1093,6 +1094,15 @@ int PosixQuotaManager::MainCacheManager(int argc, char **argv) { assert(watchdog.IsValid()); watchdog->Spawn("./stacktrace.cachemgr"); + if ((getuid() == 0) && (geteuid() != 0)) { + // Permanently drop credentials + assert(ClearPermittedCapabilities(0, 0, 0, 0)); + // Leave this process ptraceable + assert(prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == 0); + // but without core dumps + assert(SetLimitCore(0) == 0); + } + // Initialize pipe, open non-blocking as cvmfs is not yet connected const int fd_lockfile_fifo = LockFile(shared_manager.workspace_dir_ + "/lock_cachemgr.fifo"); diff --git a/cvmfs/swissknife_capabilities.cc b/cvmfs/swissknife_capabilities.cc deleted file mode 100644 index cd7a95ff9a..0000000000 --- a/cvmfs/swissknife_capabilities.cc +++ /dev/null @@ -1,62 +0,0 @@ -/** - * This file is part of the CernVM File System. - */ - - -#include "swissknife_capabilities.h" - -#include -#include - -#include - -#include "util/logging.h" - -namespace swissknife { - -bool ObtainDacReadSearchCapability() { - cap_value_t cap = CAP_DAC_READ_SEARCH; -#ifdef CAP_IS_SUPPORTED - assert(CAP_IS_SUPPORTED(cap)); -#endif - - cap_t caps_proc = cap_get_proc(); - assert(caps_proc != NULL); - - cap_flag_value_t cap_state; - int retval = cap_get_flag(caps_proc, cap, CAP_EFFECTIVE, &cap_state); - assert(retval == 0); - - if (cap_state == CAP_SET) { - cap_free(caps_proc); - return true; - } - - retval = cap_get_flag(caps_proc, cap, CAP_PERMITTED, &cap_state); - assert(retval == 0); - if (cap_state != CAP_SET) { - LogCvmfs(kLogCvmfs, kLogStdout, - "Warning: CAP_DAC_READ_SEARCH cannot be obtained. " - "It's not in the process's permitted set."); - cap_free(caps_proc); - return false; - } - - retval = cap_set_flag(caps_proc, CAP_EFFECTIVE, 1, &cap, CAP_SET); - assert(retval == 0); - - retval = cap_set_proc(caps_proc); - cap_free(caps_proc); - - if (retval != 0) { - LogCvmfs(kLogCvmfs, kLogStderr, - "Cannot reset capabilities for current process " - "(errno: %d)", - errno); - return false; - } - - return true; -} - -} // namespace swissknife diff --git a/cvmfs/swissknife_capabilities.h b/cvmfs/swissknife_capabilities.h deleted file mode 100644 index a80bb706a2..0000000000 --- a/cvmfs/swissknife_capabilities.h +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This file is part of the CernVM File System. - */ - -#ifndef CVMFS_SWISSKNIFE_CAPABILITIES_H_ -#define CVMFS_SWISSKNIFE_CAPABILITIES_H_ - -namespace swissknife { - -bool ObtainDacReadSearchCapability(); - -} // namespace swissknife - -#endif // CVMFS_SWISSKNIFE_CAPABILITIES_H_ diff --git a/cvmfs/swissknife_ingest.cc b/cvmfs/swissknife_ingest.cc index 8c41c0b8e6..76e9169419 100644 --- a/cvmfs/swissknife_ingest.cc +++ b/cvmfs/swissknife_ingest.cc @@ -7,11 +7,11 @@ #include #include +#include "capabilities.h" #include "catalog_virtual.h" #include "manifest.h" #include "statistics.h" #include "statistics_database.h" -#include "swissknife_capabilities.h" #include "sync_mediator.h" #include "sync_union.h" #include "sync_union_tarball.h" diff --git a/cvmfs/swissknife_sync.cc b/cvmfs/swissknife_sync.cc index 0cc8e07b4a..7c12d4e615 100644 --- a/cvmfs/swissknife_sync.cc +++ b/cvmfs/swissknife_sync.cc @@ -29,13 +29,13 @@ #include #include #include -#include #include #include #include #include +#include "capabilities.h" #include "catalog_mgr_ro.h" #include "catalog_mgr_rw.h" #include "catalog_virtual.h" @@ -47,7 +47,6 @@ #include "sanitizer.h" #include "statistics.h" #include "statistics_database.h" -#include "swissknife_capabilities.h" #include "sync_mediator.h" #include "sync_union.h" #include "sync_union_aufs.h" diff --git a/cvmfs/util/logging.h b/cvmfs/util/logging.h index 5fc0f1051c..41b5797427 100644 --- a/cvmfs/util/logging.h +++ b/cvmfs/util/logging.h @@ -5,8 +5,6 @@ #ifndef CVMFS_UTIL_LOGGING_H_ #define CVMFS_UTIL_LOGGING_H_ -#include - #include "util/export.h" // Shared declarations of debug and non-debug logging #include "util/logging_internal.h" diff --git a/cvmfs/util/logging_internal.h b/cvmfs/util/logging_internal.h index ffaf5cb897..a17ebf90ad 100644 --- a/cvmfs/util/logging_internal.h +++ b/cvmfs/util/logging_internal.h @@ -2,7 +2,7 @@ * This file is part of the CernVM File System. */ -// Internal use, include only logging.h! +// Internal use, included only by logging.h and logging.cc! #ifndef CVMFS_UTIL_LOGGING_INTERNAL_H_ #define CVMFS_UTIL_LOGGING_INTERNAL_H_ diff --git a/cvmfs/util/posix.cc b/cvmfs/util/posix.cc index 7390f51b54..94e82fd8a8 100644 --- a/cvmfs/util/posix.cc +++ b/cvmfs/util/posix.cc @@ -768,6 +768,7 @@ std::string GetHostname() { /** * set(e){g/u}id wrapper. + * If temporarily is true, reserve the ability to switch back. */ bool SwitchCredentials(const uid_t uid, const gid_t gid, const bool temporarily) @@ -783,7 +784,7 @@ bool SwitchCredentials(const uid_t uid, const gid_t gid, retval = seteuid(uid); } else { // If effective uid is not root, we must first gain root access back - if ((getuid() == 0) && (getuid() != geteuid())) { + if ((getuid() == 0) && (geteuid() != 0)) { retval = SwitchCredentials(0, getgid(), true); if (!retval) return false; @@ -1507,6 +1508,21 @@ void GetLimitNoFile(unsigned *soft_limit, unsigned *hard_limit) { } +/** + * Sets soft and hard limit for maximum core size + */ +int SetLimitCore(unsigned limit_core) { + struct rlimit rpl; + memset(&rpl, 0, sizeof(rpl)); + rpl.rlim_max = limit_core; + rpl.rlim_cur = limit_core; + int retval = setrlimit(RLIMIT_CORE, &rpl); + if (retval == 0) + return 0; + return -1; +} + + std::vector Lsof(const std::string &path) { std::vector result; diff --git a/cvmfs/util/posix.h b/cvmfs/util/posix.h index 8f0ade7904..19a9cd2484 100644 --- a/cvmfs/util/posix.h +++ b/cvmfs/util/posix.h @@ -157,6 +157,7 @@ CVMFS_EXPORT std::string GetArch(); CVMFS_EXPORT int SetLimitNoFile(unsigned limit_nofile); CVMFS_EXPORT void GetLimitNoFile(unsigned *soft_limit, unsigned *hard_limit); +CVMFS_EXPORT int SetLimitCore(unsigned limit_core); /** * Searches for open file descriptors on the subtree starting at path. diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index ed79b5d4c0..bf4c9c11aa 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -99,6 +99,7 @@ if (BUILD_SHRINKWRAP) cvmfs_crypto cvmfs_util ${GTEST_LIBRARIES} + ${CAP_LIBRARIES} pthread dl ) @@ -414,6 +415,7 @@ set(CVMFS_UNITTEST_SOURCES ${CVMFS_SOURCE_DIR}/cache_stream.cc ${CVMFS_SOURCE_DIR}/cache_tiered.cc ${CVMFS_SOURCE_DIR}/cache_transport.cc + ${CVMFS_SOURCE_DIR}/capabilities.cc ${CVMFS_SOURCE_DIR}/catalog.cc ${CVMFS_SOURCE_DIR}/catalog_counters.cc ${CVMFS_SOURCE_DIR}/catalog_mgr_client.cc @@ -551,6 +553,7 @@ list (APPEND CVMFS_UNITTESTS_LINK_LIBRARIES ${PACPARSER_LIBRARIES} ${VJSON_LIBRARIES} ${PROTOBUF_LITE_LIBRARY} + ${CAP_LIBRARIES} ${LibArchive_LIBRARY} ${RT_LIBRARY} pthread