Re: [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]

 



On Fri, 2024-04-05 at 13:33 -0700, Andrii Nakryiko wrote:
[...]
> > 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;
> 
> ack for this part:
> 
> Acked-by: Andrii Nakryiko <andrii@xxxxxxxxxx>
> 
> but I'm not sure if we need all the *_caps logic for selftests just to
> test this, tbh. I'll let other decide, though.

Hi Tao, Andrii,

I agree with Andrii that *_caps logic is a bit heavy-handed.
It works on top of the test_loader.c:drop_capabilities() used for
unpriv testing, so I suggest that we add only one macro: __caps_unpriv(<set-of-caps>),
that would enable specified capabilities when testing unpriv.
E.g. as in the patch at the end of this email (based on Tao's work).

A few additional notes:
- changes to the verifier.c, to the test_loader.c and actual feature
  test should be split in separate commits;
- __caps_unpriv as in Tao's or my patches allows to set arbitrary
  capabilities, however restore_capabilities() uses
  cap_enable_effective(requested_caps), which does logical OR
  of current_caps and requested_caps.
  For the purpose of restore_capabilities(), I think
  cap_enable_effective() should be updated with a flag to overwrite
  current_caps with requested_caps. Otherwise it might not undo
  __caps_unpriv() effects in some cases.
- I changed the test case to avoid BPF_ST assembly instructions
  ('*(u64*)(r10 - 8) = 1') in order to allow compilation by LLVM 16.
  
Thanks,
Eduard

---

diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
index fb2f5513e29e..14a79bc76b45 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().
  *
@@ -72,6 +76,7 @@
 #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)))
+#define __caps_unpriv(caps)	__attribute__((btf_decl_tag("comment:test_caps_unpriv="EXPAND_QUOTE(caps))))
 
 /* Convenience macro for use with 'asm volatile' blocks */
 #define __naked __attribute__((naked))
diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
index 85e48069c9e6..a043514da4f0 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,19 @@ __naked void old_stack_misc_vs_cur_ctx_ptr(void)
 	: __clobber_all);
 }
 
+/* 32-bit scalars should be able to overwrite 64-bit scalar spilled slots */
+SEC("socket")
+__log_level(2) __success __caps_unpriv(CAP_BPF)
+__naked void spill_32bit_onto_64bit_slot(void)
+{
+	asm volatile("					\
+	r0 = 0;						\
+	*(u64*)(r10 - 8) = r0;				\
+	*(u32*)(r10 - 8) = r0;				\
+	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..ad79b5948440 100644
--- a/tools/testing/selftests/bpf/test_loader.c
+++ b/tools/testing/selftests/bpf/test_loader.c
@@ -27,6 +27,7 @@
 #define TEST_TAG_RETVAL_PFX_UNPRIV "comment:test_retval_unpriv="
 #define TEST_TAG_AUXILIARY "comment:test_auxiliary"
 #define TEST_TAG_AUXILIARY_UNPRIV "comment:test_auxiliary_unpriv"
+#define TEST_TAG_CAPS_UNPRIV "comment:test_caps_unpriv="
 #define TEST_BTF_PATH "comment:test_btf_path="
 
 /* Warning: duplicated in bpf_misc.h */
@@ -53,6 +54,7 @@ struct test_subspec {
 	size_t expect_msg_cnt;
 	int retval;
 	bool execute;
+	__u64 caps;
 };
 
 struct test_spec {
@@ -133,6 +135,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 {
@@ -258,6 +287,12 @@ 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_CAPS_UNPRIV)) {
+			val = s + sizeof(TEST_TAG_CAPS_UNPRIV) - 1;
+			err = parse_caps(val, &spec->unpriv.caps, "test caps");
+			if (err)
+				goto cleanup;
+			spec->mode_mask |= UNPRIV;
 		} 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");
@@ -575,6 +610,7 @@ void run_subtest(struct test_loader *tester,
 		return;
 
 	if (unpriv) {
+		fprintf(stderr, "can_execute_unpriv: %d\n", can_execute_unpriv(tester, spec));
 		if (!can_execute_unpriv(tester, spec)) {
 			test__skip();
 			test__end_subtest();
@@ -584,6 +620,13 @@ void run_subtest(struct test_loader *tester,
 			test__end_subtest();
 			return;
 		}
+		if (subspec->caps) {
+			err = cap_enable_effective(subspec->caps, NULL);
+			if (err) {
+				PRINT_FAIL("failed to set capabilities: %i, %s\n", err, strerror(err));
+				goto subtest_cleanup;
+			}
+		}
 	}
 
 	/* Implicitly reset to NULL if next test case doesn't specify */






[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