mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 16:56:26 +00:00
Merge branch 'fixes-for-stack-with-allow_ptr_leaks'
Kumar Kartikeya Dwivedi says: ==================== Fixes for stack with allow_ptr_leaks Two fixes for usability/correctness gaps when interacting with the stack without CAP_PERFMON (i.e. with allow_ptr_leaks = false). See the commits for details. I've verified that the tests fail when run without the fixes. Changelog: ---------- v3 -> v4 v3: https://lore.kernel.org/bpf/20241202083814.1888784-1-memxor@gmail.com * Address Andrii's comments * Fix bug paperered over by missing CAP_NET_ADMIN in verifier_mtu test * Add warning when undefined CAP_ constant is specified, and fail test * Reorder annotations to be more clear * Verify that fixes fail without patches again * Add Acked-by from Andrii v2 -> v3 v2: https://lore.kernel.org/bpf/20241127212026.3580542-1-memxor@gmail.com * Address comments from Eduard * Fix comment for mark_stack_slot_misc * We can simply always return early when stype == STACK_INVALID * Drop allow_ptr_leaks conditionals * Add Eduard's __caps_unpriv patch into the series * Convert test_verifier_mtu to use it * Move existing tests to __caps_unpriv annotation and verifier_spill_fill.c * Add Acked-by from Eduard v1 -> v2 v1: https://lore.kernel.org/bpf/20241127185135.2753982-1-memxor@gmail.com * Fix CI errors in selftest by removing dependence on BPF_ST ==================== Link: https://patch.msgid.link/20241204044757.1483141-1-memxor@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
e2cf913314
@ -1202,14 +1202,17 @@ static bool is_spilled_scalar_reg64(const struct bpf_stack_state *stack)
|
||||
/* Mark stack slot as STACK_MISC, unless it is already STACK_INVALID, in which
|
||||
* case they are equivalent, or it's STACK_ZERO, in which case we preserve
|
||||
* more precise STACK_ZERO.
|
||||
* Note, in uprivileged mode leaving STACK_INVALID is wrong, so we take
|
||||
* env->allow_ptr_leaks into account and force STACK_MISC, if necessary.
|
||||
* Regardless of allow_ptr_leaks setting (i.e., privileged or unprivileged
|
||||
* mode), we won't promote STACK_INVALID to STACK_MISC. In privileged case it is
|
||||
* unnecessary as both are considered equivalent when loading data and pruning,
|
||||
* in case of unprivileged mode it will be incorrect to allow reads of invalid
|
||||
* slots.
|
||||
*/
|
||||
static void mark_stack_slot_misc(struct bpf_verifier_env *env, u8 *stype)
|
||||
{
|
||||
if (*stype == STACK_ZERO)
|
||||
return;
|
||||
if (env->allow_ptr_leaks && *stype == STACK_INVALID)
|
||||
if (*stype == STACK_INVALID)
|
||||
return;
|
||||
*stype = STACK_MISC;
|
||||
}
|
||||
@ -4700,6 +4703,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;
|
||||
|
@ -225,24 +225,7 @@ void test_verifier_xdp(void) { RUN(verifier_xdp); }
|
||||
void test_verifier_xdp_direct_packet_access(void) { RUN(verifier_xdp_direct_packet_access); }
|
||||
void test_verifier_bits_iter(void) { RUN(verifier_bits_iter); }
|
||||
void test_verifier_lsm(void) { RUN(verifier_lsm); }
|
||||
|
||||
void test_verifier_mtu(void)
|
||||
{
|
||||
__u64 caps = 0;
|
||||
int ret;
|
||||
|
||||
/* In case CAP_BPF and CAP_PERFMON is not set */
|
||||
ret = cap_enable_effective(1ULL << CAP_BPF | 1ULL << CAP_NET_ADMIN, &caps);
|
||||
if (!ASSERT_OK(ret, "set_cap_bpf_cap_net_admin"))
|
||||
return;
|
||||
ret = cap_disable_effective(1ULL << CAP_SYS_ADMIN | 1ULL << CAP_PERFMON, NULL);
|
||||
if (!ASSERT_OK(ret, "disable_cap_sys_admin"))
|
||||
goto restore_cap;
|
||||
RUN(verifier_mtu);
|
||||
restore_cap:
|
||||
if (caps)
|
||||
cap_enable_effective(caps, NULL);
|
||||
}
|
||||
void test_verifier_mtu(void) { RUN(verifier_mtu); }
|
||||
|
||||
static int init_test_val_map(struct bpf_object *obj, char *map_name)
|
||||
{
|
||||
|
@ -5,6 +5,10 @@
|
||||
#define XSTR(s) STR(s)
|
||||
#define STR(s) #s
|
||||
|
||||
/* 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().
|
||||
*
|
||||
@ -106,6 +110,7 @@
|
||||
* __arch_* Specify on which architecture the test case should be tested.
|
||||
* Several __arch_* annotations could be specified at once.
|
||||
* When test case is not run on current arch it is marked as skipped.
|
||||
* __caps_unpriv Specify the capabilities that should be set when running the test.
|
||||
*/
|
||||
#define __msg(msg) __attribute__((btf_decl_tag("comment:test_expect_msg=" XSTR(__COUNTER__) "=" msg)))
|
||||
#define __xlated(msg) __attribute__((btf_decl_tag("comment:test_expect_xlated=" XSTR(__COUNTER__) "=" msg)))
|
||||
@ -129,6 +134,13 @@
|
||||
#define __arch_x86_64 __arch("X86_64")
|
||||
#define __arch_arm64 __arch("ARM64")
|
||||
#define __arch_riscv64 __arch("RISCV64")
|
||||
#define __caps_unpriv(caps) __attribute__((btf_decl_tag("comment:test_caps_unpriv=" EXPAND_QUOTE(caps))))
|
||||
|
||||
/* Define common capabilities tested using __caps_unpriv */
|
||||
#define CAP_NET_ADMIN 12
|
||||
#define CAP_SYS_ADMIN 21
|
||||
#define CAP_PERFMON 38
|
||||
#define CAP_BPF 39
|
||||
|
||||
/* Convenience macro for use with 'asm volatile' blocks */
|
||||
#define __naked __attribute__((naked))
|
||||
|
@ -6,7 +6,9 @@
|
||||
|
||||
SEC("tc/ingress")
|
||||
__description("uninit/mtu: write rejected")
|
||||
__failure __msg("invalid indirect read from stack")
|
||||
__success
|
||||
__caps_unpriv(CAP_BPF|CAP_NET_ADMIN)
|
||||
__failure_unpriv __msg_unpriv("invalid indirect read from stack")
|
||||
int tc_uninit_mtu(struct __sk_buff *ctx)
|
||||
{
|
||||
__u32 mtu;
|
||||
|
@ -1244,4 +1244,39 @@ __naked void old_stack_misc_vs_cur_ctx_ptr(void)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("stack_noperfmon: reject read of invalid slots")
|
||||
__success
|
||||
__caps_unpriv(CAP_BPF)
|
||||
__failure_unpriv __msg_unpriv("invalid read from stack off -8+1 size 8")
|
||||
__naked void stack_noperfmon_reject_invalid_read(void)
|
||||
{
|
||||
asm volatile (" \
|
||||
r2 = 1; \
|
||||
r6 = r10; \
|
||||
r6 += -8; \
|
||||
*(u8 *)(r6 + 0) = r2; \
|
||||
r2 = *(u64 *)(r6 + 0); \
|
||||
r0 = 0; \
|
||||
exit; \
|
||||
" ::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("stack_noperfmon: narrow spill onto 64-bit scalar spilled slots")
|
||||
__success
|
||||
__caps_unpriv(CAP_BPF)
|
||||
__success_unpriv
|
||||
__naked void stack_noperfmon_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";
|
||||
|
@ -36,6 +36,7 @@
|
||||
#define TEST_TAG_ARCH "comment:test_arch="
|
||||
#define TEST_TAG_JITED_PFX "comment:test_jited="
|
||||
#define TEST_TAG_JITED_PFX_UNPRIV "comment:test_jited_unpriv="
|
||||
#define TEST_TAG_CAPS_UNPRIV "comment:test_caps_unpriv="
|
||||
|
||||
/* Warning: duplicated in bpf_misc.h */
|
||||
#define POINTER_VALUE 0xcafe4all
|
||||
@ -74,6 +75,7 @@ struct test_subspec {
|
||||
struct expected_msgs jited;
|
||||
int retval;
|
||||
bool execute;
|
||||
__u64 caps;
|
||||
};
|
||||
|
||||
struct test_spec {
|
||||
@ -276,6 +278,37 @@ 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;
|
||||
if (!strncmp("CAP_", token, sizeof("CAP_") - 1)) {
|
||||
PRINT_FAIL("define %s constant in bpf_misc.h, failed to parse caps\n", token);
|
||||
return -EINVAL;
|
||||
}
|
||||
cap_flag = strtol(token, NULL, 10);
|
||||
if (!cap_flag || 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 {
|
||||
@ -541,6 +574,12 @@ static int parse_test_spec(struct test_loader *tester,
|
||||
jit_on_next_line = true;
|
||||
} else if (str_has_pfx(s, TEST_BTF_PATH)) {
|
||||
spec->btf_custom_path = s + sizeof(TEST_BTF_PATH) - 1;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -917,6 +956,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 */
|
||||
|
Loading…
Reference in New Issue
Block a user