Merge branch 'bpf-extend-the-bpf_list-family-of-apis'

Kaitao Cheng says:

====================
bpf: Extend the bpf_list family of APIs

In BPF, a list can only be used to implement a stack structure.
Due to an incomplete API set, only FIFO or LIFO operations are
supported. The patches enhance the BPF list API, making it more
list-like.

Five new kfuncs have been added:
bpf_list_del: remove a node from the list
bpf_list_add_impl: insert a node after a given list node
bpf_list_is_first: check if a node is the first in the list
bpf_list_is_last: check if a node is the last in the list
bpf_list_empty: check if the list is empty

And add test cases for the aforementioned kfuncs.

Changes in v11:
- Move [PATCH v10 7/8] earlier (Eduard Zingerman)
- Fix the synchronization issue in [PATCH v10 2/8] (Eduard Zingerman,
  Alexei Starovoitov)

Changes in v10:
- Remove the table-driven approach (Ihor Solodrai)
- Use the __nonown_allowed suffix for bpf_list_del/front/back
- Add test cases for __nonown_allowed

Changes in v9:
- Expand table-driven approach coverage (Emil Tsalapatis)
- Clear list node owner and unlink before drop (Emil Tsalapatis)
- Remove warnings caused by WARN_ON_ONCE() (Emil Tsalapatis)
- Introduce the __nonown_allowed suffix (Alexei Starovoitov)

Changes in v8:
- Use [patch v7 5/5] as the start of the patch series (Leon Hwang)
- Introduce double pointer prev_ptr in __bpf_list_del
  (Kumar Kartikeya Dwivedi)
- Extract refactored __bpf_list_del/add into separate patches (Leon Hwang)
- Allow bpf_list_front/back result as the prev argument of bpf_list_add
- Split test cases (Leon Hwang)

Changes in v7:
- Replace bpf_list_node_is_edge with bpf_list_is_first/is_last
- Reimplement __bpf_list_del and __bpf_list_add (Kumar Kartikeya Dwivedi)
- Simplify test cases (Mykyta Yatsenko)

Changes in v6:
- Merge [patch v5 (2,4,6)/6] into [patch v6 4/5] (Leon Hwang)
- If list_head was 0-initialized, init it
- refactor kfunc checks to table-driven approach (Leon Hwang)

Changes in v5:
- Fix bpf_obj leak on bpf_list_add_impl error

Changes in v4:
- [patch v3 1/6] Revert to version v1 (Alexei Starovoitov)
- Change the parameters of bpf_list_add_impl to (head, new, prev, ...)

Changes in v3:
- Add a new lock_rec member to struct bpf_reference_state for lock
  holding detection.
- Add test cases to verify that the verifier correctly restricts calls
  to bpf_list_del when the spin_lock is not held.

Changes in v2:
- Remove the head parameter from bpf_list_del (Alexei Starovoitov)
- Add bpf_list_add/is_first/is_last/empty to API and test cases
  (Alexei Starovoitov)

Link to v10:
https://lore.kernel.org/all/20260512055919.95716-1-kaitao.cheng@linux.dev/

Link to v9:
https://lore.kernel.org/all/20260329140506.9595-1-pilgrimtao@gmail.com/

Link to v8:
https://lore.kernel.org/all/20260316112843.78657-1-pilgrimtao@gmail.com/

Link to v7:
https://lore.kernel.org/all/20260308134614.29711-1-pilgrimtao@gmail.com/

Link to v6:
https://lore.kernel.org/all/20260304143459.78059-1-pilgrimtao@gmail.com/

Link to v5:
https://lore.kernel.org/all/20260304031606.43884-1-pilgrimtao@gmail.com/

Link to v4:
https://lore.kernel.org/all/20260303135219.33726-1-pilgrimtao@gmail.com/

Link to v3:
https://lore.kernel.org/all/20260302124028.82420-1-pilgrimtao@gmail.com/

