perf cap: Tidy up and improve capability testing
authorIan Rogers <irogers@google.com>
Tue, 6 Aug 2024 22:06:14 +0000 (15:06 -0700)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Tue, 20 Aug 2024 20:53:12 +0000 (17:53 -0300)
Remove dependence on libcap. libcap is only used to query whether a
capability is supported, which is just 1 capget system call.

If the capget system call fails, fall back on root permission
checking. Previously if libcap fails then the permission is assumed
not present which may be pessimistic/wrong.

Add a used_root out argument to perf_cap__capable to say whether the
fall back root check was used. This allows the correct error message,
"root" vs "users with the CAP_PERFMON or CAP_SYS_ADMIN capability", to
be selected.

Tidy uses of perf_cap__capable so that tests aren't repeated if capget
isn't supported.

Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Athira Rajeev <atrajeev@linux.vnet.ibm.com>
Cc: Changbin Du <changbin.du@huawei.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@arm.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Leo Yan <leo.yan@linux.dev>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Oliver Upton <oliver.upton@linux.dev>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20240806220614.831914-1-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/Makefile.config
tools/perf/builtin-ftrace.c
tools/perf/util/Build
tools/perf/util/cap.c
tools/perf/util/cap.h
tools/perf/util/symbol.c
tools/perf/util/util.c

index fa679db61f6226ef838de3b1653341562960a9e3..4eb1fc897baf64b83fee2205ce9cdd058754a265 100644 (file)
@@ -1031,17 +1031,6 @@ ifndef NO_LIBZSTD
   endif
 endif
 
-ifndef NO_LIBCAP
-  ifeq ($(feature-libcap), 1)
-    CFLAGS += -DHAVE_LIBCAP_SUPPORT
-    EXTLIBS += -lcap
-    $(call detected,CONFIG_LIBCAP)
-  else
-    $(warning No libcap found, disables capability support, please install libcap-devel/libcap-dev)
-    NO_LIBCAP := 1
-  endif
-endif
-
 ifndef NO_BACKTRACE
   ifeq ($(feature-backtrace), 1)
     CFLAGS += -DHAVE_BACKTRACE_SUPPORT
index a615c405d98f73c1f822ab16ad1c51a97d1268b8..88a87bf387d2a62878432060e3cc288be73b3530 100644 (file)
@@ -63,20 +63,21 @@ static void ftrace__workload_exec_failed_signal(int signo __maybe_unused,
        done = true;
 }
 
-static int check_ftrace_capable(void)
+static bool check_ftrace_capable(void)
 {
-       if (!(perf_cap__capable(CAP_PERFMON) ||
-             perf_cap__capable(CAP_SYS_ADMIN))) {
-               pr_err("ftrace only works for %s!\n",
-#ifdef HAVE_LIBCAP_SUPPORT
-               "users with the CAP_PERFMON or CAP_SYS_ADMIN capability"
-#else
-               "root"
-#endif
+       bool used_root;
+
+       if (perf_cap__capable(CAP_PERFMON, &used_root))
+               return true;
+
+       if (!used_root && perf_cap__capable(CAP_SYS_ADMIN, &used_root))
+               return true;
+
+       pr_err("ftrace only works for %s!\n",
+               used_root ? "root"
+                         : "users with the CAP_PERFMON or CAP_SYS_ADMIN capability"
                );
-               return -1;
-       }
-       return 0;
+       return false;
 }
 
 static int __write_tracing_file(const char *name, const char *val, bool append)
@@ -1579,8 +1580,7 @@ int cmd_ftrace(int argc, const char **argv)
        signal(SIGCHLD, sig_handler);
        signal(SIGPIPE, sig_handler);
 
-       ret = check_ftrace_capable();
-       if (ret < 0)
+       if (!check_ftrace_capable())
                return -1;
 
        ret = perf_config(perf_ftrace_config, &ftrace);
index 7ea261416c14fbac5fb9249c3dbe59ce2a7c1a72..b87f918bdfe7dee8eef2c13c5ded28f6801a472c 100644 (file)
@@ -223,7 +223,7 @@ perf-util-$(CONFIG_ZLIB) += zlib.o
 perf-util-$(CONFIG_LZMA) += lzma.o
 perf-util-$(CONFIG_ZSTD) += zstd.o
 
-perf-util-$(CONFIG_LIBCAP) += cap.o
+perf-util-y += cap.o
 
 perf-util-$(CONFIG_CXX_DEMANGLE) += demangle-cxx.o
 perf-util-y += demangle-ocaml.o
index c3ba841bbf3772bffabde9e5d4f0060a8daa9dfa..7574a67651bc545a5e3358a6295457b82f7d5f95 100644 (file)
@@ -3,27 +3,52 @@
  * Capability utilities
  */
 
-#ifdef HAVE_LIBCAP_SUPPORT
-
 #include "cap.h"
-#include <stdbool.h>
-#include <sys/capability.h>
-
-bool perf_cap__capable(cap_value_t cap)
-{
-       cap_flag_value_t val;
-       cap_t caps = cap_get_proc();
+#include "debug.h"
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/capability.h>
+#include <sys/syscall.h>
 
-       if (!caps)
-               return false;
+#ifndef SYS_capget
+#define SYS_capget 90
+#endif
 
-       if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &val) != 0)
-               val = CAP_CLEAR;
+#define MAX_LINUX_CAPABILITY_U32S _LINUX_CAPABILITY_U32S_3
 
