diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5de7c19e..a1c95ee6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,15 +59,15 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install Dependencies - run: sudo apt-get install libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev + run: sudo apt-get install libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors + run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors --with-capabilities - name: Build run: make -k - name: Distcheck - run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors' + run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors --with-capabilities' build-ubuntu-latest-full-featured-clang: runs-on: ubuntu-latest @@ -81,15 +81,15 @@ jobs: sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install clang-11 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev + run: sudo apt-get install clang-11 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors + run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors --with-capabilities - name: Build run: make -k - name: Distcheck - run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors' + run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors --with-capabilities' build-ubuntu-latest-clang-analyzer: runs-on: ubuntu-latest @@ -103,11 +103,11 @@ jobs: sudo add-apt-repository 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install clang-11 clang-tools-11 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev + run: sudo apt-get install clang-11 clang-tools-11 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors4-dev libcap-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: scan-build-11 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors + run: scan-build-11 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-setuid --enable-delayacct --with-sensors --with-capabilities - name: Build run: scan-build-11 -analyze-headers --status-bugs make -j"$(nproc)" diff --git a/configure.ac b/configure.ac index 227f6b14..1feeb0fc 100644 --- a/configure.ac +++ b/configure.ac @@ -307,6 +307,13 @@ if test "x$enable_setuid" = xyes; then AC_DEFINE(HAVE_SETUID_ENABLED, 1, [Define if setuid support should be enabled.]) fi +AC_ARG_WITH(capabilities, [AS_HELP_STRING([--with-capabilities], [Enable option to drop Linux capabilities via libcap])],, with_capabilities="no") +if test "x$with_capabilities" = xyes +then + AC_CHECK_LIB([cap], [cap_init], [], [missing_libraries="$missing_libraries libcap"]) + AC_CHECK_HEADERS([sys/capability.h], [:], [missing_headers="$missing_headers $ac_header"]) +fi + AC_ARG_ENABLE(delayacct, [AS_HELP_STRING([--enable-delayacct], [enable Linux delay accounting])],, enable_delayacct="no") if test "x$enable_delayacct" = xyes; then m4_ifdef([PKG_PROG_PKG_CONFIG], [ @@ -408,6 +415,7 @@ AC_MSG_RESULT([ (Linux) affinity: $enable_linux_affinity (Linux) delay accounting: $enable_delayacct (Linux) sensors: $with_sensors + (Linux) capabilities: $with_capabilities unicode: $enable_unicode hwloc: $enable_hwloc setuid: $enable_setuid diff --git a/htop.1.in b/htop.1.in index cd984789..2ab5c329 100644 --- a/htop.1.in +++ b/htop.1.in @@ -63,6 +63,14 @@ requesting a sort order with -s. .TP \fB\-H \-\-highlight-changes=DELAY\fR Highlight new and old processes +.TP +\fB \-\-drop-capabilities[=none|basic|strict]\fR +Linux only; requires libcap support. +.br +Drop unneeded Linux capabilities. +In strict mode features like killing, changing process priorities, and reading +process delay accounting information will not work, due to less capabilities +held. .SH "INTERACTIVE COMMANDS" The following commands are supported while in .BR htop : diff --git a/htop.c b/htop.c index e59c0282..ff5dc875 100644 --- a/htop.c +++ b/htop.c @@ -8,6 +8,7 @@ in the source distribution for its full text. #include "config.h" // IWYU pragma: keep #include +#include #include #include #include @@ -34,6 +35,19 @@ in the source distribution for its full text. #include "UsersTable.h" #include "XUtils.h" +#ifdef HAVE_LIBCAP +#include +#endif + + +#ifdef HAVE_LIBCAP +enum CapMode { + CAP_MODE_NONE, + CAP_MODE_BASIC, + CAP_MODE_STRICT +}; +#endif + static void printVersionFlag(void) { fputs(PACKAGE " " VERSION "\n", stdout); } @@ -46,6 +60,12 @@ static void printHelpFlag(void) { "-d --delay=DELAY Set the delay between updates, in tenths of seconds\n" "-F --filter=FILTER Show only the commands matching the given filter\n" "-h --help Print this help screen\n" +#ifdef HAVE_LIBCAP + " --drop-capabilities[=none|basic|strict] Drop Linux capabilities when running as root\n" + " none - do not drop any capabilities\n" + " basic (default) - drop all capabilities not needed by htop\n" + " strict - drop all capabilities except those needed for core functionality\n" +#endif "-H --highlight-changes[=DELAY] Highlight new and old processes\n" "-M --no-mouse Disable the mouse\n" "-p --pid=PID[,PID,PID...] Show only the given PIDs\n" @@ -75,6 +95,9 @@ typedef struct CommandLineSettings_ { bool allowUnicode; bool highlightChanges; int highlightDelaySecs; +#ifdef HAVE_LIBCAP + enum CapMode capabilitiesMode; +#endif } CommandLineSettings; static CommandLineSettings parseArguments(int argc, char** argv) { @@ -91,6 +114,9 @@ static CommandLineSettings parseArguments(int argc, char** argv) { .allowUnicode = true, .highlightChanges = false, .highlightDelaySecs = -1, +#ifdef HAVE_LIBCAP + .capabilitiesMode = (geteuid() == 0) ? CAP_MODE_BASIC : CAP_MODE_NONE, +#endif }; const struct option long_opts[] = @@ -108,6 +134,9 @@ static CommandLineSettings parseArguments(int argc, char** argv) { {"pid", required_argument, 0, 'p'}, {"filter", required_argument, 0, 'F'}, {"highlight-changes", optional_argument, 0, 'H'}, +#ifdef HAVE_LIBCAP + {"drop-capabilities", optional_argument, 0, 128}, +#endif {0,0,0,0} }; @@ -225,6 +254,27 @@ static CommandLineSettings parseArguments(int argc, char** argv) { flags.highlightChanges = true; break; } +#ifdef HAVE_LIBCAP + case 128: { + const char* mode = optarg; + if (!mode && optind < argc && argv[optind] != NULL && + (argv[optind][0] != '\0' && argv[optind][0] != '-')) { + mode = argv[optind++]; + } + + if (!mode || String_eq(mode, "basic")) { + flags.capabilitiesMode = CAP_MODE_BASIC; + } else if (String_eq(mode, "none")) { + flags.capabilitiesMode = CAP_MODE_NONE; + } else if (String_eq(mode, "strict")) { + flags.capabilitiesMode = CAP_MODE_STRICT; + } else { + fprintf(stderr, "Error: invalid capabilities mode \"%s\".\n", mode); + exit(1); + } + break; + } +#endif default: exit(1); } @@ -254,6 +304,65 @@ static void setCommFilter(State* state, char** commFilter) { *commFilter = NULL; } +#ifdef HAVE_LIBCAP +static int dropCapabilities(enum CapMode mode) { + + if (mode == CAP_MODE_NONE) + return 0; + + /* capabilities we keep to operate */ + const cap_value_t keepcapsStrict[] = { + CAP_DAC_READ_SEARCH, + CAP_SYS_PTRACE, + }; + const cap_value_t keepcapsBasic[] = { + CAP_DAC_READ_SEARCH, /* read non world-readable process files of other users, like /proc/[pid]/io */ + CAP_KILL, /* send signals to processes of other users */ + CAP_SYS_NICE, /* lower process nice value / change nice value for arbitrary processes */ + CAP_SYS_PTRACE, /* read /proc/[pid]/exe */ +#ifdef HAVE_DELAYACCT + CAP_NET_ADMIN, /* communicate over netlink socket for delay accounting */ +#endif + }; + const cap_value_t* const keepcaps = (mode == CAP_MODE_BASIC) ? keepcapsBasic : keepcapsStrict; + const int ncap = (mode == CAP_MODE_BASIC) ? ARRAYSIZE(keepcapsBasic) : ARRAYSIZE(keepcapsStrict); + + cap_t caps = cap_init(); + if (caps == NULL) { + fprintf(stderr, "Error: can not initialize capabilities: %s\n", strerror(errno)); + return -1; + } + + if (cap_clear(caps) < 0) { + fprintf(stderr, "Error: can not clear capabilities: %s\n", strerror(errno)); + cap_free(caps); + return -1; + } + + if (cap_set_flag(caps, CAP_PERMITTED, ncap, keepcaps, CAP_SET) < 0) { + fprintf(stderr, "Error: can not set permitted capabilities: %s\n", strerror(errno)); + cap_free(caps); + return -1; + } + + if (cap_set_flag(caps, CAP_EFFECTIVE, ncap, keepcaps, CAP_SET) < 0) { + fprintf(stderr, "Error: can not set effective capabilities: %s\n", strerror(errno)); + cap_free(caps); + return -1; + } + + if (cap_set_proc(caps) < 0) { + fprintf(stderr, "Error: can not set process capabilities: %s\n", strerror(errno)); + cap_free(caps); + return -1; + } + + cap_free(caps); + + return 0; +} +#endif + int main(int argc, char** argv) { /* initialize locale */ @@ -265,6 +374,11 @@ int main(int argc, char** argv) { CommandLineSettings flags = parseArguments(argc, argv); +#ifdef HAVE_LIBCAP + if (dropCapabilities(flags.capabilitiesMode) < 0) + exit(1); +#endif + Platform_init(); Process_setupColumnWidths();