Link to v2:
https://lore.kernel.org/all/20260225092651.94689-1-pilgrimtao@gmail.com/

Link to v1:
https://lore.kernel.org/all/20260209025250.55750-1-pilgrimtao@gmail.com/
====================

Link: https://patch.msgid.link/20260521032306.97118-1-kaitao.cheng@linux.dev
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-05-21 02:47:46 -07:00
commit b1fcdf9aa9
4 changed files with 601 additions and 33 deletions

View File

@ -207,8 +207,26 @@ Here, the buffer may be NULL. If the buffer is not NULL, it must be at least
buffer__szk bytes in size. The kfunc is responsible for checking if the buffer
is NULL before using it.
2.3.5 __str Annotation
----------------------------
2.3.5 __nonown_allowed Annotation
---------------------------------
This annotation is used to indicate that the parameter may be a non-owning reference.
An example is given below::
__bpf_kfunc int bpf_list_add(..., struct bpf_list_node
*prev__nonown_allowed, ...)
{
...
}
For the ``prev__nonown_allowed`` parameter (resolved as ``KF_ARG_PTR_TO_LIST_NODE``),
suffix ``__nonown_allowed`` retains the usual owning-pointer rules and also
permits a non-owning reference with no ref_obj_id (e.g. the return value of
bpf_list_front() / bpf_list_back()).
2.3.6 __str Annotation
----------------------
This annotation is used to indicate that the argument is a constant string.
An example is given below::

View File