-       if (cap_free(caps) != 0)
-               return false;
-
-       return val == CAP_SET;
+bool perf_cap__capable(int cap, bool *used_root)
+{
+       struct __user_cap_header_struct header = {
+               .version = _LINUX_CAPABILITY_VERSION_3,
+               .pid = getpid(),
+       };
+       struct __user_cap_data_struct data[MAX_LINUX_CAPABILITY_U32S];
+       __u32 cap_val;
+
+       *used_root = false;
+       while (syscall(SYS_capget, &header, &data[0]) == -1) {
+               /* Retry, first attempt has set the header.version correctly. */
+               if (errno == EINVAL && header.version != _LINUX_CAPABILITY_VERSION_3 &&
+                   header.version == _LINUX_CAPABILITY_VERSION_1)
+                       continue;
+
+               pr_debug2("capget syscall failed (%s - %d) fall back on root check\n",
+                         strerror(errno), errno);
+               *used_root = true;
+               return geteuid() == 0;
+       }
+
+       /* Extract the relevant capability bit. */
+       if (cap >= 32) {
+               if (header.version == _LINUX_CAPABILITY_VERSION_3) {
+                       cap_val = data[1].effective;
+               } else {
+                       /* Capability beyond 32 is requested but only 32 are supported. */
+                       return false;
+               }
+       } else {
+               cap_val = data[0].effective;
+       }
+       return (cap_val & (1 << (cap & 0x1f))) != 0;
 }
-
-#endif  /* HAVE_LIBCAP_SUPPORT */
index ae52878c0b2e69d976159f0e83a65978c2c7f7df..0c6a1ff55f07340a68538f4eb8f4e4a73d11c5f6 100644 (file)
@@ -3,26 +3,6 @@
 #define __PERF_CAP_H
 
 #include <stdbool.h>
-#include <linux/capability.h>
-#include <linux/compiler.h>
-
-#ifdef HAVE_LIBCAP_SUPPORT
-
-#include <sys/capability.h>
-
-bool perf_cap__capable(cap_value_t cap);
-
-#else
-
-#include <unistd.h>
-#include <sys/types.h>
-
-static inline bool perf_cap__capable(int cap __maybe_unused)
-{
-       return geteuid() == 0;
-}
-
-#endif /* HAVE_LIBCAP_SUPPORT */
 
 /* For older systems */
 #ifndef CAP_SYSLOG
@@ -33,4 +13,7 @@ static inline bool perf_cap__capable(int cap __maybe_unused)
 #define CAP_PERFMON    38
 #endif
 
+/* Query if a capability is supported, used_root is set if the fallback root check was used. */
+bool perf_cap__capable(int cap, bool *used_root);
+
 #endif /* __PERF_CAP_H */
index 19eb623e08266b46e808e8d9f0773c79914fc888..a18927d792afc504c8641ff2915b3a89b4c37031 100644 (file)
@@ -2425,14 +2425,14 @@ static bool symbol__read_kptr_restrict(void)
 {
        bool value = false;
        FILE *fp = fopen("/proc/sys/kernel/kptr_restrict", "r");
+       bool used_root;
+       bool cap_syslog = perf_cap__capable(CAP_SYSLOG, &used_root);
 
        if (fp != NULL) {
                char line[8];
 
                if (fgets(line, sizeof(line), fp) != NULL)
-                       value = perf_cap__capable(CAP_SYSLOG) ?
-                                       (atoi(line) >= 2) :
-                                       (atoi(line) != 0);
+                       value = cap_syslog ? (atoi(line) >= 2) : (atoi(line) != 0);
 
                fclose(fp);
        }
@@ -2440,7 +2440,7 @@ static bool symbol__read_kptr_restrict(void)
        /* Per kernel/kallsyms.c:
         * we also restrict when perf_event_paranoid > 1 w/o CAP_SYSLOG
         */
-       if (perf_event_paranoid() > 1 && !perf_cap__capable(CAP_SYSLOG))
+       if (perf_event_paranoid() > 1 && !cap_syslog)
                value = true;
 
        return value;
index 4f561e5e41622fbfe11dea9f7ae06e31c259953a..9d55a13787ce3c051d8109d3ca7d064697481904 100644 (file)
@@ -325,9 +325,15 @@ int perf_event_paranoid(void)
 
 bool perf_event_paranoid_check(int max_level)
 {
-       return perf_cap__capable(CAP_SYS_ADMIN) ||
-                       perf_cap__capable(CAP_PERFMON) ||
-                       perf_event_paranoid() <= max_level;
+       bool used_root;
+
+       if (perf_cap__capable(CAP_SYS_ADMIN, &used_root))
+               return true;
+
+       if (!used_root && perf_cap__capable(CAP_PERFMON, &used_root))
+               return true;
+
+       return perf_event_paranoid() <= max_level;
 }
 
 static int