From e25ebda78e230283bf707ae3e9655270ff40a7f9 Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Tue, 6 Aug 2024 15:06:14 -0700 Subject: [PATCH] perf cap: Tidy up and improve capability testing 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 Cc: Adrian Hunter Cc: Alexander Shishkin Cc: Athira Rajeev Cc: Changbin Du Cc: Ingo Molnar Cc: James Clark Cc: Jiri Olsa Cc: Kan Liang Cc: Leo Yan Cc: Mark Rutland Cc: Namhyung Kim Cc: Oliver Upton Cc: Peter Zijlstra Link: https://lore.kernel.org/r/20240806220614.831914-1-irogers@google.com Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Makefile.config | 11 ------- tools/perf/builtin-ftrace.c | 28 +++++++++--------- tools/perf/util/Build | 2 +- tools/perf/util/cap.c | 59 ++++++++++++++++++++++++++----------- tools/perf/util/cap.h | 23 ++------------- tools/perf/util/symbol.c | 8 ++--- tools/perf/util/util.c | 12 ++++++-- 7 files changed, 73 insertions(+), 70 deletions(-) diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index fa679db61f62..4eb1fc897baf 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -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 diff --git a/tools/perf/builtin-ftrace.c b/tools/perf/builtin-ftrace.c index a615c405d98f..88a87bf387d2 100644 --- a/tools/perf/builtin-ftrace.c +++ b/tools/perf/builtin-ftrace.c @@ -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); diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 7ea261416c14..b87f918bdfe7 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -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 diff --git a/tools/perf/util/cap.c b/tools/perf/util/cap.c index c3ba841bbf37..7574a67651bc 100644 --- a/tools/perf/util/cap.c +++ b/tools/perf/util/cap.c @@ -3,27 +3,52 @@ * Capability utilities */ -#ifdef HAVE_LIBCAP_SUPPORT - #include "cap.h" -#include -#include +#include "debug.h" +#include +#include +#include +#include +#include -bool perf_cap__capable(cap_value_t cap) +#ifndef SYS_capget +#define SYS_capget 90 +#endif + +#define MAX_LINUX_CAPABILITY_U32S _LINUX_CAPABILITY_U32S_3 + +bool perf_cap__capable(int cap, bool *used_root) { - cap_flag_value_t val; - cap_t caps = cap_get_proc(); + 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; - if (!caps) - return false; + *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; - if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &val) != 0) - val = CAP_CLEAR; + pr_debug2("capget syscall failed (%s - %d) fall back on root check\n", + strerror(errno), errno); + *used_root = true; + return geteuid() == 0; + } - if (cap_free(caps) != 0) - return false; - - return val == CAP_SET; + /* 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 */ diff --git a/tools/perf/util/cap.h b/tools/perf/util/cap.h index ae52878c0b2e..0c6a1ff55f07 100644 --- a/tools/perf/util/cap.h +++ b/tools/perf/util/cap.h @@ -3,26 +3,6 @@ #define __PERF_CAP_H #include -#include -#include - -#ifdef HAVE_LIBCAP_SUPPORT - -#include - -bool perf_cap__capable(cap_value_t cap); - -#else - -#include -#include - -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 */ diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 19eb623e0826..a18927d792af 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -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; diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 4f561e5e4162..9d55a13787ce 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -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