Remove libcap_ng and replace it with syscalls 50/39550/5
authorLukasz Kostyra <l.kostyra@samsung.com>
Wed, 6 May 2015 10:16:49 +0000 (12:16 +0200)
committerLukasz Kostyra <l.kostyra@samsung.com>
Tue, 26 May 2015 08:00:20 +0000 (10:00 +0200)
[Feature]       libcap_ng is removed
[Cause]         N/A
[Solution]      Replace libcap_ng with syscalls
[Verification]  Build, install, run vasum-server without root and check if all
                needed capabilities were kept by the process.

Change-Id: Idab4c7b579c6541d941e8c9e9c792427428f8fe5

client/CMakeLists.txt
common/utils/environment.cpp
common/utils/environment.hpp
packaging/vasum.spec
server/CMakeLists.txt
server/server.cpp
tests/unit_tests/CMakeLists.txt
zone-daemon/CMakeLists.txt

index c1fdd19..c9f4c17 100644 (file)
@@ -44,7 +44,7 @@ SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
 
 ## Link libraries ##############################################################
 FIND_PACKAGE(Boost COMPONENTS system filesystem)
-PKG_CHECK_MODULES(LIB_DEPS REQUIRED gio-2.0 libcap-ng)
+PKG_CHECK_MODULES(LIB_DEPS REQUIRED gio-2.0)
 INCLUDE_DIRECTORIES(SYSTEM ${LIB_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
 INCLUDE_DIRECTORIES(${COMMON_FOLDER})
 INCLUDE_DIRECTORIES(${LIBS_FOLDER})
index 57b6e2a..9614cfa 100644 (file)
@@ -31,7 +31,6 @@
 #include "base-exception.hpp"
 #include "logger/logger.hpp"
 
-#include <cap-ng.h>
 #include <grp.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <map>
+#include <iomanip>
 #include <cassert>
 #include <features.h>
+#include <linux/capability.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
 
-#if !__GLIBC_PREREQ(2, 14)
 
-#include <sys/syscall.h>
+#if !__GLIBC_PREREQ(2, 14)
 
 #ifdef __NR_setns
 static inline int setns(int fd, int nstype)
@@ -61,10 +63,37 @@ static inline int setns(int fd, int nstype)
 
 #endif
 
+#ifdef __NR_capset
+static inline int capset(cap_user_header_t header, const cap_user_data_t data)
+{
+    return syscall(__NR_capset, header, data);
+}
+#else
+#error "capset syscall isn't available"
+#endif
+
+#ifdef __NR_capget
+static inline int capget(cap_user_header_t header, cap_user_data_t data)
+{
+    return syscall(__NR_capget, header, data);
+}
+#else
+#error "capget syscall isn't available"
+#endif
+
 using namespace utils;
 
 namespace {
 
+#define CAP_SET_INHERITABLE (1 << 0)
+#define CAP_SET_PERMITTED (1 << 1)
+#define CAP_SET_EFFECTIVE (1 << 2)
+
+// number of __user_cap_data_struct elements needed
+#define CAP_DATA_ELEMENT_COUNT 2
+
+typedef unsigned int CapSet;
+
 const std::map<int, std::string> NAMESPACES = {
     {CLONE_NEWIPC, "ipc"},
     {CLONE_NEWNET, "net"},
@@ -125,6 +154,75 @@ bool fdSend(int socket, int fd)
     return true;
 }
 
+inline bool isValidCap(unsigned int cap)
+{
+    return cap <= CAP_LAST_CAP;
+}
+
+// hasCap assumes that "set" variable will refer to only one set of capabilities
+inline bool hasCap(unsigned int cap, const cap_user_data_t data, CapSet set)
+{
+    // calculate which half of data we need to update
+    int dataInd = 0;
+    if (cap > 31) {
+        dataInd = cap >> 5;
+        cap %= 32;
+    }
+
+    switch (set) {
+    case CAP_SET_INHERITABLE:
+        return CAP_TO_MASK(cap) & data[dataInd].inheritable ? true : false;
+    case CAP_SET_PERMITTED:
+        return CAP_TO_MASK(cap) & data[dataInd].permitted ? true : false;
+    case CAP_SET_EFFECTIVE:
+        return CAP_TO_MASK(cap) & data[dataInd].effective ? true : false;
+    default:
+        return false;
+    };
+}
+
+// these inlines work in-place and update provided "data" array
+// in these inlines, "set" can refer to mulitple sets of capabilities
+inline void addCap(unsigned int cap, cap_user_data_t data, CapSet set)
+{
+    // calculate which half of data we need to update
+    int dataInd = 0;
+    if (cap > 31) {
+        dataInd = cap >> 5;
+        cap %= 32;
+    }
+
+    if ((set & CAP_SET_INHERITABLE) == CAP_SET_INHERITABLE) {
+        data[dataInd].inheritable |= CAP_TO_MASK(cap);
+    }
+    if ((set & CAP_SET_PERMITTED) == CAP_SET_PERMITTED) {
+        data[dataInd].permitted |= CAP_TO_MASK(cap);
+    }
+    if ((set & CAP_SET_EFFECTIVE) == CAP_SET_EFFECTIVE) {
+        data[dataInd].effective |= CAP_TO_MASK(cap);
+    }
+}
+
+inline void removeCap(unsigned int cap, cap_user_data_t data, CapSet set)
+{
+    // calculate which half of data we need to update
+    int dataInd = 0;
+    if (cap > 31) {
+        dataInd = cap >> 5;
+        cap %= 32;
+    }
+
+    if ((set & CAP_SET_INHERITABLE) == CAP_SET_INHERITABLE) {
+        data[dataInd].inheritable &= ~(CAP_TO_MASK(cap));
+    }
+    if ((set & CAP_SET_PERMITTED) == CAP_SET_PERMITTED) {
+        data[dataInd].permitted &= ~(CAP_TO_MASK(cap));
+    }
+    if ((set & CAP_SET_EFFECTIVE) == CAP_SET_EFFECTIVE) {
+        data[dataInd].effective &= ~(CAP_TO_MASK(cap));
+    }
+}
+
 } // namespace
 
 namespace utils {
@@ -156,19 +254,107 @@ bool setSuppGroups(const std::vector<std::string>& groups)
 
 bool dropRoot(uid_t uid, gid_t gid, const std::vector<unsigned int>& caps)
 {
-    ::capng_clear(CAPNG_SELECT_BOTH);
+    ::__user_cap_header_struct header;
+    ::__user_cap_data_struct data[CAP_DATA_ELEMENT_COUNT];
+
+    // initial setup - equivalent to capng_clear
+    header.version = _LINUX_CAPABILITY_VERSION_3;
+    header.pid = ::getpid();
+    memset(data, 0, CAP_DATA_ELEMENT_COUNT*sizeof(__user_cap_data_struct));
 
+    // update cap sets - equivalent to capng_update
     for (const auto cap : caps) {
-        if (::capng_update(CAPNG_ADD, static_cast<capng_type_t>(CAPNG_EFFECTIVE |
-                                                                CAPNG_PERMITTED |
-                                                                CAPNG_INHERITABLE), cap)) {
-            LOGE("Failed to set capability: " << ::capng_capability_to_name(cap));
+        if (!isValidCap(cap)) {
+            LOGE("Capability " << cap << " is invalid.");
             return false;
         }
+
+        addCap(cap, data, CAP_SET_INHERITABLE | CAP_SET_PERMITTED | CAP_SET_EFFECTIVE);
     }
 
-    if (::capng_change_id(uid, gid, static_cast<capng_flags_t>(CAPNG_CLEAR_BOUNDING))) {
-        LOGE("Failed to change process user");
+    // perform some checks and cap updates
+    bool updatedSetUid, updatedSetGid;
+    // check if we are capable of switching our UID
+    if (hasCap(CAP_SETUID, data, CAP_SET_EFFECTIVE)) {
+        // we want to keep CAP_SETUID after change
+        updatedSetUid = false;
+    } else {
+        // we don't have CAP_SETUID and switch is needed - add SETUID to effective and permitted set
+        updatedSetUid = true;
+        addCap(CAP_SETUID, data, CAP_SET_PERMITTED | CAP_SET_EFFECTIVE);
+    }
+
+    // do the same routine for CAP_SETGID
+    if (hasCap(CAP_SETGID, data, CAP_SET_EFFECTIVE)) {
+        updatedSetGid = false;
+    } else {
+        updatedSetGid = true;
+        addCap(CAP_SETGID, data, CAP_SET_PERMITTED | CAP_SET_EFFECTIVE);
+    }
+
+    // we need CAP_SETPCAP as well to clear bounding caps
+    if (!hasCap(CAP_SETPCAP, data, CAP_SET_EFFECTIVE)) {
+        addCap(CAP_SETPCAP, data, CAP_SET_PERMITTED | CAP_SET_EFFECTIVE);
+    }
+
+    // now we can work - first, use prctl to tell system we want to keep our caps when changing UID
+    if (::prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) {
+        LOGE("prctl failed while trying to enable keepcaps: " << strerror(errno));
+        return false;
+    }
+
+    LOGD("Setting temporary caps to process -" << std::hex << std::setfill('0')
+         << " inh:" << std::setw(8) << data[1].inheritable << std::setw(8) << data[0].inheritable
+         << " prm:" << std::setw(8) << data[1].permitted << std::setw(8) << data[0].permitted
+         << " eff:" << std::setw(8) << data[1].effective << std::setw(8) << data[0].effective);
+
+    // set our modified caps before UID/GID change
+    if (::capset(&header, data)) {
+        LOGE("capset failed: " << strerror(errno));
+        return false;
+    }
+
+    // CAP_SETPCAP is available, drop bounding caps
+    for (int i = 0; i <= CAP_LAST_CAP; ++i) {
+        if (::prctl(PR_CAPBSET_DROP, i, 0, 0, 0)) {
+            LOGE("prctl failed while dropping bounding caps: " << strerror(errno));
+            return false;
+        }
+    }
+
+    // set up GID and UID
+    if (::setresgid(gid, gid, gid)) {
+        LOGE("setresgid failed: " << strerror(errno));
+        return false;
+    }
+    if (::setresuid(uid, uid, uid)) {
+        LOGE("setresuid failed: " << strerror(errno));
+        return false;
+    }
+
+    // we are after switch now - disable PR_SET_KEEPCAPS
+    if (::prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0)) {
+        LOGE("prctl failed while trying to disable keepcaps: " << strerror(errno));
+        return false;
+    }
+
+    // disable rendundant caps
+    if (updatedSetUid) {
+        removeCap(CAP_SETUID, data, CAP_SET_PERMITTED | CAP_SET_EFFECTIVE);
+    }
+    if (updatedSetGid) {
+        removeCap(CAP_SETGID, data, CAP_SET_PERMITTED | CAP_SET_EFFECTIVE);
+    }
+    removeCap(CAP_SETPCAP, data, CAP_SET_PERMITTED | CAP_SET_EFFECTIVE);
+
+    LOGD("Setting final caps to process -" << std::hex << std::setfill('0')
+         << " inh:" << std::setw(8) << data[1].inheritable << std::setw(8) << data[0].inheritable
+         << " prm:" << std::setw(8) << data[1].permitted << std::setw(8) << data[0].permitted
+         << " eff:" << std::setw(8) << data[1].effective << std::setw(8) << data[0].effective);
+
+    // finally, apply correct caps
+    if (::capset(&header, data)) {
+        LOGE("capset failed: " << strerror(errno));
         return false;
     }
 
index 9039239..8c99dd4 100644 (file)
@@ -40,7 +40,7 @@ namespace utils {
 bool setSuppGroups(const std::vector<std::string>& groups);
 
 /**
- * Set effective and permited capabilities on the current process and drop root privileges.
+ * Set effective and permitted capabilities on the current process and drop root privileges.
  */
 bool dropRoot(uid_t uid, gid_t gid, const std::vector<unsigned int>& caps);
 
index 3b241df..82a859e 100644 (file)
@@ -20,7 +20,6 @@ Summary:        Daemon for managing zones
 BuildRequires:  cmake
 BuildRequires:  boost-devel
 BuildRequires:  libjson-devel >= 0.10
-BuildRequires:  libcap-ng-devel
 BuildRequires:  lxc-devel
 BuildRequires:  pkgconfig(glib-2.0)
 BuildRequires:  pkgconfig(sqlite3)
index f9ce867..9a1985c 100644 (file)
@@ -30,7 +30,7 @@ ADD_EXECUTABLE(${SERVER_CODENAME} ${project_SRCS} ${common_SRCS})
 
 ## Link libraries ##############################################################
 FIND_PACKAGE(Boost COMPONENTS program_options system filesystem regex)
-PKG_CHECK_MODULES(SERVER_DEPS REQUIRED lxc json gio-2.0 libcap-ng)
+PKG_CHECK_MODULES(SERVER_DEPS REQUIRED lxc json gio-2.0)
 
 INCLUDE_DIRECTORIES(${COMMON_FOLDER})
 INCLUDE_DIRECTORIES(${LIBS_FOLDER})
index 95f22a0..936db28 100644 (file)
 #include <cstring>
 #include <atomic>
 #include <unistd.h>
-#include <cap-ng.h>
 #include <pwd.h>
 #include <sys/stat.h>
 #include <boost/filesystem.hpp>
+#include <linux/capability.h>
 
 
 #ifndef VASUM_USER
index 2b5a09e..484e810 100644 (file)
@@ -47,7 +47,7 @@ ADD_EXECUTABLE(${SOCKET_TEST_CODENAME} ${socket_test_SRCS} ${common_SRCS} ${clie
 ## Link libraries ##############################################################
 FIND_PACKAGE (Boost COMPONENTS unit_test_framework system filesystem regex)
 
-PKG_CHECK_MODULES(UT_SERVER_DEPS REQUIRED lxc json gio-2.0 libcap-ng)
+PKG_CHECK_MODULES(UT_SERVER_DEPS REQUIRED lxc json gio-2.0)
 INCLUDE_DIRECTORIES(${COMMON_FOLDER} ${SERVER_FOLDER} ${UNIT_TESTS_FOLDER} ${CLIENT_FOLDER}
                     ${LIBS_FOLDER} ${SOCKET_TEST_FOLDER})
 INCLUDE_DIRECTORIES(SYSTEM ${UT_SERVER_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
index 4d48141..4944dfb 100644 (file)
@@ -31,7 +31,7 @@ ADD_EXECUTABLE(${ZONE_DAEMON_CODENAME} ${project_SRCS} ${common_SRCS})
 ## Link libraries ##############################################################
 FIND_PACKAGE (Boost COMPONENTS program_options system filesystem)
 
-PKG_CHECK_MODULES(ZONE_DAEMON_DEPS REQUIRED gio-2.0 libcap-ng)
+PKG_CHECK_MODULES(ZONE_DAEMON_DEPS REQUIRED gio-2.0)
 
 INCLUDE_DIRECTORIES(${COMMON_FOLDER})
 INCLUDE_DIRECTORIES(${LIBS_FOLDER})