[PATCH bpf-next] bpf: Fix verifier error due to narrower scalar spill onto 64-bit spilled scalar slots

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



When CAP_PERFMON and CAP_SYS_ADMIN (allow_ptr_leaks) are disabled,
the verifier previously aimed to reject partial overwrite on an 8-byte stack slot
that contains a spilled pointer.

However, it rejects all partial stack overwrites
as long as the targeted stack slot is a spilled register in that cap setting,
because it does not check if the stack slot is a spilled pointer.

Finally, incomplete checks will result in the rejection of valid programs,
which spill narrower scalar values onto scalar slots, as shown below.

0: R1=ctx() R10=fp0
; asm volatile ( @ repro.bpf.c:679
0: (7a) *(u64 *)(r10 -8) = 1          ; R10=fp0 fp-8_w=1
1: (62) *(u32 *)(r10 -8) = 1
attempt to corrupt spilled pointer on stack
processed 2 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0.

Note that only when CAP_BPF or CAP_SYS_ADMIN are enabled,
are 64-bit scalars stored onto stacks marked as spilled scalars.

Thus, to reproduce this issue,
we can only assign CAP_BPF capability to the test.

However, the existing bpf selftest doesn't support setting specific caps.

So, I modified the test_loader.c and some related header files to
enabled set specific capabilities on a test with the following attributes:

__msg_caps(msg)
__failure_caps(caps)
__success_caps(caps)
__retval_caps(val)

Here, caps can be any combination of capabilities, like "CAP_BPF | CAP_PERFMON".

Finally, the issue is fixed by checking the spilled register type of targeted slots.

Fixes: ab125ed3ec1c ("bpf: fix check for attempt to corrupt spilled pointer")
Signed-off-by: Tao Lyu <tao.lyu@xxxxxxx>
---
 kernel/bpf/verifier.c                         |   1 +
 tools/testing/selftests/bpf/progs/bpf_misc.h  |  13 ++
 .../selftests/bpf/progs/verifier_spill_fill.c |  18 ++
 tools/testing/selftests/bpf/test_loader.c     | 154 ++++++++++++++++--
 4 files changed, 169 insertions(+), 17 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 63749ad5ac6b..da575e295d53 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4493,6 +4493,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
 	 */
 	if (!env->allow_ptr_leaks &&
 	    is_spilled_reg(&state->stack[spi]) &&
+	    !is_spilled_scalar_reg(&state->stack[spi]) &&
 	    size != BPF_REG_SIZE) {
 		verbose(env, "attempt to corrupt spilled pointer on stack\n");
 		return -EACCES;
diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
index fb2f5513e29e..b8dc0d73932f 100644
--- a/tools/testing/selftests/bpf/progs/bpf_misc.h
+++ b/tools/testing/selftests/bpf/progs/bpf_misc.h
@@ -2,6 +2,10 @@
 #ifndef __BPF_MISC_H__
 #define __BPF_MISC_H__
 
+/* Expand a macro and then stringize the expansion */
+#define QUOTE(str) #str
+#define EXPAND_QUOTE(str) QUOTE(str)
+
 /* This set of attributes controls behavior of the
  * test_loader.c:test_loader__run_subtests().
  *
@@ -24,11 +28,15 @@
  *                   Multiple __msg attributes could be specified.
  * __msg_unpriv      Same as __msg but for unprivileged mode.
  *
+ * __msg_caps        Same as __msg but for specified capabilities.
+ *
  * __success         Expect program load success in privileged mode.
  * __success_unpriv  Expect program load success in unprivileged mode.
+ * __success_caps    Expect program load success in specified cap mode.
  *
  * __failure         Expect program load failure in privileged mode.
  * __failure_unpriv  Expect program load failure in unprivileged mode.
+ * __failure_caps    Expect program load failure in specified cap mode.
  *
  * __retval          Execute the program using BPF_PROG_TEST_RUN command,
  *                   expect return value to match passed parameter:
@@ -38,6 +46,7 @@
  *                   - literal POINTER_VALUE (see definition below)
  *                   - literal TEST_DATA_LEN (see definition below)
  * __retval_unpriv   Same, but load program in unprivileged mode.
+ * __retval_caps     Same, but load program in specified cap mode.
  *
  * __description     Text to be used instead of a program name for display
  *                   and filtering purposes.
@@ -63,12 +72,16 @@
 #define __success		__attribute__((btf_decl_tag("comment:test_expect_success")))
 #define __description(desc)	__attribute__((btf_decl_tag("comment:test_description=" desc)))
 #define __msg_unpriv(msg)	__attribute__((btf_decl_tag("comment:test_expect_msg_unpriv=" msg)))
+#define __msg_caps(msg)	__attribute__((btf_decl_tag("comment:test_expect_msg_caps=" msg)))
 #define __failure_unpriv	__attribute__((btf_decl_tag("comment:test_expect_failure_unpriv")))
 #define __success_unpriv	__attribute__((btf_decl_tag("comment:test_expect_success_unpriv")))
+#define __failure_caps(caps)	__attribute__((btf_decl_tag("comment:test_expect_failure_caps="EXPAND_QUOTE(caps))))
+#define __success_caps(caps)	__attribute__((btf_decl_tag("comment:test_expect_success_caps="EXPAND_QUOTE(caps))))
 #define __log_level(lvl)	__attribute__((btf_decl_tag("comment:test_log_level="#lvl)))
 #define __flag(flag)		__attribute__((btf_decl_tag("comment:test_prog_flags="#flag)))
 #define __retval(val)		__attribute__((btf_decl_tag("comment:test_retval="#val)))
 #define __retval_unpriv(val)	__attribute__((btf_decl_tag("comment:test_retval_unpriv="#val)))
+#define __retval_caps(val)	__attribute__((btf_decl_tag("comment:test_retval_caps="#val)))
 #define __auxiliary		__attribute__((btf_decl_tag("comment:test_auxiliary")))
 #define __auxiliary_unpriv	__attribute__((btf_decl_tag("comment:test_auxiliary_unpriv")))
 #define __btf_path(path)	__attribute__((btf_decl_tag("comment:test_btf_path=" path)))
diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
index 85e48069c9e6..c3eb6f7cf0c2 100644
--- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
+++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
@@ -5,6 +5,7 @@
 #include <bpf/bpf_helpers.h>
 #include "bpf_misc.h"
 #include <../../../tools/include/linux/filter.h>
+#include "../cap_helpers.h"
 
 struct {
 	__uint(type, BPF_MAP_TYPE_RINGBUF);
@@ -1244,4 +1245,21 @@ __naked void old_stack_misc_vs_cur_ctx_ptr(void)
 	: __clobber_all);
 }
 
+SEC("socket")
+__description("32-bit scalars should be able to overwrite 64-bit scalar spilled slots")
+__log_level(2)
+__success __success_unpriv
+__success_caps(CAP_BPF) __retval_caps(0)
+__naked void spill_32bit_onto_64bit_slot(void)
+{
+	asm volatile("					\
+	*(u64*)(r10 - 8) = 1;				\
+	*(u32*)(r10 - 8) = 1;				\
+	r0 = 0;						\
+	exit;						\
+"	:
+	:
+	: __clobber_all);
+}
+
 char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_loader.c b/tools/testing/selftests/bpf/test_loader.c
index 524c38e9cde4..42701ec0212f 100644
--- a/tools/testing/selftests/bpf/test_loader.c
+++ b/tools/testing/selftests/bpf/test_loader.c
@@ -20,11 +20,15 @@
 #define TEST_TAG_EXPECT_FAILURE_UNPRIV "comment:test_expect_failure_unpriv"
 #define TEST_TAG_EXPECT_SUCCESS_UNPRIV "comment:test_expect_success_unpriv"
 #define TEST_TAG_EXPECT_MSG_PFX_UNPRIV "comment:test_expect_msg_unpriv="
+#define TEST_TAG_EXPECT_FAILURE_CAPS "comment:test_expect_failure_caps="
+#define TEST_TAG_EXPECT_SUCCESS_CAPS "comment:test_expect_success_caps="
+#define TEST_TAG_EXPECT_MSG_PFX_CAPS "comment:test_expect_msg_caps="
 #define TEST_TAG_LOG_LEVEL_PFX "comment:test_log_level="
 #define TEST_TAG_PROG_FLAGS_PFX "comment:test_prog_flags="
 #define TEST_TAG_DESCRIPTION_PFX "comment:test_description="
 #define TEST_TAG_RETVAL_PFX "comment:test_retval="
 #define TEST_TAG_RETVAL_PFX_UNPRIV "comment:test_retval_unpriv="
+#define TEST_TAG_RETVAL_PFX_CAPS "comment:test_retval_caps="
 #define TEST_TAG_AUXILIARY "comment:test_auxiliary"
 #define TEST_TAG_AUXILIARY_UNPRIV "comment:test_auxiliary_unpriv"
 #define TEST_BTF_PATH "comment:test_btf_path="
@@ -43,7 +47,8 @@ static int sysctl_unpriv_disabled = -1;
 
 enum mode {
 	PRIV = 1,
-	UNPRIV = 2
+	UNPRIV = 2,
+	CUSTCAPS = 4
 };
 
 struct test_subspec {
@@ -59,6 +64,8 @@ struct test_spec {
 	const char *prog_name;
 	struct test_subspec priv;
 	struct test_subspec unpriv;
+	struct test_subspec custom_caps;
+	__u64 caps;
 	const char *btf_custom_path;
 	int log_level;
 	int prog_flags;
@@ -133,6 +140,33 @@ static int parse_int(const char *str, int *val, const char *name)
 	return 0;
 }
 
+static int parse_caps(const char *str, __u64 *val, const char *name)
+{
+	int cap_flag = 0;
+	char *token = NULL, *saveptr = NULL;
+
+	char *str_cpy = strdup(str);
+	if (str_cpy == NULL) {
+		PRINT_FAIL("Memory allocation failed\n");
+		return -EINVAL;
+	}
+
+	token = strtok_r(str_cpy, "|", &saveptr);
+	while (token != NULL) {
+		errno = 0;
+		cap_flag = strtol(token, NULL, 10);
+		if (errno) {
+			PRINT_FAIL("failed to parse caps %s\n", name);
+			return -EINVAL;
+		}
+		*val |= (1ULL << cap_flag);
+		token = strtok_r(NULL, "|", &saveptr);
+	}
+
+	free(str_cpy);
+	return 0;
+}
+
 static int parse_retval(const char *str, int *val, const char *name)
 {
 	struct {
@@ -163,6 +197,22 @@ static void update_flags(int *flags, int flag, bool clear)
 		*flags |= flag;
 }
 
+static char *create_desc(const char *description, const char *suffix)
+{
+	int descr_len = strlen(description);
+	char *name;
+
+	name = malloc(descr_len + strlen(suffix) + 1);
+	if (!name) {
+		PRINT_FAIL("failed to allocate memory for unpriv.name\n");
+		return NULL;
+	}
+
+	strcpy(name, description);
+	strcpy(&name[descr_len], suffix);
+	return name;
+}
+
 /* Uses btf_decl_tag attributes to describe the expected test
  * behavior, see bpf_misc.h for detailed description of each attribute
  * and attribute combinations.
@@ -225,6 +275,20 @@ static int parse_test_spec(struct test_loader *tester,
 			spec->unpriv.expect_failure = false;
 			spec->mode_mask |= UNPRIV;
 			has_unpriv_result = true;
+		} else if (str_has_pfx(s, TEST_TAG_EXPECT_FAILURE_CAPS)) {
+			val = s + sizeof(TEST_TAG_EXPECT_FAILURE_CAPS) - 1;
+			err = parse_caps(val, &spec->caps, "test caps");
+			if (err)
+				goto cleanup;
+			spec->custom_caps.expect_failure = true;
+			spec->mode_mask |= CUSTCAPS;
+                } else if (str_has_pfx(s, TEST_TAG_EXPECT_SUCCESS_CAPS)) {
+			val = s + sizeof(TEST_TAG_EXPECT_SUCCESS_CAPS) - 1;
+			err = parse_caps(val, &spec->caps, "test caps");
+			if (err)
+				goto cleanup;
+			spec->custom_caps.expect_failure = false;
+			spec->mode_mask |= CUSTCAPS;
 		} else if (strcmp(s, TEST_TAG_AUXILIARY) == 0) {
 			spec->auxiliary = true;
 			spec->mode_mask |= PRIV;
@@ -243,6 +307,12 @@ static int parse_test_spec(struct test_loader *tester,
 			if (err)
 				goto cleanup;
 			spec->mode_mask |= UNPRIV;
+		} else if (str_has_pfx(s, TEST_TAG_EXPECT_MSG_PFX_CAPS)) {
+			msg = s + sizeof(TEST_TAG_EXPECT_MSG_PFX_CAPS) - 1;
+			err = push_msg(msg, &spec->custom_caps);
+			if (err)
+				goto cleanup;
+			spec->mode_mask |= CUSTCAPS;
 		} else if (str_has_pfx(s, TEST_TAG_RETVAL_PFX)) {
 			val = s + sizeof(TEST_TAG_RETVAL_PFX) - 1;
 			err = parse_retval(val, &spec->priv.retval, "__retval");
@@ -258,6 +328,13 @@ static int parse_test_spec(struct test_loader *tester,
 			spec->mode_mask |= UNPRIV;
 			spec->unpriv.execute = true;
 			has_unpriv_retval = true;
+		} else if (str_has_pfx(s, TEST_TAG_RETVAL_PFX_CAPS)) {
+			val = s + sizeof(TEST_TAG_RETVAL_PFX_CAPS) - 1;
+			err = parse_retval(val, &spec->custom_caps.retval, "__retval_caps");
+			if (err)
+				goto cleanup;
+			spec->mode_mask |= CUSTCAPS;
+			spec->custom_caps.execute = true;
 		} else if (str_has_pfx(s, TEST_TAG_LOG_LEVEL_PFX)) {
 			val = s + sizeof(TEST_TAG_LOG_LEVEL_PFX) - 1;
 			err = parse_int(val, &spec->log_level, "test log level");
@@ -311,22 +388,23 @@ static int parse_test_spec(struct test_loader *tester,
 	}
 
 	if (spec->mode_mask & UNPRIV) {
-		int descr_len = strlen(description);
-		const char *suffix = " @unpriv";
-		char *name;
-
-		name = malloc(descr_len + strlen(suffix) + 1);
+		char *name = create_desc(description, " @unpriv");
 		if (!name) {
-			PRINT_FAIL("failed to allocate memory for unpriv.name\n");
 			err = -ENOMEM;
 			goto cleanup;
 		}
-
-		strcpy(name, description);
-		strcpy(&name[descr_len], suffix);
 		spec->unpriv.name = name;
 	}
 
+	if (spec->mode_mask & CUSTCAPS) {
+		char *name = create_desc(description, " @caps");
+		if (!name) {
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		spec->custom_caps.name = name;
+	}
+
 	if (spec->mode_mask & (PRIV | UNPRIV)) {
 		if (!has_unpriv_result)
 			spec->unpriv.expect_failure = spec->priv.expect_failure;
@@ -461,6 +539,28 @@ static int restore_capabilities(struct cap_state *caps)
 	return err;
 }
 
+static int set_capabilities(__u64 new_caps, struct cap_state *save_caps) {
+
+	int err;
+
+	/* Drop all bpf related caps */
+	err = drop_capabilities(save_caps);
+	if (err) {
+		PRINT_FAIL("failed to drop capabilities: %i, %s\n", err, strerror(err));
+		return err;
+	}
+
+	/* Set the specified caps */
+	err = cap_enable_effective(new_caps, NULL);
+	if (err) {
+		PRINT_FAIL("failed to set capabilities: %i, %s\n", err, strerror(err));
+		return err;
+	}
+
+	save_caps->initialized = true;
+	return 0;
+}
+
 static bool can_execute_unpriv(struct test_loader *tester, struct test_spec *spec)
 {
 	if (sysctl_unpriv_disabled < 0)
@@ -560,21 +660,34 @@ void run_subtest(struct test_loader *tester,
 		 size_t obj_byte_cnt,
 		 struct test_spec *specs,
 		 struct test_spec *spec,
-		 bool unpriv)
+		 int priv_level)
 {
-	struct test_subspec *subspec = unpriv ? &spec->unpriv : &spec->priv;
+	struct test_subspec *subspec = NULL;
 	struct bpf_program *tprog = NULL, *tprog_iter;
 	struct test_spec *spec_iter;
 	struct cap_state caps = {};
 	struct bpf_object *tobj;
 	struct bpf_map *map;
 	int retval, err, i;
-	bool should_load;
+	bool should_load, unpriv = (priv_level != PRIV);
+
+	switch (priv_level) {
+		case PRIV:
+			subspec = &spec->priv;
+			break;
+		case UNPRIV:
+			subspec = &spec->unpriv;
+			break;
+		case CUSTCAPS:
+			subspec = &spec->custom_caps;
+			break;
+		default:
+	}
 
 	if (!test__start_subtest(subspec->name))
 		return;
 
-	if (unpriv) {
+	if (priv_level == UNPRIV) {
 		if (!can_execute_unpriv(tester, spec)) {
 			test__skip();
 			test__end_subtest();
@@ -584,6 +697,11 @@ void run_subtest(struct test_loader *tester,
 			test__end_subtest();
 			return;
 		}
+	} else if (priv_level == CUSTCAPS) {
+		if (set_capabilities(spec->caps, &caps)) {
+			test__end_subtest();
+			return;
+		}
 	}
 
 	/* Implicitly reset to NULL if next test case doesn't specify */
@@ -714,11 +832,13 @@ static void process_subtest(struct test_loader *tester,
 
 		if (spec->mode_mask & PRIV)
 			run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt,
-				    specs, spec, false);
+				    specs, spec, PRIV);
 		if (spec->mode_mask & UNPRIV)
 			run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt,
-				    specs, spec, true);
-
+				    specs, spec, UNPRIV);
+		if (spec->mode_mask & CUSTCAPS)
+			run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt,
+				    specs, spec, CUSTCAPS);
 	}
 
 	for (i = 0; i < nr_progs; ++i)
-- 
2.34.1





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux