Files
linux-stable-mirror/tools/testing/selftests/bpf/progs/map_kptr.c
T
Kumar Kartikeya DwivediandAlexei Starovoitov 2e7c6cb4d8 selftests/bpf: Exercise kptr map update lifetime
Add focused map_kptr coverage for BPF-side map updates that touch values
containing referenced kptrs.

The new syscall programs stash the testmod refcounted object in an array
map, a preallocated hash map, and a no-prealloc hash map, then update the
same map from BPF. The refcount must remain elevated after the update,
while the userspace runner destroys the skeleton and reuses the existing
refcount wait to confirm map teardown releases the kptr.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20260609202548.3571690-5-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2026-06-09 21:23:11 -07:00

642 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include "../test_kmods/bpf_testmod_kfunc.h"
struct map_value {
struct prog_test_ref_kfunc __kptr_untrusted *unref_ptr;
struct prog_test_ref_kfunc __kptr *ref_ptr;
};
struct array_map {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, int);
__type(value, struct map_value);
__uint(max_entries, 1);
} array_map SEC(".maps");
struct pcpu_array_map {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, int);
__type(value, struct map_value);
__uint(max_entries, 1);
} pcpu_array_map SEC(".maps");
struct hash_map {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, int);
__type(value, struct map_value);
__uint(max_entries, 1);
} hash_map SEC(".maps");
struct pcpu_hash_map {
__uint(type, BPF_MAP_TYPE_PERCPU_HASH);
__type(key, int);
__type(value, struct map_value);
__uint(max_entries, 1);
} pcpu_hash_map SEC(".maps");
struct hash_malloc_map {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, int);
__type(value, struct map_value);
__uint(max_entries, 1);
__uint(map_flags, BPF_F_NO_PREALLOC);
} hash_malloc_map SEC(".maps");
struct pcpu_hash_malloc_map {
__uint(type, BPF_MAP_TYPE_PERCPU_HASH);
__type(key, int);
__type(value, struct map_value);
__uint(max_entries, 1);
__uint(map_flags, BPF_F_NO_PREALLOC);
} pcpu_hash_malloc_map SEC(".maps");
struct lru_hash_map {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__type(key, int);
__type(value, struct map_value);
__uint(max_entries, 1);
} lru_hash_map SEC(".maps");
struct lru_pcpu_hash_map {
__uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH);
__type(key, int);
__type(value, struct map_value);
__uint(max_entries, 1);
} lru_pcpu_hash_map SEC(".maps");
struct cgrp_ls_map {
__uint(type, BPF_MAP_TYPE_CGRP_STORAGE);
__uint(map_flags, BPF_F_NO_PREALLOC);
__type(key, int);
__type(value, struct map_value);
} cgrp_ls_map SEC(".maps");
struct task_ls_map {
__uint(type, BPF_MAP_TYPE_TASK_STORAGE);
__uint(map_flags, BPF_F_NO_PREALLOC);
__type(key, int);
__type(value, struct map_value);
} task_ls_map SEC(".maps");
struct inode_ls_map {
__uint(type, BPF_MAP_TYPE_INODE_STORAGE);
__uint(map_flags, BPF_F_NO_PREALLOC);
__type(key, int);
__type(value, struct map_value);
} inode_ls_map SEC(".maps");
struct sk_ls_map {
__uint(type, BPF_MAP_TYPE_SK_STORAGE);
__uint(map_flags, BPF_F_NO_PREALLOC);
__type(key, int);
__type(value, struct map_value);
} sk_ls_map SEC(".maps");
#define DEFINE_MAP_OF_MAP(map_type, inner_map_type, name) \
struct { \
__uint(type, map_type); \
__uint(max_entries, 1); \
__uint(key_size, sizeof(int)); \
__uint(value_size, sizeof(int)); \
__array(values, struct inner_map_type); \
} name SEC(".maps") = { \
.values = { [0] = &inner_map_type }, \
}
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_ARRAY_OF_MAPS, array_map, array_of_array_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_ARRAY_OF_MAPS, hash_map, array_of_hash_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_ARRAY_OF_MAPS, hash_malloc_map, array_of_hash_malloc_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_ARRAY_OF_MAPS, lru_hash_map, array_of_lru_hash_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_ARRAY_OF_MAPS, pcpu_array_map, array_of_pcpu_array_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_ARRAY_OF_MAPS, pcpu_hash_map, array_of_pcpu_hash_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, array_map, hash_of_array_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, hash_map, hash_of_hash_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, hash_malloc_map, hash_of_hash_malloc_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, lru_hash_map, hash_of_lru_hash_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, pcpu_array_map, hash_of_pcpu_array_maps);
DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, pcpu_hash_map, hash_of_pcpu_hash_maps);
#define WRITE_ONCE(x, val) ((*(volatile typeof(x) *) &(x)) = (val))
static void test_kptr_unref(struct map_value *v)
{
struct prog_test_ref_kfunc *p;
p = v->unref_ptr;
/* store untrusted_ptr_or_null_ */
WRITE_ONCE(v->unref_ptr, p);
if (!p)
return;
if (p->a + p->b > 100)
return;
/* store untrusted_ptr_ */
WRITE_ONCE(v->unref_ptr, p);
/* store NULL */
WRITE_ONCE(v->unref_ptr, NULL);
}
static void test_kptr_ref(struct map_value *v)
{
struct prog_test_ref_kfunc *p;
p = v->ref_ptr;
/* store ptr_or_null_ */
WRITE_ONCE(v->unref_ptr, p);
if (!p)
return;
/*
* p is rcu_ptr_prog_test_ref_kfunc,
* because bpf prog is non-sleepable and runs in RCU CS.
* p can be passed to kfunc that requires KF_RCU.
*/
bpf_kfunc_call_test_ref(p);
if (p->a + p->b > 100)
return;
/* store NULL */
p = bpf_kptr_xchg(&v->ref_ptr, NULL);
if (!p)
return;
/*
* p is trusted_ptr_prog_test_ref_kfunc.
* p can be passed to kfunc that requires KF_RCU.
*/
bpf_kfunc_call_test_ref(p);
if (p->a + p->b > 100) {
bpf_kfunc_call_test_release(p);
return;
}
/* store ptr_ */
WRITE_ONCE(v->unref_ptr, p);
bpf_kfunc_call_test_release(p);
p = bpf_kfunc_call_test_acquire(&(unsigned long){0});
if (!p)
return;
/* store ptr_ */
p = bpf_kptr_xchg(&v->ref_ptr, p);
if (!p)
return;
if (p->a + p->b > 100) {
bpf_kfunc_call_test_release(p);
return;
}
bpf_kfunc_call_test_release(p);
}
static void test_kptr(struct map_value *v)
{
test_kptr_unref(v);
test_kptr_ref(v);
}
SEC("tc")
int test_map_kptr(struct __sk_buff *ctx)
{
struct map_value *v;
int key = 0;
#define TEST(map) \
v = bpf_map_lookup_elem(&map, &key); \
if (!v) \
return 0; \
test_kptr(v)
TEST(array_map);
TEST(hash_map);
TEST(hash_malloc_map);
TEST(lru_hash_map);
TEST(pcpu_array_map);
TEST(pcpu_hash_map);
#undef TEST
return 0;
}
SEC("tp_btf/cgroup_mkdir")
int BPF_PROG(test_cgrp_map_kptr, struct cgroup *cgrp, const char *path)
{
struct map_value *v;
v = bpf_cgrp_storage_get(&cgrp_ls_map, cgrp, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE);
if (v)
test_kptr(v);
return 0;
}
SEC("lsm/inode_unlink")
int BPF_PROG(test_task_map_kptr, struct inode *inode, struct dentry *victim)
{
struct task_struct *task;
struct map_value *v;
task = bpf_get_current_task_btf();
if (!task)
return 0;
v = bpf_task_storage_get(&task_ls_map, task, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE);
if (v)
test_kptr(v);
return 0;
}
SEC("lsm/inode_unlink")
int BPF_PROG(test_inode_map_kptr, struct inode *inode, struct dentry *victim)
{
struct map_value *v;
v = bpf_inode_storage_get(&inode_ls_map, inode, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE);
if (v)
test_kptr(v);
return 0;
}
SEC("tc")
int test_sk_map_kptr(struct __sk_buff *ctx)
{
struct map_value *v;
struct bpf_sock *sk;
sk = ctx->sk;
if (!sk)
return 0;
v = bpf_sk_storage_get(&sk_ls_map, sk, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE);
if (v)
test_kptr(v);
return 0;
}
SEC("tc")
int test_map_in_map_kptr(struct __sk_buff *ctx)
{
struct map_value *v;
int key = 0;
void *map;
#define TEST(map_in_map) \
map = bpf_map_lookup_elem(&map_in_map, &key); \
if (!map) \
return 0; \
v = bpf_map_lookup_elem(map, &key); \
if (!v) \
return 0; \
test_kptr(v)
TEST(array_of_array_maps);
TEST(array_of_hash_maps);
TEST(array_of_hash_malloc_maps);
TEST(array_of_lru_hash_maps);
TEST(array_of_pcpu_array_maps);
TEST(array_of_pcpu_hash_maps);
TEST(hash_of_array_maps);
TEST(hash_of_hash_maps);
TEST(hash_of_hash_malloc_maps);
TEST(hash_of_lru_hash_maps);
TEST(hash_of_pcpu_array_maps);
TEST(hash_of_pcpu_hash_maps);
#undef TEST
return 0;
}
int ref = 1;
static __always_inline
int test_map_kptr_ref_pre(struct map_value *v)
{
struct prog_test_ref_kfunc *p, *p_st;
unsigned long arg = 0;
int ret;
p = bpf_kfunc_call_test_acquire(&arg);
if (!p)
return 1;
ref++;
p_st = p->next;
if (p_st->cnt.refs.counter != ref) {
ret = 2;
goto end;
}
p = bpf_kptr_xchg(&v->ref_ptr, p);
if (p) {
ret = 3;
goto end;
}
if (p_st->cnt.refs.counter != ref)
return 4;
p = bpf_kptr_xchg(&v->ref_ptr, NULL);
if (!p)
return 5;
bpf_kfunc_call_test_release(p);
ref--;
if (p_st->cnt.refs.counter != ref)
return 6;
p = bpf_kfunc_call_test_acquire(&arg);
if (!p)
return 7;
ref++;
p = bpf_kptr_xchg(&v->ref_ptr, p);
if (p) {
ret = 8;
goto end;
}
if (p_st->cnt.refs.counter != ref)
return 9;
/* Leave in map */
return 0;
end:
ref--;
bpf_kfunc_call_test_release(p);
return ret;
}
static __always_inline
int test_map_kptr_ref_post(struct map_value *v)
{
struct prog_test_ref_kfunc *p, *p_st;
p_st = v->ref_ptr;
if (!p_st || p_st->cnt.refs.counter != ref)
return 1;
p = bpf_kptr_xchg(&v->ref_ptr, NULL);
if (!p)
return 2;
if (p_st->cnt.refs.counter != ref) {
bpf_kfunc_call_test_release(p);
return 3;
}
p = bpf_kptr_xchg(&v->ref_ptr, p);
if (p) {
bpf_kfunc_call_test_release(p);
return 4;
}
if (p_st->cnt.refs.counter != ref)
return 5;
return 0;
}
#define TEST(map) \
v = bpf_map_lookup_elem(&map, &key); \
if (!v) \
return -1; \
ret = test_map_kptr_ref_pre(v); \
if (ret) \
return ret;
#define TEST_PCPU(map) \
v = bpf_map_lookup_percpu_elem(&map, &key, 0); \
if (!v) \
return -1; \
ret = test_map_kptr_ref_pre(v); \
if (ret) \
return ret;
SEC("tc")
int test_map_kptr_ref1(struct __sk_buff *ctx)
{
struct map_value *v, val = {};
int key = 0, ret;
bpf_map_update_elem(&hash_map, &key, &val, 0);
bpf_map_update_elem(&hash_malloc_map, &key, &val, 0);
bpf_map_update_elem(&lru_hash_map, &key, &val, 0);
bpf_map_update_elem(&pcpu_hash_map, &key, &val, 0);
bpf_map_update_elem(&pcpu_hash_malloc_map, &key, &val, 0);
bpf_map_update_elem(&lru_pcpu_hash_map, &key, &val, 0);
TEST(array_map);
TEST(hash_map);
TEST(hash_malloc_map);
TEST(lru_hash_map);
TEST_PCPU(pcpu_array_map);
TEST_PCPU(pcpu_hash_map);
TEST_PCPU(pcpu_hash_malloc_map);
TEST_PCPU(lru_pcpu_hash_map);
return 0;
}
#undef TEST
#undef TEST_PCPU
#define TEST(map) \
v = bpf_map_lookup_elem(&map, &key); \
if (!v) \
return -1; \
ret = test_map_kptr_ref_post(v); \
if (ret) \
return ret;
#define TEST_PCPU(map) \
v = bpf_map_lookup_percpu_elem(&map, &key, 0); \
if (!v) \
return -1; \
ret = test_map_kptr_ref_post(v); \
if (ret) \
return ret;
SEC("tc")
int test_map_kptr_ref2(struct __sk_buff *ctx)
{
struct map_value *v;
int key = 0, ret;
TEST(array_map);
TEST(hash_map);
TEST(hash_malloc_map);
TEST(lru_hash_map);
TEST_PCPU(pcpu_array_map);
TEST_PCPU(pcpu_hash_map);
TEST_PCPU(pcpu_hash_malloc_map);
TEST_PCPU(lru_pcpu_hash_map);
return 0;
}
#undef TEST
#undef TEST_PCPU
SEC("tc")
int test_map_kptr_ref3(struct __sk_buff *ctx)
{
struct prog_test_ref_kfunc *p;
unsigned long sp = 0;
p = bpf_kfunc_call_test_acquire(&sp);
if (!p)
return 1;
ref++;
if (p->cnt.refs.counter != ref) {
bpf_kfunc_call_test_release(p);
return 2;
}
bpf_kfunc_call_test_release(p);
ref--;
return 0;
}
int num_of_refs;
static __always_inline int read_ref_count(void)
{
struct prog_test_ref_kfunc *p;
unsigned long arg = 0;
p = bpf_kfunc_call_test_acquire(&arg);
if (!p)
return 1;
num_of_refs = p->cnt.refs.counter;
bpf_kfunc_call_test_release(p);
return 0;
}
SEC("syscall")
int count_ref(void *ctx)
{
return read_ref_count();
}
static __always_inline int stash_ref_ptr(struct map_value *v)
{
struct prog_test_ref_kfunc *p, *old;
unsigned long arg = 0;
p = bpf_kfunc_call_test_acquire(&arg);
if (!p)
return 1;
old = bpf_kptr_xchg(&v->ref_ptr, p);
if (old) {
bpf_kfunc_call_test_release(old);
old = bpf_kptr_xchg(&v->ref_ptr, NULL);
if (old)
bpf_kfunc_call_test_release(old);
return 2;
}
return 0;
}
static __always_inline int check_refs(int expected)
{
int ret;
ret = read_ref_count();
if (ret)
return ret;
return num_of_refs == expected ? 0 : 3;
}
SEC("syscall")
int test_array_map_update_kptr(void *ctx)
{
struct map_value init = {}, *v;
int key = 0, ret;
v = bpf_map_lookup_elem(&array_map, &key);
if (!v)
return 1;
ret = stash_ref_ptr(v);
if (ret)
return ret;
ret = check_refs(3);
if (ret)
return ret;
ret = bpf_map_update_elem(&array_map, &key, &init, BPF_EXIST);
if (ret)
return 4;
return check_refs(3);
}
#define DEFINE_HASH_UPDATE_KPTR_TEST(name, map) \
SEC("syscall") \
int name(void *ctx) \
{ \
struct map_value init = {}, *v; \
int key = 0, ret; \
\
ret = bpf_map_update_elem(&map, &key, &init, BPF_NOEXIST); \
if (ret) \
return 1; \
v = bpf_map_lookup_elem(&map, &key); \
if (!v) \
return 2; \
ret = stash_ref_ptr(v); \
if (ret) \
return ret; \
ret = check_refs(3); \
if (ret) \
return ret; \
ret = bpf_map_update_elem(&map, &key, &init, BPF_EXIST); \
if (ret) \
return 4; \
return check_refs(3); \
}
DEFINE_HASH_UPDATE_KPTR_TEST(test_hash_map_update_kptr, hash_map)
DEFINE_HASH_UPDATE_KPTR_TEST(test_hash_malloc_map_update_kptr, hash_malloc_map)
SEC("syscall")
int test_ls_map_kptr_ref1(void *ctx)
{
struct task_struct *current;
struct map_value *v;
current = bpf_get_current_task_btf();
if (!current)
return 100;
v = bpf_task_storage_get(&task_ls_map, current, NULL, 0);
if (v)
return 150;
v = bpf_task_storage_get(&task_ls_map, current, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE);
if (!v)
return 200;
return test_map_kptr_ref_pre(v);
}
SEC("syscall")
int test_ls_map_kptr_ref2(void *ctx)
{
struct task_struct *current;
struct map_value *v;
current = bpf_get_current_task_btf();
if (!current)
return 100;
v = bpf_task_storage_get(&task_ls_map, current, NULL, 0);
if (!v)
return 200;
return test_map_kptr_ref_post(v);
}
SEC("syscall")
int test_ls_map_kptr_ref_del(void *ctx)
{
struct task_struct *current;
struct map_value *v;
current = bpf_get_current_task_btf();
if (!current)
return 100;
v = bpf_task_storage_get(&task_ls_map, current, NULL, 0);
if (!v)
return 200;
if (!v->ref_ptr)
return 300;
return bpf_task_storage_delete(&task_ls_map, current);
}
char _license[] SEC("license") = "GPL";