@ -2247,10 +2247,11 @@ EXPORT_SYMBOL_GPL(bpf_base_func_proto);
void bpf_list_head_free(const struct btf_field *field, void *list_head,
struct bpf_spin_lock *spin_lock)
{
struct list_head *head = list_head, *orig_head = list_head;
struct list_head *head = list_head, drain, *pos, *n;
BUILD_BUG_ON(sizeof(struct list_head) > sizeof(struct bpf_list_head));
BUILD_BUG_ON(__alignof__(struct list_head) > __alignof__(struct bpf_list_head));
INIT_LIST_HEAD(&drain);
/* Do the actual list draining outside the lock to not hold the lock for
* too long, and also prevent deadlocks if tracing programs end up
@ -2261,20 +2262,30 @@ void bpf_list_head_free(const struct btf_field *field, void *list_head,
__bpf_spin_lock_irqsave(spin_lock);
if (!head->next || list_empty(head))
goto unlock;
head = head->next;
list_for_each_safe(pos, n, head) {
struct bpf_list_node_kern *node;
node = container_of(pos, struct bpf_list_node_kern, list_head);
WRITE_ONCE(node->owner, BPF_PTR_POISON);
list_move_tail(pos, &drain);
}
unlock:
INIT_LIST_HEAD(orig_head);
INIT_LIST_HEAD(head);
__bpf_spin_unlock_irqrestore(spin_lock);
while (head != orig_head) {
void *obj = head;
while (!list_empty(&drain)) {
struct bpf_list_node_kern *node;
obj -= field->graph_root.node_offset;
head = head->next;
pos = drain.next;
node = container_of(pos, struct bpf_list_node_kern, list_head);
list_del_init(pos);
/* Ensure __bpf_list_add() sees the node as unlinked. */
smp_store_release(&node->owner, NULL);
/* The contained type can also have resources, including a
* bpf_list_head which needs to be freed.
*/
__bpf_obj_drop_impl(obj, field->graph_root.value_rec, false);
__bpf_obj_drop_impl((char *)pos - field->graph_root.node_offset,
field->graph_root.value_rec, false);
}
}
@ -2467,9 +2478,11 @@ __bpf_kfunc void *bpf_refcount_acquire_impl(void *p__refcounted_kptr, void *meta
static int __bpf_list_add(struct bpf_list_node_kern *node,
struct bpf_list_head *head,
bool tail, struct btf_record *rec, u64 off)
struct list_head **prev_ptr,
struct btf_record *rec, u64 off)
{
struct list_head *n = &node->list_head, *h = (void *)head;
struct list_head *prev;
/* If list_head was 0-initialized by map, bpf_obj_init_field wasn't
* called on its fields, so init here
@ -2477,19 +2490,31 @@ static int __bpf_list_add(struct bpf_list_node_kern *node,
if (unlikely(!h->next))
INIT_LIST_HEAD(h);
prev = *prev_ptr;
/* When prev is not the list head, it must be a node in this list. */
if (prev != h) {
struct bpf_list_node_kern *prev_kn =
container_of(prev, struct bpf_list_node_kern, list_head);
if (unlikely(READ_ONCE(prev_kn->owner) != head))
goto fail;
}
/* node->owner != NULL implies !list_empty(n), no need to separately
* check the latter
*/
if (cmpxchg(&node->owner, NULL, BPF_PTR_POISON)) {
/* Only called from BPF prog, no need to migrate_disable */
__bpf_obj_drop_impl((void *)n - off, rec, false);
return -EINVAL;
}
if (cmpxchg(&node->owner, NULL, BPF_PTR_POISON))
goto fail;
tail ? list_add_tail(n, h) : list_add(n, h);
list_add(n, prev);
WRITE_ONCE(node->owner, head);
return 0;
fail:
/* Only called from BPF prog, no need to migrate_disable */
__bpf_obj_drop_impl((void *)n - off, rec, false);
return -EINVAL;
}
/**
@ -2510,8 +2535,9 @@ __bpf_kfunc int bpf_list_push_front(struct bpf_list_head *head,
u64 off)
{
struct bpf_list_node_kern *n = (void *)node;
struct list_head *h = (void *)head;
return __bpf_list_add(n, head, false, meta ? meta->record : NULL, off);
return __bpf_list_add(n, head, &h, meta ? meta->record : NULL, off);
}
__bpf_kfunc int bpf_list_push_front_impl(struct bpf_list_head *head,
@ -2539,8 +2565,9 @@ __bpf_kfunc int bpf_list_push_back(struct bpf_list_head *head,
u64 off)
{
struct bpf_list_node_kern *n = (void *)node;
struct list_head *h = (void *)head;
return __bpf_list_add(n, head, true, meta ? meta->record : NULL, off);
return __bpf_list_add(n, head, &h->prev, meta ? meta->record : NULL, off);
}
__bpf_kfunc int bpf_list_push_back_impl(struct bpf_list_head *head,
@ -2550,37 +2577,63 @@ __bpf_kfunc int bpf_list_push_back_impl(struct bpf_list_head *head,
return bpf_list_push_back(head, node, meta__ign, off);
}
static struct bpf_list_node *__bpf_list_del(struct bpf_list_head *head, bool tail)
__bpf_kfunc int bpf_list_add(struct bpf_list_head *head, struct bpf_list_node *new,
struct bpf_list_node *prev__nonown_allowed,
struct btf_struct_meta *meta, u64 off)
{
struct list_head *n, *h = (void *)head;
struct bpf_list_node_kern *n = (void *)new, *p = (void *)prev__nonown_allowed;
struct list_head *prev_ptr = &p->list_head;
return __bpf_list_add(n, head, &prev_ptr, meta ? meta->record : NULL, off);
}
static struct bpf_list_node *__bpf_list_del(struct bpf_list_head *head,
struct list_head *n)
{
struct list_head *h = (void *)head;
struct bpf_list_node_kern *node;
/* If list_head was 0-initialized by map, bpf_obj_init_field wasn't
* called on its fields, so init here
*/
if (unlikely(!h->next))
if (unlikely(!h->next)) {
INIT_LIST_HEAD(h);
return NULL;
}
if (list_empty(h))
return NULL;
n = tail ? h->prev : h->next;
node = container_of(n, struct bpf_list_node_kern, list_head);
if (WARN_ON_ONCE(READ_ONCE(node->owner) != head))
if (unlikely(READ_ONCE(node->owner) != head))
return NULL;
list_del_init(n);
WRITE_ONCE(node->owner, NULL);
/* Ensure __bpf_list_add() sees the node as unlinked. */
smp_store_release(&node->owner, NULL);
return (struct bpf_list_node *)n;
}
__bpf_kfunc struct bpf_list_node *bpf_list_pop_front(struct bpf_list_head *head)
{
return __bpf_list_del(head, false);
struct list_head *h = (void *)head;
return __bpf_list_del(head, h->next);
}
__bpf_kfunc struct bpf_list_node *bpf_list_pop_back(struct bpf_list_head *head)
{
return __bpf_list_del(head, true);
struct list_head *h = (void *)head;
return __bpf_list_del(head, h->prev);
}
__bpf_kfunc struct bpf_list_node *bpf_list_del(struct bpf_list_head *head,
struct bpf_list_node *node__nonown_allowed)
{
struct bpf_list_node_kern *kn = (void *)node__nonown_allowed;
/* verifier guarantees node is a list node rather than list head */
return __bpf_list_del(head, &kn->list_head);
}
__bpf_kfunc struct bpf_list_node *bpf_list_front(struct bpf_list_head *head)
@ -2603,6 +2656,43 @@ __bpf_kfunc struct bpf_list_node *bpf_list_back(struct bpf_list_head *head)
return (struct bpf_list_node *)h->prev;
}
__bpf_kfunc bool bpf_list_is_first(struct bpf_list_head *head,
struct bpf_list_node *node__nonown_allowed)
{
struct list_head *h = (struct list_head *)head;
struct bpf_list_node_kern *kn = (struct bpf_list_node_kern *)node__nonown_allowed;
if (READ_ONCE(kn->owner) != head)
return false;
return list_is_first(&kn->list_head, h);
}
__bpf_kfunc bool bpf_list_is_last(struct bpf_list_head *head,
struct bpf_list_node *node__nonown_allowed)
{
struct list_head *h = (struct list_head *)head;
struct bpf_list_node_kern *kn = (struct bpf_list_node_kern *)node__nonown_allowed;
if (READ_ONCE(kn->owner) != head)
return false;
return list_is_last(&kn->list_head, h);
}
__bpf_kfunc bool bpf_list_empty(struct bpf_list_head *head)
{
struct list_head *h = (struct list_head *)head;
/* If list_head was 0-initialized by map, bpf_obj_init_field wasn't
* called on its fields, so init here
*/
if (unlikely(!h->next))
INIT_LIST_HEAD(h);
return list_empty(h);
}
__bpf_kfunc struct bpf_rb_node *bpf_rbtree_remove(struct bpf_rb_root *root,
struct bpf_rb_node *node)
{
@ -4713,10 +4803,15 @@ BTF_ID_FLAGS(func, bpf_list_push_front, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_list_push_front_impl)
BTF_ID_FLAGS(func, bpf_list_push_back, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_list_push_back_impl)
BTF_ID_FLAGS(func, bpf_list_add, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_list_pop_front, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_list_pop_back, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_list_del, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_list_front, KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_list_back, KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_list_is_first)
BTF_ID_FLAGS(func, bpf_list_is_last)
BTF_ID_FLAGS(func, bpf_list_empty)
BTF_ID_FLAGS(func, bpf_task_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_task_release, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_rbtree_remove, KF_ACQUIRE | KF_RET_NULL)

View File

@ -10714,6 +10714,11 @@ static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param
return btf_param_match_suffix(btf, arg, "__nullable");
}
static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
{
return btf_param_match_suffix(btf, arg, "__nonown_allowed");
}
static bool is_kfunc_arg_const_str(const struct btf *btf, const struct btf_param *arg)
{
return btf_param_match_suffix(btf, arg, "__str");
@ -10954,10 +10959,15 @@ enum special_kfunc_type {
KF_bpf_list_push_front,
KF_bpf_list_push_back_impl,
KF_bpf_list_push_back,
KF_bpf_list_add,
KF_bpf_list_pop_front,
KF_bpf_list_pop_back,
KF_bpf_list_del,
KF_bpf_list_front,
KF_bpf_list_back,
KF_bpf_list_is_first,
KF_bpf_list_is_last,
KF_bpf_list_empty,
KF_bpf_cast_to_kern_ctx,
KF_bpf_rdonly_cast,
KF_bpf_rcu_read_lock,
@ -11022,10 +11032,15 @@ BTF_ID(func, bpf_list_push_front_impl)
BTF_ID(func, bpf_list_push_front)
BTF_ID(func, bpf_list_push_back_impl)
BTF_ID(func, bpf_list_push_back)
BTF_ID(func, bpf_list_add)
BTF_ID(func, bpf_list_pop_front)
BTF_ID(func, bpf_list_pop_back)
BTF_ID(func, bpf_list_del)
BTF_ID(func, bpf_list_front)
BTF_ID(func, bpf_list_back)
BTF_ID(func, bpf_list_is_first)
BTF_ID(func, bpf_list_is_last)
BTF_ID(func, bpf_list_empty)
BTF_ID(func, bpf_cast_to_kern_ctx)
BTF_ID(func, bpf_rdonly_cast)
BTF_ID(func, bpf_rcu_read_lock)
@ -11133,7 +11148,8 @@ static bool is_bpf_list_push_kfunc(u32 func_id)
return func_id == special_kfunc_list[KF_bpf_list_push_front] ||
func_id == special_kfunc_list[KF_bpf_list_push_front_impl] ||
func_id == special_kfunc_list[KF_bpf_list_push_back] ||
func_id == special_kfunc_list[KF_bpf_list_push_back_impl];
func_id == special_kfunc_list[KF_bpf_list_push_back_impl] ||
func_id == special_kfunc_list[KF_bpf_list_add];
}
static bool is_bpf_rbtree_add_kfunc(u32 func_id)
@ -11544,8 +11560,12 @@ static bool is_bpf_list_api_kfunc(u32 btf_id)
return is_bpf_list_push_kfunc(btf_id) ||
btf_id == special_kfunc_list[KF_bpf_list_pop_front] ||
btf_id == special_kfunc_list[KF_bpf_list_pop_back] ||
btf_id == special_kfunc_list[KF_bpf_list_del] ||
btf_id == special_kfunc_list[KF_bpf_list_front] ||
btf_id == special_kfunc_list[KF_bpf_list_back];
btf_id == special_kfunc_list[KF_bpf_list_back] ||
btf_id == special_kfunc_list[KF_bpf_list_is_first] ||
btf_id == special_kfunc_list[KF_bpf_list_is_last] ||
btf_id == special_kfunc_list[KF_bpf_list_empty];
}
static bool is_bpf_rbtree_api_kfunc(u32 btf_id)
@ -11666,7 +11686,10 @@ static bool check_kfunc_is_graph_node_api(struct bpf_verifier_env *env,
switch (node_field_type) {
case BPF_LIST_NODE:
ret = is_bpf_list_push_kfunc(kfunc_btf_id);
ret = is_bpf_list_push_kfunc(kfunc_btf_id) ||
kfunc_btf_id == special_kfunc_list[KF_bpf_list_del] ||
kfunc_btf_id == special_kfunc_list[KF_bpf_list_is_first] ||
kfunc_btf_id == special_kfunc_list[KF_bpf_list_is_last];
break;
case BPF_RB_NODE:
ret = (is_bpf_rbtree_add_kfunc(kfunc_btf_id) ||
@ -12244,6 +12267,13 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
return ret;
break;
case KF_ARG_PTR_TO_LIST_NODE:
if (is_kfunc_arg_nonown_allowed(btf, &args[i]) &&
type_is_non_owning_ref(reg->type) && !reg->ref_obj_id) {
/* Allow bpf_list_front/back return value for
* __nonown_allowed list-node arguments.
*/
goto check_ok;
}
if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
verbose(env, "%s expected pointer to allocated object\n",
reg_arg_name(env, argno));
@ -12253,6 +12283,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
verbose(env, "allocated object must be referenced\n");
return -EINVAL;
}
check_ok:
ret = process_kf_arg_ptr_to_list_node(env, reg, argno, meta);
if (ret < 0)
return ret;
@ -19507,8 +19538,11 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
int struct_meta_reg = BPF_REG_3;
int node_offset_reg = BPF_REG_4;
/* rbtree_add has extra 'less' arg, so args-to-fixup are in diff regs */
if (is_bpf_rbtree_add_kfunc(desc->func_id)) {
/* list_add/rbtree_add have an extra arg (prev/less),
* so args-to-fixup are in diff regs.
*/
if (desc->func_id == special_kfunc_list[KF_bpf_list_add] ||
is_bpf_rbtree_add_kfunc(desc->func_id)) {
struct_meta_reg = BPF_REG_4;
node_offset_reg = BPF_REG_5;
}

View File

@ -367,6 +367,427 @@ long insert_rbtree_and_stash__del_tree_##rem_tree(void *ctx) \
INSERT_STASH_READ(true, "insert_stash_read: remove from tree");
INSERT_STASH_READ(false, "insert_stash_read: don't remove from tree");
SEC("tc")
__description("list_empty_test: list empty before add, non-empty after add")
__success __retval(0)
int list_empty_test(void *ctx)
{
struct node_data *node_new;
bpf_spin_lock(&lock);
if (!bpf_list_empty(&head)) {
bpf_spin_unlock(&lock);
return -1;
}
bpf_spin_unlock(&lock);
node_new = bpf_obj_new(typeof(*node_new));
if (!node_new)
return -2;
bpf_spin_lock(&lock);
bpf_list_push_front(&head, &node_new->l);
if (bpf_list_empty(&head)) {
bpf_spin_unlock(&lock);
return -3;
}
bpf_spin_unlock(&lock);
return 0;
}
static struct node_data *__add_in_list(struct bpf_list_head *head,
struct bpf_spin_lock *lock)
{
struct node_data *node_new, *node_ref;
node_new = bpf_obj_new(typeof(*node_new));
if (!node_new)
return NULL;
node_ref = bpf_refcount_acquire(node_new);
bpf_spin_lock(lock);
bpf_list_push_front(head, &node_new->l);
bpf_spin_unlock(lock);
return node_ref;
}
SEC("tc")
__description("list_is_edge_test1: is_first on first node, is_last on last node")
__success __retval(0)
int list_is_edge_test1(void *ctx)
{
struct node_data *node_first, *node_last;
int err = 0;
node_last = __add_in_list(&head, &lock);
if (!node_last)
return -1;
node_first = __add_in_list(&head, &lock);
if (!node_first) {
bpf_obj_drop(node_last);
return -2;
}
bpf_spin_lock(&lock);
if (!bpf_list_is_first(&head, &node_first->l)) {
err = -3;
goto fail;
}
if (!bpf_list_is_last(&head, &node_last->l))
err = -4;
fail:
bpf_spin_unlock(&lock);
bpf_obj_drop(node_first);
bpf_obj_drop(node_last);
return err;
}
SEC("tc")
__description("list_is_edge_test2: accept list_front/list_back return value")
__success __retval(0)
int list_is_edge_test2(void *ctx)
{
struct bpf_list_node *front, *back;
struct node_data *a, *b;
long err = 0;
a = __add_in_list(&head, &lock);
if (!a)
return -1;
b = __add_in_list(&head, &lock);
if (!b) {
bpf_obj_drop(a);
return -2;
}
bpf_spin_lock(&lock);
front = bpf_list_front(&head);
back = bpf_list_back(&head);
if (!front || !back) {
err = -3;
goto out_unlock;
}
if (!bpf_list_is_first(&head, front) || bpf_list_is_last(&head, front)) {
err = -4;
goto out_unlock;
}
if (!bpf_list_is_last(&head, back) || bpf_list_is_first(&head, back)) {
err = -5;
goto out_unlock;
}
out_unlock:
bpf_spin_unlock(&lock);
bpf_obj_drop(a);
bpf_obj_drop(b);
return err;
}
SEC("tc")
__description("list_is_edge_test3: single node is both first and last")
__success __retval(0)
int list_is_edge_test3(void *ctx)
{
struct node_data *tmp;
struct bpf_list_node *node;
long err = 0;
tmp = __add_in_list(&head, &lock);
if (!tmp)
return -1;
bpf_spin_lock(&lock);
node = bpf_list_front(&head);
if (!node) {
bpf_spin_unlock(&lock);
bpf_obj_drop(tmp);
return -2;
}
if (!bpf_list_is_first(&head, node) || !bpf_list_is_last(&head, node))
err = -3;
bpf_spin_unlock(&lock);
bpf_obj_drop(tmp);
return err;
}
SEC("tc")
__description("list_del_test1: del returns removed nodes")
__success __retval(0)
int list_del_test1(void *ctx)
{
struct node_data *node_first, *node_last;
struct bpf_list_node *bpf_node_first, *bpf_node_last;
int err = 0;
node_last = __add_in_list(&head, &lock);
if (!node_last)
return -1;
node_first = __add_in_list(&head, &lock);
if (!node_first) {
bpf_obj_drop(node_last);
return -2;
}
bpf_spin_lock(&lock);
bpf_node_last = bpf_list_del(&head, &node_last->l);
bpf_node_first = bpf_list_del(&head, &node_first->l);
bpf_spin_unlock(&lock);
if (bpf_node_first)
bpf_obj_drop(container_of(bpf_node_first, struct node_data, l));
else
err = -3;
if (bpf_node_last)
bpf_obj_drop(container_of(bpf_node_last, struct node_data, l));
else
err = -4;
bpf_obj_drop(node_first);
bpf_obj_drop(node_last);
return err;
}
SEC("tc")
__description("list_del_test2: remove an arbitrary node from the list")
__success __retval(0)
int list_del_test2(void *ctx)
{
struct bpf_rb_node *rb;
struct bpf_list_node *l;
struct node_data *n;
long err;
err = __insert_in_tree_and_list(&head, &root, &lock);
if (err)
return err;
bpf_spin_lock(&lock);
rb = bpf_rbtree_first(&root);
if (!rb) {
bpf_spin_unlock(&lock);
return -4;
}
rb = bpf_rbtree_remove(&root, rb);
if (!rb) {
bpf_spin_unlock(&lock);
return -5;
}
n = container_of(rb, struct node_data, r);
l = bpf_list_del(&head, &n->l);
bpf_spin_unlock(&lock);
bpf_obj_drop(n);
if (!l)
return -6;
bpf_obj_drop(container_of(l, struct node_data, l));
return 0;
}
SEC("tc")
__description("list_del_test3: list_del accepts list_front return value as node")
__success __retval(0)
int list_del_test3(void *ctx)
{
struct node_data *tmp;
struct bpf_list_node *bpf_node, *l;
long err = 0;
tmp = __add_in_list(&head, &lock);
if (!tmp)
return -1;
bpf_spin_lock(&lock);
bpf_node = bpf_list_front(&head);
if (!bpf_node) {
bpf_spin_unlock(&lock);
err = -2;
goto fail;
}
l = bpf_list_del(&head, bpf_node);
bpf_spin_unlock(&lock);
if (!l) {
err = -3;
goto fail;
}
bpf_obj_drop(container_of(l, struct node_data, l));
bpf_obj_drop(tmp);
return 0;
fail:
bpf_obj_drop(tmp);
return err;
}
SEC("tc")
__description("list_add_test1: insert new node after prev")
__success __retval(0)
int list_add_test1(void *ctx)
{
struct node_data *node_first;
struct node_data *new_node;
long err = 0;
node_first = __add_in_list(&head, &lock);
if (!node_first)
return -1;
new_node = bpf_obj_new(typeof(*new_node));
if (!new_node) {
err = -2;
goto fail;
}
bpf_spin_lock(&lock);
err = bpf_list_add(&head, &new_node->l, &node_first->l);
bpf_spin_unlock(&lock);
if (err) {
err = -3;
goto fail;
}
fail:
bpf_obj_drop(node_first);
return err;
}
SEC("tc")
__description("list_add_test2: list_add accepts list_front return value as prev")
__success __retval(0)
int list_add_test2(void *ctx)
{
struct node_data *new_node, *tmp;
struct bpf_list_node *bpf_node;
long err = 0;
tmp = __add_in_list(&head, &lock);
if (!tmp)
return -1;
new_node = bpf_obj_new(typeof(*new_node));
if (!new_node) {
err = -2;
goto fail;
}
bpf_spin_lock(&lock);
bpf_node = bpf_list_front(&head);
if (!bpf_node) {
bpf_spin_unlock(&lock);
bpf_obj_drop(new_node);
err = -3;
goto fail;
}
err = bpf_list_add(&head, &new_node->l, bpf_node);
bpf_spin_unlock(&lock);
if (err) {
err = -4;
goto fail;
}
fail:
bpf_obj_drop(tmp);
return err;
}
struct uninit_head_val {
struct bpf_spin_lock lock;
struct bpf_list_head head __contains(node_data, l);
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, int);
__type(value, struct uninit_head_val);
__uint(max_entries, 1);
} uninit_head_map SEC(".maps");
SEC("tc")
__description("list_push_back_uninit_head: push_back on 0-initialized list head")
__success __retval(0)
int list_push_back_uninit_head(void *ctx)
{
struct uninit_head_val *st;
struct node_data *node;
int ret = -1, key = 0;
st = bpf_map_lookup_elem(&uninit_head_map, &key);
if (!st)
return -1;
node = bpf_obj_new(typeof(*node));
if (!node)
return -1;
bpf_spin_lock(&st->lock);
ret = bpf_list_push_back(&st->head, &node->l);
bpf_spin_unlock(&st->lock);
return ret;
}
SEC("?tc")
__failure __msg("bpf_spin_lock at off=32 must be held for bpf_list_head")
long list_del_without_lock_fail(void *ctx)
{
struct node_data *n;
struct bpf_list_node *l;
n = bpf_obj_new(typeof(*n));
if (!n)
return -1;
/* Error case: delete list node without holding lock */
l = bpf_list_del(&head, &n->l);
bpf_obj_drop(n);
if (!l)
return -2;
bpf_obj_drop(container_of(l, struct node_data, l));
return 0;
}
SEC("?tc")
__failure __msg("bpf_spin_lock at off=32 must be held for bpf_list_head")
long list_add_without_lock_fail(void *ctx)
{
struct node_data *n, *prev;
long err;
n = bpf_obj_new(typeof(*n));
if (!n)
return -1;
prev = bpf_obj_new(typeof(*prev));
if (!prev) {
bpf_obj_drop(n);
return -1;
}
/* Error case: add list node without holding lock */
err = bpf_list_add(&head, &n->l, &prev->l);
bpf_obj_drop(prev);
if (err)
return -2;
return 0;
}
SEC("tc")
__success
long rbtree_refcounted_node_ref_escapes(void *ctx)