mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
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:
commit
b1fcdf9aa9
|
|
@ -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::
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user