[PATCH bpf-next v1] selftests/bpf:Enhance bpf ability to detect ksym read error by libcap

Lin Yikai posted 1 patch 2 months, 2 weeks ago
tools/testing/selftests/bpf/Makefile        |  7 ++-
tools/testing/selftests/bpf/trace_helpers.c | 63 +++++++++++++++++++++
2 files changed, 69 insertions(+), 1 deletion(-)
[PATCH bpf-next v1] selftests/bpf:Enhance bpf ability to detect ksym read error by libcap
Posted by Lin Yikai 2 months, 2 weeks ago
Ksym addr access is restricted
by ``kptr_restrict``(/proc/sys/kernel/kptr_restrict).

On some OS systems(like Android),
ksym addr access is not accessed because ``kptr_restrict=2`.
And it took me a long time to find the root case.

-When ``kptr_restrict==0``, addr is accessed.
	# echo 0 > /proc/sys/kernel/kptr_restrict
	# cat /proc/kallsyms | grep bpf_link_fops
	ffffffd6bfd3fb60 d bpf_link_fops
-When ``kptr_restrict==2``, addr is replaced by ZERO.
	# echo 2 > /proc/sys/kernel/kptr_restrict
	# cat /proc/kallsyms | grep bpf_link_fops
	0000000000000000 d bpf_link_fops
-When ``kptr_restrict==1``, addr is accessed for user having CAP_SYSLOG.

So we should perform a check to remind users for these conditions
before reading /proc/kallsyms.

[before]:
	# echo 2 > /proc/sys/kernel/kptr_restrict
	# ./test_progs -t ksyms
	#133     ksyms:FAIL

[after]:
	# echo 2 > /proc/sys/kernel/kptr_restrict
	# ./test_progs -t ksym
	ksyms restricted, please check /proc/sys/kernel/kptr_restrict
	#133     ksyms:FAIL

Signed-off-by: Lin Yikai <yikai.lin@vivo.com>
---
 tools/testing/selftests/bpf/Makefile        |  7 ++-
 tools/testing/selftests/bpf/trace_helpers.c | 63 +++++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 04716a5e43f1..369c5ad8fc4a 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -183,7 +183,7 @@ NON_CHECK_FEAT_TARGETS := clean docs-clean
 CHECK_FEAT := $(filter-out $(NON_CHECK_FEAT_TARGETS),$(or $(MAKECMDGOALS), "none"))
 ifneq ($(CHECK_FEAT),)
 FEATURE_USER := .selftests
-FEATURE_TESTS := llvm
+FEATURE_TESTS := llvm libcap
 FEATURE_DISPLAY := $(FEATURE_TESTS)
 
 # Makefile.feature expects OUTPUT to end with a slash
@@ -208,6 +208,11 @@ ifeq ($(feature-llvm),1)
   LLVM_LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags)
 endif
 
+ifeq ($(feature-libcap), 1)
+  CFLAGS += -DHAVE_LIBCAP_SUPPORT
+  LDLIBS += -lcap
+endif
+
 SCRATCH_DIR := $(OUTPUT)/tools
 BUILD_DIR := $(SCRATCH_DIR)/build
 INCLUDE_DIR := $(SCRATCH_DIR)/include
diff --git a/tools/testing/selftests/bpf/trace_helpers.c b/tools/testing/selftests/bpf/trace_helpers.c
index 2d742fdac6b9..8d2f951464ff 100644
--- a/tools/testing/selftests/bpf/trace_helpers.c
+++ b/tools/testing/selftests/bpf/trace_helpers.c
@@ -17,6 +17,10 @@
 #include <linux/limits.h>
 #include <libelf.h>
 #include <gelf.h>
+#include <stdbool.h>
+#include <linux/capability.h>
+#include <linux/compiler.h>
+#include <sys/types.h>
 #include "bpf/libbpf_internal.h"
 
 #define TRACEFS_PIPE	"/sys/kernel/tracing/trace_pipe"
@@ -31,6 +35,55 @@ struct ksyms {
 static struct ksyms *ksyms;
 static pthread_mutex_t ksyms_mutex = PTHREAD_MUTEX_INITIALIZER;
 
+#ifdef HAVE_LIBCAP_SUPPORT
+#include <sys/capability.h>
+static bool bpf_cap__capable(cap_value_t cap)
+{
+	cap_flag_value_t val;
+	cap_t caps = cap_get_proc();
+
+	if (!caps)
+		return false;
+
+	if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &val) != 0)
+		val = CAP_CLEAR;
+
+	if (cap_free(caps) != 0)
+		return false;
+
+	return val == CAP_SET;
+}
+#else
+static inline bool bpf_cap__capable(int cap __maybe_unused)
+{
+	return geteuid() == 0;
+}
+#endif /* HAVE_LIBCAP_SUPPORT */
+
+/* For older systems */
+#ifndef CAP_SYSLOG
+#define CAP_SYSLOG	34
+#endif
+
+static bool ksyms__kptr_restrict(void)
+{
+	bool value = false;
+	FILE *fp = fopen("/proc/sys/kernel/kptr_restrict", "r");
+
+	if (fp != NULL) {
+		char line[8];
+
+		if (fgets(line, sizeof(line), fp) != NULL)
+			value = bpf_cap__capable(CAP_SYSLOG) ?
+					(atoi(line) >= 2) :
+					(atoi(line) != 0);
+
+		fclose(fp);
+	}
+
+	return value;
+}
+
 static int ksyms__add_symbol(struct ksyms *ksyms, const char *name,
 			     unsigned long addr)
 {
@@ -72,6 +125,11 @@ static struct ksyms *load_kallsyms_local_common(ksym_cmp_t cmp_cb)
 	int ret;
 	struct ksyms *ksyms;
 
+	if (ksyms__kptr_restrict()) {
+		printf("ksyms restricted, please check /proc/sys/kernel/kptr_restrict\n");
+		return NULL;
+	}
+
 	f = fopen("/proc/kallsyms", "r");
 	if (!f)
 		return NULL;
@@ -218,6 +276,11 @@ int kallsyms_find(const char *sym, unsigned long long *addr)
 	int err = 0;
 	FILE *f;
 
+	if (ksyms__kptr_restrict()) {
+		printf("ksyms restricted, please check /proc/sys/kernel/kptr_restrict\n");
+		return -EINVAL;
+	}
+
 	f = fopen("/proc/kallsyms", "r");
 	if (!f)
 		return -EINVAL;
-- 
2.34.1
Re: [PATCH bpf-next v1] selftests/bpf:Enhance bpf ability to detect ksym read error by libcap
Posted by Martin KaFai Lau 2 months ago
On 9/14/24 11:24 AM, Lin Yikai wrote:
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index 04716a5e43f1..369c5ad8fc4a 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -183,7 +183,7 @@ NON_CHECK_FEAT_TARGETS := clean docs-clean
>   CHECK_FEAT := $(filter-out $(NON_CHECK_FEAT_TARGETS),$(or $(MAKECMDGOALS), "none"))
>   ifneq ($(CHECK_FEAT),)
>   FEATURE_USER := .selftests
> -FEATURE_TESTS := llvm
> +FEATURE_TESTS := llvm libcap
>   FEATURE_DISPLAY := $(FEATURE_TESTS)
>   
>   # Makefile.feature expects OUTPUT to end with a slash
> @@ -208,6 +208,11 @@ ifeq ($(feature-llvm),1)
>     LLVM_LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags)
>   endif
>   
> +ifeq ($(feature-libcap), 1)
> +  CFLAGS += -DHAVE_LIBCAP_SUPPORT
> +  LDLIBS += -lcap
> +endif
> +

[ ... ]

> @@ -31,6 +35,55 @@ struct ksyms {
>   static struct ksyms *ksyms;
>   static pthread_mutex_t ksyms_mutex = PTHREAD_MUTEX_INITIALIZER;
>   
> +#ifdef HAVE_LIBCAP_SUPPORT
> +#include <sys/capability.h>
> +static bool bpf_cap__capable(cap_value_t cap)
> +{
> +	cap_flag_value_t val;
> +	cap_t caps = cap_get_proc();
> +
> +	if (!caps)
> +		return false;
> +
> +	if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &val) != 0)

Instead of adding new dependency on libcap, please check if capget() can 
directly be used. Take a look at tools/testing/selftests/bpf/cap_helpers.c.

pw-bot: cr

> +		val = CAP_CLEAR;
> +
> +	if (cap_free(caps) != 0)
> +		return false;
> +
> +	return val == CAP_SET;
> +}