// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause #include #include #include #include #include "bpf_misc.h" extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym; extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym; struct bpf_testmod_oob { __u8 data; union { __u8 redzone_1; __u16 redzone_2; __u32 redzone_4; __u64 redzone_8; }; }; extern struct bpf_testmod_oob *bpf_testmod_oob_alloc(void) __ksym; extern void bpf_testmod_oob_free(struct bpf_testmod_oob *oob) __ksym; int access_size; struct kasan_test_val { __u8 data_1; __u16 data_2; __u32 data_4; __u64 data_8; }; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 1); __type(key, __u32); __type(value, struct kasan_test_val); } test_map SEC(".maps"); /* * ST instructions are only emitted if the BPF cpu supports it (eg cpuv4), * they are otherwise turned into MOV + STX, so compile and exercise ST * only if supported. */ #ifdef __BPF_FEATURE_ST SEC("tcx/ingress") int st_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: val.data_1 = 0xAA; break; case 2: val.data_2 = 0xAA; break; case 4: val.data_4 = 0xAA; break; case 8: val.data_8 = 0xAA; break; } bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int st_not_on_stack(struct __sk_buff *skb) { struct kasan_test_val *val; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: val->data_1 = 0xAA; break; case 2: val->data_2 = 0xAA; break; case 4: val->data_4 = 0xAA; break; case 8: val->data_8 = 0xAA; break; } bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val)); return 0; } bool skip_st_tests SEC(".data") = 0; #else bool skip_st_tests SEC(".data") = 1; #endif SEC("tcx/ingress") int stx_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val)); /* * Unlike the st() programs above, the stored value comes from a * runtime source (skb->len), so it cannot be constant-folded and * clang always emits a genuine BPF_STX (register store) regardless * of the target cpu version. */ switch (access_size) { case 1: val.data_1 = (__u8)skb->len; break; case 2: val.data_2 = (__u16)skb->len; break; case 4: val.data_4 = (__u32)skb->len; break; case 8: val.data_8 = (__u64)skb->len; break; } bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int stx_not_on_stack(struct __sk_buff *skb) { struct kasan_test_val *val; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: val->data_1 = (__u8)skb->len; break; case 2: val->data_2 = (__u16)skb->len; break; case 4: val->data_4 = (__u32)skb->len; break; case 8: val->data_8 = (__u64)skb->len; break; } bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int ldx_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: __sink(val.data_1); break; case 2: __sink(val.data_2); break; case 4: __sink(val.data_4); break; case 8: __sink(val.data_8); break; } bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int ldx_not_on_stack(struct __sk_buff *skb) { struct kasan_test_val *val; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: __sink(val->data_1); break; case 2: __sink(val->data_2); break; case 4: __sink(val->data_4); break; case 8: __sink(val->data_8); break; } bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int ldx_patched_not_on_stack(struct __sk_buff *skb) { struct kasan_test_val *val; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val)); __sink(val->data_4); bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int ldx_patched_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val)); __sink(val.data_4); bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int simple_atomic_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val)); switch (access_size) { case 4: __sync_fetch_and_add(&val.data_4, 4); break; case 8: __sync_fetch_and_add(&val.data_8, 8); break; } bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int simple_atomic_not_on_stack(struct __sk_buff *skb) { struct kasan_test_val *val; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val)); switch (access_size) { case 4: __sync_fetch_and_add(&val->data_4, 4); break; case 8: __sync_fetch_and_add(&val->data_8, 8); break; } bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int simple_atomic_fetch_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val)); __sync_fetch_and_or(&val.data_8, 8); bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int simple_atomic_fetch_not_on_stack(struct __sk_buff *skb) { struct kasan_test_val *val; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val)); __sync_fetch_and_or(&val->data_8, 8); bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val)); return 0; } #ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL bool skip_load_acq_store_rel_tests SEC(".data") = 0; SEC("tcx/ingress") int load_acquire_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: __atomic_load_n(&val.data_1, __ATOMIC_ACQUIRE); break; case 2: __atomic_load_n(&val.data_2, __ATOMIC_ACQUIRE); break; case 4: __atomic_load_n(&val.data_4, __ATOMIC_ACQUIRE); break; case 8: __atomic_load_n(&val.data_8, __ATOMIC_ACQUIRE); break; } bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int load_acquire_not_on_stack(struct __sk_buff *skb) { struct kasan_test_val *val; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: __atomic_load_n(&val->data_1, __ATOMIC_ACQUIRE); break; case 2: __atomic_load_n(&val->data_2, __ATOMIC_ACQUIRE); break; case 4: __atomic_load_n(&val->data_4, __ATOMIC_ACQUIRE); break; case 8: __atomic_load_n(&val->data_8, __ATOMIC_ACQUIRE); break; } bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int store_release_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: __atomic_store_n(&val.data_1, 0xAA, __ATOMIC_RELEASE); break; case 2: __atomic_store_n(&val.data_2, 0xBBBB, __ATOMIC_RELEASE); break; case 4: __atomic_store_n(&val.data_4, 0xCCCCCCCC, __ATOMIC_RELEASE); break; case 8: __atomic_store_n(&val.data_8, 0xDDDDDDDDDDDDDDDD, __ATOMIC_RELEASE); break; } bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val)); return 0; } SEC("tcx/ingress") int store_release_not_on_stack(struct __sk_buff *skb) { struct kasan_test_val *val; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val)); switch (access_size) { case 1: __atomic_store_n(&val->data_1, 0xAA, __ATOMIC_RELEASE); break; case 2: __atomic_store_n(&val->data_2, 0xBBBB, __ATOMIC_RELEASE); break; case 4: __atomic_store_n(&val->data_4, 0xCCCCCCCC, __ATOMIC_RELEASE); break; case 8: __atomic_store_n(&val->data_8, 0xDDDDDDDDDDDDDDDD, __ATOMIC_RELEASE); break; } bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val)); return 0; } #else bool skip_load_acq_store_rel_tests SEC(".data") = 1; #endif SEC("tcx/ingress") int verifier_paths_stack_and_non_stack(struct __sk_buff *skb) { struct kasan_test_val stack_val = {}; struct kasan_test_val *val; void *ptr; __u32 key = 0; val = bpf_map_lookup_elem(&test_map, &key); if (!val) return 0; if (access_size) ptr = val; else ptr = &stack_val; bpf_kfunc_kasan_poison(val, sizeof(*val)); *(__u8 *)ptr = 0xAA; bpf_kfunc_kasan_unpoison(val, sizeof(*val)); return 0; } SEC("tcx/ingress") int ldx_oob(struct __sk_buff *skb) { struct bpf_testmod_oob *val; struct kasan_test_val volatile tmp; val = bpf_testmod_oob_alloc(); if (!val) return 0; switch (access_size) { case 1: tmp.data_1 = (__u8)val->redzone_1; break; case 2: tmp.data_2 = (__u16)val->redzone_2; break; case 4: tmp.data_4 = (__u32)val->redzone_4; break; case 8: tmp.data_8 = (__u64)val->redzone_8; break; } bpf_testmod_oob_free(val); return tmp.data_1; } SEC("tcx/ingress") int ldx_self_alias_on_stack(struct __sk_buff *skb) { struct kasan_test_val val; __u64 addr; bpf_kfunc_kasan_poison(&val, sizeof(val)); /* * Check that a stack access with dst_reg == src_reg is correctly * flagged as stack-only access */ addr = (__u64)&val; asm volatile( "r1 = %0\n" "r1 = *(u64 *)(r1 + 0)\n" : : "r"(addr) : "r1", "memory"); bpf_kfunc_kasan_unpoison(&val, sizeof(val)); return 0; } char LICENSE[] SEC("license") = "GPL";