blob: fe8e0dd228a09a0f6abad1d802141d45d4c213c3 [file] [edit]
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
#include <stdbool.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#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";