BPF List
 help / color / mirror / Atom feed
* [PATCH bpf v3 0/9] Fixes for LPM trie
@ 2024-12-06 11:06 Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 1/9] bpf: Remove unnecessary check when updating " Hou Tao
                   ` (9 more replies)
  0 siblings, 10 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

Hi,

This patch set fixes several issues for LPM trie. These issues were
found during adding new test cases or were reported by syzbot.

The patch set is structured as follows:

Patch #1~#2 are clean-ups for lpm_trie_update_elem().
Patch #3 handles BPF_EXIST and BPF_NOEXIST correctly for LPM trie.
Patch #4 fixes the accounting of n_entries when doing in-place update.
Patch #5 fixes the exact match condition in trie_get_next_key() and it
may skip keys when the passed key is not found in the map.
Patch #6~#7 switch from kmalloc() to bpf memory allocator for LPM trie
to fix several lock order warnings reported by syzbot. It also enables
raw_spinlock_t for LPM trie again. After these changes, the LPM trie will
be closer to being usable in any context (though the reentrance check of
trie->lock is still missing, but it is on my todo list).
Patch #8: move test_lpm_map to map_tests to make it run regularly.
Patch #9: add test cases for the issues fixed by patch #3~#5.

Please see individual patches for more details. Comments are always
welcome.

Change Log:
v3:
  * patch #2: remove the unnecessary NULL-init for im_node
  * patch #6: alloc the leaf node before disabling IRQ to low
    the possibility of -ENOMEM when leaf_size is large; Free
    these nodes outside the trie lock (Suggested by Alexei)
  * collect review and ack tags (Thanks for Toke & Daniel)

v2: https://lore.kernel.org/bpf/20241127004641.1118269-1-houtao@huaweicloud.com/
  * collect review tags (Thanks for Toke)
  * drop "Add bpf_mem_cache_is_mergeable() helper" patch
  * patch #3~#4: add fix tag
  * patch #4: rename the helper to trie_check_add_elem() and increase
    n_entries in it.
  * patch #6: use one bpf mem allocator and update commit message to
    clarify that using bpf mem allocator is more appropriate.
  * patch #7: update commit message to add the possible max running time
    for update operation.
  * patch #9: update commit message to specify the purpose of these test
    cases.

v1: https://lore.kernel.org/bpf/20241118010808.2243555-1-houtao@huaweicloud.com/

Hou Tao (9):
  bpf: Remove unnecessary check when updating LPM trie
  bpf: Remove unnecessary kfree(im_node) in lpm_trie_update_elem
  bpf: Handle BPF_EXIST and BPF_NOEXIST for LPM trie
  bpf: Handle in-place update for full LPM trie correctly
  bpf: Fix exact match conditions in trie_get_next_key()
  bpf: Switch to bpf mem allocator for LPM trie
  bpf: Use raw_spinlock_t for LPM trie
  selftests/bpf: Move test_lpm_map.c to map_tests
  selftests/bpf: Add more test cases for LPM trie

 kernel/bpf/lpm_trie.c                         | 133 +++---
 tools/testing/selftests/bpf/.gitignore        |   1 -
 tools/testing/selftests/bpf/Makefile          |   2 +-
 .../lpm_trie_map_basic_ops.c}                 | 405 +++++++++++++++++-
 4 files changed, 484 insertions(+), 57 deletions(-)
 rename tools/testing/selftests/bpf/{test_lpm_map.c => map_tests/lpm_trie_map_basic_ops.c} (65%)

-- 
2.29.2


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 1/9] bpf: Remove unnecessary check when updating LPM trie
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 2/9] bpf: Remove unnecessary kfree(im_node) in lpm_trie_update_elem Hou Tao
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

When "node->prefixlen == matchlen" is true, it means that the node is
fully matched. If "node->prefixlen == key->prefixlen" is false, it means
the prefix length of key is greater than the prefix length of node,
otherwise, matchlen will not be equal with node->prefixlen. However, it
also implies that the prefix length of node must be less than
max_prefixlen.

Therefore, "node->prefixlen == trie->max_prefixlen" will always be false
when the check of "node->prefixlen == key->prefixlen" returns false.
Remove this unnecessary comparison.

Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 kernel/bpf/lpm_trie.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
index 9b60eda0f727..73fd593d3745 100644
--- a/kernel/bpf/lpm_trie.c
+++ b/kernel/bpf/lpm_trie.c
@@ -364,8 +364,7 @@ static long trie_update_elem(struct bpf_map *map,
 		matchlen = longest_prefix_match(trie, node, key);
 
 		if (node->prefixlen != matchlen ||
-		    node->prefixlen == key->prefixlen ||
-		    node->prefixlen == trie->max_prefixlen)
+		    node->prefixlen == key->prefixlen)
 			break;
 
 		next_bit = extract_bit(key->data, node->prefixlen);
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 2/9] bpf: Remove unnecessary kfree(im_node) in lpm_trie_update_elem
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 1/9] bpf: Remove unnecessary check when updating " Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 3/9] bpf: Handle BPF_EXIST and BPF_NOEXIST for LPM trie Hou Tao
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

There is no need to call kfree(im_node) when updating element fails,
because im_node must be NULL. Remove the unnecessary kfree() for
im_node.

Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 kernel/bpf/lpm_trie.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
index 73fd593d3745..b5e281a55760 100644
--- a/kernel/bpf/lpm_trie.c
+++ b/kernel/bpf/lpm_trie.c
@@ -315,7 +315,7 @@ static long trie_update_elem(struct bpf_map *map,
 			     void *_key, void *value, u64 flags)
 {
 	struct lpm_trie *trie = container_of(map, struct lpm_trie, map);
-	struct lpm_trie_node *node, *im_node = NULL, *new_node = NULL;
+	struct lpm_trie_node *node, *im_node, *new_node = NULL;
 	struct lpm_trie_node *free_node = NULL;
 	struct lpm_trie_node __rcu **slot;
 	struct bpf_lpm_trie_key_u8 *key = _key;
@@ -431,9 +431,7 @@ static long trie_update_elem(struct bpf_map *map,
 	if (ret) {
 		if (new_node)
 			trie->n_entries--;
-
 		kfree(new_node);
-		kfree(im_node);
 	}
 
 	spin_unlock_irqrestore(&trie->lock, irq_flags);
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 3/9] bpf: Handle BPF_EXIST and BPF_NOEXIST for LPM trie
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 1/9] bpf: Remove unnecessary check when updating " Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 2/9] bpf: Remove unnecessary kfree(im_node) in lpm_trie_update_elem Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 4/9] bpf: Handle in-place update for full LPM trie correctly Hou Tao
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

Add the currently missing handling for the BPF_EXIST and BPF_NOEXIST
flags. These flags can be specified by users and are relevant since LPM
trie supports exact matches during update.

Fixes: b95a5c4db09b ("bpf: add a longest prefix match trie map implementation")
Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 kernel/bpf/lpm_trie.c | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
index b5e281a55760..be5bf0389532 100644
--- a/kernel/bpf/lpm_trie.c
+++ b/kernel/bpf/lpm_trie.c
@@ -375,6 +375,10 @@ static long trie_update_elem(struct bpf_map *map,
 	 * simply assign the @new_node to that slot and be done.
 	 */
 	if (!node) {
+		if (flags == BPF_EXIST) {
+			ret = -ENOENT;
+			goto out;
+		}
 		rcu_assign_pointer(*slot, new_node);
 		goto out;
 	}
@@ -383,18 +387,31 @@ static long trie_update_elem(struct bpf_map *map,
 	 * which already has the correct data array set.
 	 */
 	if (node->prefixlen == matchlen) {
+		if (!(node->flags & LPM_TREE_NODE_FLAG_IM)) {
+			if (flags == BPF_NOEXIST) {
+				ret = -EEXIST;
+				goto out;
+			}
+			trie->n_entries--;
+		} else if (flags == BPF_EXIST) {
+			ret = -ENOENT;
+			goto out;
+		}
+
 		new_node->child[0] = node->child[0];
 		new_node->child[1] = node->child[1];
 
-		if (!(node->flags & LPM_TREE_NODE_FLAG_IM))
-			trie->n_entries--;
-
 		rcu_assign_pointer(*slot, new_node);
 		free_node = node;
 
 		goto out;
 	}
 
+	if (flags == BPF_EXIST) {
+		ret = -ENOENT;
+		goto out;
+	}
+
 	/* If the new node matches the prefix completely, it must be inserted
 	 * as an ancestor. Simply insert it between @node and *@slot.
 	 */
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 4/9] bpf: Handle in-place update for full LPM trie correctly
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
                   ` (2 preceding siblings ...)
  2024-12-06 11:06 ` [PATCH bpf v3 3/9] bpf: Handle BPF_EXIST and BPF_NOEXIST for LPM trie Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 5/9] bpf: Fix exact match conditions in trie_get_next_key() Hou Tao
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

When a LPM trie is full, in-place updates of existing elements
incorrectly return -ENOSPC.

Fix this by deferring the check of trie->n_entries. For new insertions,
n_entries must not exceed max_entries. However, in-place updates are
allowed even when the trie is full.

Fixes: b95a5c4db09b ("bpf: add a longest prefix match trie map implementation")
Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 kernel/bpf/lpm_trie.c | 44 +++++++++++++++++++++----------------------
 1 file changed, 21 insertions(+), 23 deletions(-)

diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
index be5bf0389532..df6cc0a1c9bf 100644
--- a/kernel/bpf/lpm_trie.c
+++ b/kernel/bpf/lpm_trie.c
@@ -310,6 +310,16 @@ static struct lpm_trie_node *lpm_trie_node_alloc(const struct lpm_trie *trie,
 	return node;
 }
 
+static int trie_check_add_elem(struct lpm_trie *trie, u64 flags)
+{
+	if (flags == BPF_EXIST)
+		return -ENOENT;
+	if (trie->n_entries == trie->map.max_entries)
+		return -ENOSPC;
+	trie->n_entries++;
+	return 0;
+}
+
 /* Called from syscall or from eBPF program */
 static long trie_update_elem(struct bpf_map *map,
 			     void *_key, void *value, u64 flags)
@@ -333,20 +343,12 @@ static long trie_update_elem(struct bpf_map *map,
 	spin_lock_irqsave(&trie->lock, irq_flags);
 
 	/* Allocate and fill a new node */
-
-	if (trie->n_entries == trie->map.max_entries) {
-		ret = -ENOSPC;
-		goto out;
-	}
-
 	new_node = lpm_trie_node_alloc(trie, value);
 	if (!new_node) {
 		ret = -ENOMEM;
 		goto out;
 	}
 
-	trie->n_entries++;
-
 	new_node->prefixlen = key->prefixlen;
 	RCU_INIT_POINTER(new_node->child[0], NULL);
 	RCU_INIT_POINTER(new_node->child[1], NULL);
@@ -375,10 +377,10 @@ static long trie_update_elem(struct bpf_map *map,
 	 * simply assign the @new_node to that slot and be done.
 	 */
 	if (!node) {
-		if (flags == BPF_EXIST) {
-			ret = -ENOENT;
+		ret = trie_check_add_elem(trie, flags);
+		if (ret)
 			goto out;
-		}
+
 		rcu_assign_pointer(*slot, new_node);
 		goto out;
 	}
@@ -392,10 +394,10 @@ static long trie_update_elem(struct bpf_map *map,
 				ret = -EEXIST;
 				goto out;
 			}
-			trie->n_entries--;
-		} else if (flags == BPF_EXIST) {
-			ret = -ENOENT;
-			goto out;
+		} else {
+			ret = trie_check_add_elem(trie, flags);
+			if (ret)
+				goto out;
 		}
 
 		new_node->child[0] = node->child[0];
@@ -407,10 +409,9 @@ static long trie_update_elem(struct bpf_map *map,
 		goto out;
 	}
 
-	if (flags == BPF_EXIST) {
-		ret = -ENOENT;
+	ret = trie_check_add_elem(trie, flags);
+	if (ret)
 		goto out;
-	}
 
 	/* If the new node matches the prefix completely, it must be inserted
 	 * as an ancestor. Simply insert it between @node and *@slot.
@@ -424,6 +425,7 @@ static long trie_update_elem(struct bpf_map *map,
 
 	im_node = lpm_trie_node_alloc(trie, NULL);
 	if (!im_node) {
+		trie->n_entries--;
 		ret = -ENOMEM;
 		goto out;
 	}
@@ -445,12 +447,8 @@ static long trie_update_elem(struct bpf_map *map,
 	rcu_assign_pointer(*slot, im_node);
 
 out:
-	if (ret) {
-		if (new_node)
-			trie->n_entries--;
+	if (ret)
 		kfree(new_node);
-	}
-
 	spin_unlock_irqrestore(&trie->lock, irq_flags);
 	kfree_rcu(free_node, rcu);
 
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 5/9] bpf: Fix exact match conditions in trie_get_next_key()
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
                   ` (3 preceding siblings ...)
  2024-12-06 11:06 ` [PATCH bpf v3 4/9] bpf: Handle in-place update for full LPM trie correctly Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie Hou Tao
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

trie_get_next_key() uses node->prefixlen == key->prefixlen to identify
an exact match, However, it is incorrect because when the target key
doesn't fully match the found node (e.g., node->prefixlen != matchlen),
these two nodes may also have the same prefixlen. It will return
expected result when the passed key exist in the trie. However when a
recently-deleted key or nonexistent key is passed to
trie_get_next_key(), it may skip keys and return incorrect result.

Fix it by using node->prefixlen == matchlen to identify exact matches.
When the condition is true after the search, it also implies
node->prefixlen equals key->prefixlen, otherwise, the search would
return NULL instead.

Fixes: b471f2f1de8b ("bpf: implement MAP_GET_NEXT_KEY command for LPM_TRIE map")
Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 kernel/bpf/lpm_trie.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
index df6cc0a1c9bf..9ba6ae145239 100644
--- a/kernel/bpf/lpm_trie.c
+++ b/kernel/bpf/lpm_trie.c
@@ -645,7 +645,7 @@ static int trie_get_next_key(struct bpf_map *map, void *_key, void *_next_key)
 	struct lpm_trie_node **node_stack = NULL;
 	int err = 0, stack_ptr = -1;
 	unsigned int next_bit;
-	size_t matchlen;
+	size_t matchlen = 0;
 
 	/* The get_next_key follows postorder. For the 4 node example in
 	 * the top of this file, the trie_get_next_key() returns the following
@@ -684,7 +684,7 @@ static int trie_get_next_key(struct bpf_map *map, void *_key, void *_next_key)
 		next_bit = extract_bit(key->data, node->prefixlen);
 		node = rcu_dereference(node->child[next_bit]);
 	}
-	if (!node || node->prefixlen != key->prefixlen ||
+	if (!node || node->prefixlen != matchlen ||
 	    (node->flags & LPM_TREE_NODE_FLAG_IM))
 		goto find_leftmost;
 
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
                   ` (4 preceding siblings ...)
  2024-12-06 11:06 ` [PATCH bpf v3 5/9] bpf: Fix exact match conditions in trie_get_next_key() Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2025-09-19 21:28   ` Andrii Nakryiko
  2024-12-06 11:06 ` [PATCH bpf v3 7/9] bpf: Use raw_spinlock_t " Hou Tao
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

Multiple syzbot warnings have been reported. These warnings are mainly
about the lock order between trie->lock and kmalloc()'s internal lock.
See report [1] as an example:

======================================================
WARNING: possible circular locking dependency detected
6.10.0-rc7-syzkaller-00003-g4376e966ecb7 #0 Not tainted
------------------------------------------------------
syz.3.2069/15008 is trying to acquire lock:
ffff88801544e6d8 (&n->list_lock){-.-.}-{2:2}, at: get_partial_node ...

but task is already holding lock:
ffff88802dcc89f8 (&trie->lock){-.-.}-{2:2}, at: trie_update_elem ...

which lock already depends on the new lock.

the existing dependency chain (in reverse order) is:

-> #1 (&trie->lock){-.-.}-{2:2}:
       __raw_spin_lock_irqsave
       _raw_spin_lock_irqsave+0x3a/0x60
       trie_delete_elem+0xb0/0x820
       ___bpf_prog_run+0x3e51/0xabd0
       __bpf_prog_run32+0xc1/0x100
       bpf_dispatcher_nop_func
       ......
       bpf_trace_run2+0x231/0x590
       __bpf_trace_contention_end+0xca/0x110
       trace_contention_end.constprop.0+0xea/0x170
       __pv_queued_spin_lock_slowpath+0x28e/0xcc0
       pv_queued_spin_lock_slowpath
       queued_spin_lock_slowpath
       queued_spin_lock
       do_raw_spin_lock+0x210/0x2c0
       __raw_spin_lock_irqsave
       _raw_spin_lock_irqsave+0x42/0x60
       __put_partials+0xc3/0x170
       qlink_free
       qlist_free_all+0x4e/0x140
       kasan_quarantine_reduce+0x192/0x1e0
       __kasan_slab_alloc+0x69/0x90
       kasan_slab_alloc
       slab_post_alloc_hook
       slab_alloc_node
       kmem_cache_alloc_node_noprof+0x153/0x310
       __alloc_skb+0x2b1/0x380
       ......

-> #0 (&n->list_lock){-.-.}-{2:2}:
       check_prev_add
       check_prevs_add
       validate_chain
       __lock_acquire+0x2478/0x3b30
       lock_acquire
       lock_acquire+0x1b1/0x560
       __raw_spin_lock_irqsave
       _raw_spin_lock_irqsave+0x3a/0x60
       get_partial_node.part.0+0x20/0x350
       get_partial_node
       get_partial
       ___slab_alloc+0x65b/0x1870
       __slab_alloc.constprop.0+0x56/0xb0
       __slab_alloc_node
       slab_alloc_node
       __do_kmalloc_node
       __kmalloc_node_noprof+0x35c/0x440
       kmalloc_node_noprof
       bpf_map_kmalloc_node+0x98/0x4a0
       lpm_trie_node_alloc
       trie_update_elem+0x1ef/0xe00
       bpf_map_update_value+0x2c1/0x6c0
       map_update_elem+0x623/0x910
       __sys_bpf+0x90c/0x49a0
       ...

other info that might help us debug this:

 Possible unsafe locking scenario:

       CPU0                    CPU1
       ----                    ----
  lock(&trie->lock);
                               lock(&n->list_lock);
                               lock(&trie->lock);
  lock(&n->list_lock);

 *** DEADLOCK ***

[1]: https://syzkaller.appspot.com/bug?extid=9045c0a3d5a7f1b119f7

A bpf program attached to trace_contention_end() triggers after
acquiring &n->list_lock. The program invokes trie_delete_elem(), which
then acquires trie->lock. However, it is possible that another
process is invoking trie_update_elem(). trie_update_elem() will acquire
trie->lock first, then invoke kmalloc_node(). kmalloc_node() may invoke
get_partial_node() and try to acquire &n->list_lock (not necessarily the
same lock object). Therefore, lockdep warns about the circular locking
dependency.

Invoking kmalloc() before acquiring trie->lock could fix the warning.
However, since BPF programs call be invoked from any context (e.g.,
through kprobe/tracepoint/fentry), there may still be lock ordering
problems for internal locks in kmalloc() or trie->lock itself.

To eliminate these potential lock ordering problems with kmalloc()'s
internal locks, replacing kmalloc()/kfree()/kfree_rcu() with equivalent
BPF memory allocator APIs that can be invoked in any context. The lock
ordering problems with trie->lock (e.g., reentrance) will be handled
separately.

Three aspects of this change require explanation:

1. Intermediate and leaf nodes are allocated from the same allocator.
Since the value size of LPM trie is usually small, using a single
alocator reduces the memory overhead of the BPF memory allocator.

2. Leaf nodes are allocated before disabling IRQs. This handles cases
where leaf_size is large (e.g., > 4KB - 8) and updates require
intermediate node allocation. If leaf nodes were allocated in
IRQ-disabled region, the free objects in BPF memory allocator would not
be refilled timely and the intermediate node allocation may fail.

3. Paired migrate_{disable|enable}() calls for node alloc and free. The
BPF memory allocator uses per-CPU struct internally, these paired calls
are necessary to guarantee correctness.

Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 kernel/bpf/lpm_trie.c | 71 +++++++++++++++++++++++++++++--------------
 1 file changed, 48 insertions(+), 23 deletions(-)

diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
index 9ba6ae145239..f850360e75ce 100644
--- a/kernel/bpf/lpm_trie.c
+++ b/kernel/bpf/lpm_trie.c
@@ -15,6 +15,7 @@
 #include <net/ipv6.h>
 #include <uapi/linux/btf.h>
 #include <linux/btf_ids.h>
+#include <linux/bpf_mem_alloc.h>
 
 /* Intermediate node */
 #define LPM_TREE_NODE_FLAG_IM BIT(0)
@@ -22,7 +23,6 @@
 struct lpm_trie_node;
 
 struct lpm_trie_node {
-	struct rcu_head rcu;
 	struct lpm_trie_node __rcu	*child[2];
 	u32				prefixlen;
 	u32				flags;
@@ -32,6 +32,7 @@ struct lpm_trie_node {
 struct lpm_trie {
 	struct bpf_map			map;
 	struct lpm_trie_node __rcu	*root;
+	struct bpf_mem_alloc		ma;
 	size_t				n_entries;
 	size_t				max_prefixlen;
 	size_t				data_size;
@@ -287,17 +288,18 @@ static void *trie_lookup_elem(struct bpf_map *map, void *_key)
 	return found->data + trie->data_size;
 }
 
-static struct lpm_trie_node *lpm_trie_node_alloc(const struct lpm_trie *trie,
-						 const void *value)
+static struct lpm_trie_node *lpm_trie_node_alloc(struct lpm_trie *trie,
+						 const void *value,
+						 bool disable_migration)
 {
 	struct lpm_trie_node *node;
-	size_t size = sizeof(struct lpm_trie_node) + trie->data_size;
 
-	if (value)
-		size += trie->map.value_size;
+	if (disable_migration)
+		migrate_disable();
+	node = bpf_mem_cache_alloc(&trie->ma);
+	if (disable_migration)
+		migrate_enable();
 
-	node = bpf_map_kmalloc_node(&trie->map, size, GFP_NOWAIT | __GFP_NOWARN,
-				    trie->map.numa_node);
 	if (!node)
 		return NULL;
 
@@ -325,7 +327,7 @@ static long trie_update_elem(struct bpf_map *map,
 			     void *_key, void *value, u64 flags)
 {
 	struct lpm_trie *trie = container_of(map, struct lpm_trie, map);
-	struct lpm_trie_node *node, *im_node, *new_node = NULL;
+	struct lpm_trie_node *node, *im_node, *new_node;
 	struct lpm_trie_node *free_node = NULL;
 	struct lpm_trie_node __rcu **slot;
 	struct bpf_lpm_trie_key_u8 *key = _key;
@@ -340,14 +342,14 @@ static long trie_update_elem(struct bpf_map *map,
 	if (key->prefixlen > trie->max_prefixlen)
 		return -EINVAL;
 
-	spin_lock_irqsave(&trie->lock, irq_flags);
+	/* Allocate and fill a new node. Need to disable migration before
+	 * invoking bpf_mem_cache_alloc().
+	 */
+	new_node = lpm_trie_node_alloc(trie, value, true);
+	if (!new_node)
+		return -ENOMEM;
 
-	/* Allocate and fill a new node */
-	new_node = lpm_trie_node_alloc(trie, value);
-	if (!new_node) {
-		ret = -ENOMEM;
-		goto out;
-	}
+	spin_lock_irqsave(&trie->lock, irq_flags);
 
 	new_node->prefixlen = key->prefixlen;
 	RCU_INIT_POINTER(new_node->child[0], NULL);
@@ -423,7 +425,8 @@ static long trie_update_elem(struct bpf_map *map,
 		goto out;
 	}
 
-	im_node = lpm_trie_node_alloc(trie, NULL);
+	/* migration is disabled within the locked scope */
+	im_node = lpm_trie_node_alloc(trie, NULL, false);
 	if (!im_node) {
 		trie->n_entries--;
 		ret = -ENOMEM;
@@ -447,10 +450,13 @@ static long trie_update_elem(struct bpf_map *map,
 	rcu_assign_pointer(*slot, im_node);
 
 out:
-	if (ret)
-		kfree(new_node);
 	spin_unlock_irqrestore(&trie->lock, irq_flags);
-	kfree_rcu(free_node, rcu);
+
+	migrate_disable();
+	if (ret)
+		bpf_mem_cache_free(&trie->ma, new_node);
+	bpf_mem_cache_free_rcu(&trie->ma, free_node);
+	migrate_enable();
 
 	return ret;
 }
@@ -548,8 +554,11 @@ static long trie_delete_elem(struct bpf_map *map, void *_key)
 
 out:
 	spin_unlock_irqrestore(&trie->lock, irq_flags);
-	kfree_rcu(free_parent, rcu);
-	kfree_rcu(free_node, rcu);
+
+	migrate_disable();
+	bpf_mem_cache_free_rcu(&trie->ma, free_parent);
+	bpf_mem_cache_free_rcu(&trie->ma, free_node);
+	migrate_enable();
 
 	return ret;
 }
@@ -571,6 +580,8 @@ static long trie_delete_elem(struct bpf_map *map, void *_key)
 static struct bpf_map *trie_alloc(union bpf_attr *attr)
 {
 	struct lpm_trie *trie;
+	size_t leaf_size;
+	int err;
 
 	/* check sanity of attributes */
 	if (attr->max_entries == 0 ||
@@ -595,7 +606,17 @@ static struct bpf_map *trie_alloc(union bpf_attr *attr)
 
 	spin_lock_init(&trie->lock);
 
+	/* Allocate intermediate and leaf nodes from the same allocator */
+	leaf_size = sizeof(struct lpm_trie_node) + trie->data_size +
+		    trie->map.value_size;
+	err = bpf_mem_alloc_init(&trie->ma, leaf_size, false);
+	if (err)
+		goto free_out;
 	return &trie->map;
+
+free_out:
+	bpf_map_area_free(trie);
+	return ERR_PTR(err);
 }
 
 static void trie_free(struct bpf_map *map)
@@ -627,13 +648,17 @@ static void trie_free(struct bpf_map *map)
 				continue;
 			}
 
-			kfree(node);
+			/* No bpf program may access the map, so freeing the
+			 * node without waiting for the extra RCU GP.
+			 */
+			bpf_mem_cache_raw_free(node);
 			RCU_INIT_POINTER(*slot, NULL);
 			break;
 		}
 	}
 
 out:
+	bpf_mem_alloc_destroy(&trie->ma);
 	bpf_map_area_free(trie);
 }
 
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 7/9] bpf: Use raw_spinlock_t for LPM trie
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
                   ` (5 preceding siblings ...)
  2024-12-06 11:06 ` [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 8/9] selftests/bpf: Move test_lpm_map.c to map_tests Hou Tao
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

After switching from kmalloc() to the bpf memory allocator, there will be
no blocking operation during the update of LPM trie. Therefore, change
trie->lock from spinlock_t to raw_spinlock_t to make LPM trie usable in
atomic context, even on RT kernels.

The max value of prefixlen is 2048. Therefore, update or deletion
operations will find the target after at most 2048 comparisons.
Constructing a test case which updates an element after 2048 comparisons
under a 8 CPU VM, and the average time and the maximal time for such
update operation is about 210us and 900us.

Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 kernel/bpf/lpm_trie.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
index f850360e75ce..f8bc1e096182 100644
--- a/kernel/bpf/lpm_trie.c
+++ b/kernel/bpf/lpm_trie.c
@@ -36,7 +36,7 @@ struct lpm_trie {
 	size_t				n_entries;
 	size_t				max_prefixlen;
 	size_t				data_size;
-	spinlock_t			lock;
+	raw_spinlock_t			lock;
 };
 
 /* This trie implements a longest prefix match algorithm that can be used to
@@ -349,7 +349,7 @@ static long trie_update_elem(struct bpf_map *map,
 	if (!new_node)
 		return -ENOMEM;
 
-	spin_lock_irqsave(&trie->lock, irq_flags);
+	raw_spin_lock_irqsave(&trie->lock, irq_flags);
 
 	new_node->prefixlen = key->prefixlen;
 	RCU_INIT_POINTER(new_node->child[0], NULL);
@@ -450,7 +450,7 @@ static long trie_update_elem(struct bpf_map *map,
 	rcu_assign_pointer(*slot, im_node);
 
 out:
-	spin_unlock_irqrestore(&trie->lock, irq_flags);
+	raw_spin_unlock_irqrestore(&trie->lock, irq_flags);
 
 	migrate_disable();
 	if (ret)
@@ -477,7 +477,7 @@ static long trie_delete_elem(struct bpf_map *map, void *_key)
 	if (key->prefixlen > trie->max_prefixlen)
 		return -EINVAL;
 
-	spin_lock_irqsave(&trie->lock, irq_flags);
+	raw_spin_lock_irqsave(&trie->lock, irq_flags);
 
 	/* Walk the tree looking for an exact key/length match and keeping
 	 * track of the path we traverse.  We will need to know the node
@@ -553,7 +553,7 @@ static long trie_delete_elem(struct bpf_map *map, void *_key)
 	free_node = node;
 
 out:
-	spin_unlock_irqrestore(&trie->lock, irq_flags);
+	raw_spin_unlock_irqrestore(&trie->lock, irq_flags);
 
 	migrate_disable();
 	bpf_mem_cache_free_rcu(&trie->ma, free_parent);
@@ -604,7 +604,7 @@ static struct bpf_map *trie_alloc(union bpf_attr *attr)
 			  offsetof(struct bpf_lpm_trie_key_u8, data);
 	trie->max_prefixlen = trie->data_size * 8;
 
-	spin_lock_init(&trie->lock);
+	raw_spin_lock_init(&trie->lock);
 
 	/* Allocate intermediate and leaf nodes from the same allocator */
 	leaf_size = sizeof(struct lpm_trie_node) + trie->data_size +
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 8/9] selftests/bpf: Move test_lpm_map.c to map_tests
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
                   ` (6 preceding siblings ...)
  2024-12-06 11:06 ` [PATCH bpf v3 7/9] bpf: Use raw_spinlock_t " Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2024-12-06 11:06 ` [PATCH bpf v3 9/9] selftests/bpf: Add more test cases for LPM trie Hou Tao
  2024-12-06 17:40 ` [PATCH bpf v3 0/9] Fixes " patchwork-bot+netdevbpf
  9 siblings, 0 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

Move test_lpm_map.c to map_tests/ to include LPM trie test cases in
regular test_maps run. Most code remains unchanged, including the use of
assert(). Only reduce n_lookups from 64K to 512, which decreases
test_lpm_map runtime from 37s to 0.7s.

Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 tools/testing/selftests/bpf/.gitignore                 |  1 -
 tools/testing/selftests/bpf/Makefile                   |  2 +-
 .../lpm_trie_map_basic_ops.c}                          | 10 +++-------
 3 files changed, 4 insertions(+), 9 deletions(-)
 rename tools/testing/selftests/bpf/{test_lpm_map.c => map_tests/lpm_trie_map_basic_ops.c} (99%)

diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
index c2a1842c3d8b..e9c377001f93 100644
--- a/tools/testing/selftests/bpf/.gitignore
+++ b/tools/testing/selftests/bpf/.gitignore
@@ -5,7 +5,6 @@ bpf-syscall*
 test_verifier
 test_maps
 test_lru_map
-test_lpm_map
 test_tag
 FEATURE-DUMP.libbpf
 FEATURE-DUMP.selftests
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 6ad3b1ba1920..7eeb3cbe18c7 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -83,7 +83,7 @@ CLANG_CPUV4 := 1
 endif
 
 # Order correspond to 'make run_tests' order
-TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \
+TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_progs \
 	test_sockmap \
 	test_tcpnotify_user test_sysctl \
 	test_progs-no_alu32
diff --git a/tools/testing/selftests/bpf/test_lpm_map.c b/tools/testing/selftests/bpf/map_tests/lpm_trie_map_basic_ops.c
similarity index 99%
rename from tools/testing/selftests/bpf/test_lpm_map.c
rename to tools/testing/selftests/bpf/map_tests/lpm_trie_map_basic_ops.c
index d98c72dc563e..f375c89d78a4 100644
--- a/tools/testing/selftests/bpf/test_lpm_map.c
+++ b/tools/testing/selftests/bpf/map_tests/lpm_trie_map_basic_ops.c
@@ -223,7 +223,7 @@ static void test_lpm_map(int keysize)
 	n_matches = 0;
 	n_matches_after_delete = 0;
 	n_nodes = 1 << 8;
-	n_lookups = 1 << 16;
+	n_lookups = 1 << 9;
 
 	data = alloca(keysize);
 	memset(data, 0, keysize);
@@ -770,16 +770,13 @@ static void test_lpm_multi_thread(void)
 	close(map_fd);
 }
 
-int main(void)
+void test_lpm_trie_map_basic_ops(void)
 {
 	int i;
 
 	/* we want predictable, pseudo random tests */
 	srand(0xf00ba1);
 
-	/* Use libbpf 1.0 API mode */
-	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
-
 	test_lpm_basic();
 	test_lpm_order();
 
@@ -792,6 +789,5 @@ int main(void)
 	test_lpm_get_next_key();
 	test_lpm_multi_thread();
 
-	printf("test_lpm: OK\n");
-	return 0;
+	printf("%s: PASS\n", __func__);
 }
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH bpf v3 9/9] selftests/bpf: Add more test cases for LPM trie
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
                   ` (7 preceding siblings ...)
  2024-12-06 11:06 ` [PATCH bpf v3 8/9] selftests/bpf: Move test_lpm_map.c to map_tests Hou Tao
@ 2024-12-06 11:06 ` Hou Tao
  2024-12-06 17:40 ` [PATCH bpf v3 0/9] Fixes " patchwork-bot+netdevbpf
  9 siblings, 0 replies; 17+ messages in thread
From: Hou Tao @ 2024-12-06 11:06 UTC (permalink / raw)
  To: bpf
  Cc: Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

From: Hou Tao <houtao1@huawei.com>

Add more test cases for LPM trie in test_maps:

1) test_lpm_trie_update_flags
It constructs various use cases for BPF_EXIST and BPF_NOEXIST and check
whether the return value of update operation is expected.

2) test_lpm_trie_update_full_maps
It tests the update operations on a full LPM trie map. Adding new node
will fail and overwriting the value of existed node will succeed.

3) test_lpm_trie_iterate_strs and test_lpm_trie_iterate_ints
There two test cases test whether the iteration through get_next_key is
sorted and expected. These two test cases delete the minimal key after
each iteration and check whether next iteration returns the second
minimal key. The only difference between these two test cases is the
former one saves strings in the LPM trie and the latter saves integers.
Without the fix of get_next_key, these two cases will fail as shown
below:
  test_lpm_trie_iterate_strs(1091):FAIL:iterate #2 got abc exp abS
  test_lpm_trie_iterate_ints(1142):FAIL:iterate #1 got 0x2 exp 0x1

Signed-off-by: Hou Tao <houtao1@huawei.com>
---
 .../bpf/map_tests/lpm_trie_map_basic_ops.c    | 395 ++++++++++++++++++
 1 file changed, 395 insertions(+)

diff --git a/tools/testing/selftests/bpf/map_tests/lpm_trie_map_basic_ops.c b/tools/testing/selftests/bpf/map_tests/lpm_trie_map_basic_ops.c
index f375c89d78a4..d32e4edac930 100644
--- a/tools/testing/selftests/bpf/map_tests/lpm_trie_map_basic_ops.c
+++ b/tools/testing/selftests/bpf/map_tests/lpm_trie_map_basic_ops.c
@@ -20,10 +20,12 @@
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#include <endian.h>
 #include <arpa/inet.h>
 #include <sys/time.h>
 
 #include <bpf/bpf.h>
+#include <test_maps.h>
 
 #include "bpf_util.h"
 
@@ -33,6 +35,22 @@ struct tlpm_node {
 	uint8_t key[];
 };
 
+struct lpm_trie_bytes_key {
+	union {
+		struct bpf_lpm_trie_key_hdr hdr;
+		__u32 prefixlen;
+	};
+	unsigned char data[8];
+};
+
+struct lpm_trie_int_key {
+	union {
+		struct bpf_lpm_trie_key_hdr hdr;
+		__u32 prefixlen;
+	};
+	unsigned int data;
+};
+
 static struct tlpm_node *tlpm_match(struct tlpm_node *list,
 				    const uint8_t *key,
 				    size_t n_bits);
@@ -770,6 +788,378 @@ static void test_lpm_multi_thread(void)
 	close(map_fd);
 }
 
+static int lpm_trie_create(unsigned int key_size, unsigned int value_size, unsigned int max_entries)
+{
+	LIBBPF_OPTS(bpf_map_create_opts, opts);
+	int fd;
+
+	opts.map_flags = BPF_F_NO_PREALLOC;
+	fd = bpf_map_create(BPF_MAP_TYPE_LPM_TRIE, "lpm_trie", key_size, value_size, max_entries,
+			    &opts);
+	CHECK(fd < 0, "bpf_map_create", "error %d\n", errno);
+
+	return fd;
+}
+
+static void test_lpm_trie_update_flags(void)
+{
+	struct lpm_trie_int_key key;
+	unsigned int value, got;
+	int fd, err;
+
+	fd = lpm_trie_create(sizeof(key), sizeof(value), 3);
+
+	/* invalid flags (Error) */
+	key.prefixlen = 32;
+	key.data = 0;
+	value = 0;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_F_LOCK);
+	CHECK(err != -EINVAL, "invalid update flag", "error %d\n", err);
+
+	/* invalid flags (Error) */
+	key.prefixlen = 32;
+	key.data = 0;
+	value = 0;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST | BPF_EXIST);
+	CHECK(err != -EINVAL, "invalid update flag", "error %d\n", err);
+
+	/* overwrite an empty qp-trie (Error) */
+	key.prefixlen = 32;
+	key.data = 0;
+	value = 2;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_EXIST);
+	CHECK(err != -ENOENT, "overwrite empty qp-trie", "error %d\n", err);
+
+	/* add a new node */
+	key.prefixlen = 16;
+	key.data = 0;
+	value = 1;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+	CHECK(err, "add new elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup elem", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* add the same node as new node (Error) */
+	err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+	CHECK(err != -EEXIST, "add new elem again", "error %d\n", err);
+
+	/* overwrite the existed node */
+	value = 4;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_EXIST);
+	CHECK(err, "overwrite elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup elem", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* overwrite the node */
+	value = 1;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_ANY);
+	CHECK(err, "update elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup elem", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* overwrite a non-existent node which is the prefix of the first
+	 * node (Error).
+	 */
+	key.prefixlen = 8;
+	key.data = 0;
+	value = 2;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_EXIST);
+	CHECK(err != -ENOENT, "overwrite nonexistent elem", "error %d\n", err);
+
+	/* add a new node which is the prefix of the first node */
+	err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+	CHECK(err, "add new elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup key", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* add another new node which will be the sibling of the first node */
+	key.prefixlen = 9;
+	key.data = htobe32(1 << 23);
+	value = 5;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+	CHECK(err, "add new elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup key", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* overwrite the third node */
+	value = 3;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_ANY);
+	CHECK(err, "overwrite elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup key", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* delete the second node to make it an intermediate node */
+	key.prefixlen = 8;
+	key.data = 0;
+	err = bpf_map_delete_elem(fd, &key);
+	CHECK(err, "del elem", "error %d\n", err);
+
+	/* overwrite the intermediate node (Error) */
+	value = 2;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_EXIST);
+	CHECK(err != -ENOENT, "overwrite nonexistent elem", "error %d\n", err);
+
+	close(fd);
+}
+
+static void test_lpm_trie_update_full_map(void)
+{
+	struct lpm_trie_int_key key;
+	int value, got;
+	int fd, err;
+
+	fd = lpm_trie_create(sizeof(key), sizeof(value), 3);
+
+	/* add a new node */
+	key.prefixlen = 16;
+	key.data = 0;
+	value = 0;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+	CHECK(err, "add new elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup elem", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* add new node */
+	key.prefixlen = 8;
+	key.data = 0;
+	value = 1;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+	CHECK(err, "add new elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup elem", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* add new node */
+	key.prefixlen = 9;
+	key.data = htobe32(1 << 23);
+	value = 2;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+	CHECK(err, "add new elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup elem", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* try to add more node (Error) */
+	key.prefixlen = 32;
+	key.data = 0;
+	value = 3;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_ANY);
+	CHECK(err != -ENOSPC, "add to full trie", "error %d\n", err);
+
+	/* update the value of an existed node with BPF_EXIST */
+	key.prefixlen = 16;
+	key.data = 0;
+	value = 4;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_EXIST);
+	CHECK(err, "overwrite elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup elem", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	/* update the value of an existed node with BPF_ANY */
+	key.prefixlen = 9;
+	key.data = htobe32(1 << 23);
+	value = 5;
+	err = bpf_map_update_elem(fd, &key, &value, BPF_ANY);
+	CHECK(err, "overwrite elem", "error %d\n", err);
+	got = 0;
+	err = bpf_map_lookup_elem(fd, &key, &got);
+	CHECK(err, "lookup elem", "error %d\n", err);
+	CHECK(got != value, "check value", "got %d exp %d\n", got, value);
+
+	close(fd);
+}
+
+static int cmp_str(const void *a, const void *b)
+{
+	const char *str_a = *(const char **)a, *str_b = *(const char **)b;
+
+	return strcmp(str_a, str_b);
+}
+
+/* Save strings in LPM trie. The trailing '\0' for each string will be
+ * accounted in the prefixlen. The strings returned during the iteration
+ * should be sorted as expected.
+ */
+static void test_lpm_trie_iterate_strs(void)
+{
+	static const char * const keys[] = {
+		"ab", "abO", "abc", "abo", "abS", "abcd",
+	};
+	const char *sorted_keys[ARRAY_SIZE(keys)];
+	struct lpm_trie_bytes_key key, next_key;
+	unsigned int value, got, i, j, len;
+	struct lpm_trie_bytes_key *cur;
+	int fd, err;
+
+	fd = lpm_trie_create(sizeof(key), sizeof(value), ARRAY_SIZE(keys));
+
+	for (i = 0; i < ARRAY_SIZE(keys); i++) {
+		unsigned int flags;
+
+		/* add i-th element */
+		flags = i % 2 ? BPF_NOEXIST : 0;
+		len = strlen(keys[i]);
+		/* include the trailing '\0' */
+		key.prefixlen = (len + 1) * 8;
+		memset(key.data, 0, sizeof(key.data));
+		memcpy(key.data, keys[i], len);
+		value = i + 100;
+		err = bpf_map_update_elem(fd, &key, &value, flags);
+		CHECK(err, "add elem", "#%u error %d\n", i, err);
+
+		err = bpf_map_lookup_elem(fd, &key, &got);
+		CHECK(err, "lookup elem", "#%u error %d\n", i, err);
+		CHECK(got != value, "lookup elem", "#%u expect %u got %u\n", i, value, got);
+
+		/* re-add i-th element (Error) */
+		err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+		CHECK(err != -EEXIST, "re-add elem", "#%u error %d\n", i, err);
+
+		/* Overwrite i-th element */
+		flags = i % 2 ? 0 : BPF_EXIST;
+		value = i;
+		err = bpf_map_update_elem(fd, &key, &value, flags);
+		CHECK(err, "update elem", "error %d\n", err);
+
+		/* Lookup #[0~i] elements */
+		for (j = 0; j <= i; j++) {
+			len = strlen(keys[j]);
+			key.prefixlen = (len + 1) * 8;
+			memset(key.data, 0, sizeof(key.data));
+			memcpy(key.data, keys[j], len);
+			err = bpf_map_lookup_elem(fd, &key, &got);
+			CHECK(err, "lookup elem", "#%u/%u error %d\n", i, j, err);
+			CHECK(got != j, "lookup elem", "#%u/%u expect %u got %u\n",
+			      i, j, value, got);
+		}
+	}
+
+	/* Add element to a full qp-trie (Error) */
+	key.prefixlen = sizeof(key.data) * 8;
+	memset(key.data, 0, sizeof(key.data));
+	value = 0;
+	err = bpf_map_update_elem(fd, &key, &value, 0);
+	CHECK(err != -ENOSPC, "add to full qp-trie", "error %d\n", err);
+
+	/* Iterate sorted elements: no deletion */
+	memcpy(sorted_keys, keys, sizeof(keys));
+	qsort(sorted_keys, ARRAY_SIZE(sorted_keys), sizeof(sorted_keys[0]), cmp_str);
+	cur = NULL;
+	for (i = 0; i < ARRAY_SIZE(sorted_keys); i++) {
+		len = strlen(sorted_keys[i]);
+		err = bpf_map_get_next_key(fd, cur, &next_key);
+		CHECK(err, "iterate", "#%u error %d\n", i, err);
+		CHECK(next_key.prefixlen != (len + 1) * 8, "iterate",
+		      "#%u invalid len %u expect %u\n",
+		      i, next_key.prefixlen, (len + 1) * 8);
+		CHECK(memcmp(sorted_keys[i], next_key.data, len + 1), "iterate",
+		      "#%u got %.*s exp %.*s\n", i, len, next_key.data, len, sorted_keys[i]);
+
+		cur = &next_key;
+	}
+	err = bpf_map_get_next_key(fd, cur, &next_key);
+	CHECK(err != -ENOENT, "more element", "error %d\n", err);
+
+	/* Iterate sorted elements: delete the found key after each iteration */
+	cur = NULL;
+	for (i = 0; i < ARRAY_SIZE(sorted_keys); i++) {
+		len = strlen(sorted_keys[i]);
+		err = bpf_map_get_next_key(fd, cur, &next_key);
+		CHECK(err, "iterate", "#%u error %d\n", i, err);
+		CHECK(next_key.prefixlen != (len + 1) * 8, "iterate",
+		      "#%u invalid len %u expect %u\n",
+		      i, next_key.prefixlen, (len + 1) * 8);
+		CHECK(memcmp(sorted_keys[i], next_key.data, len + 1), "iterate",
+		      "#%u got %.*s exp %.*s\n", i, len, next_key.data, len, sorted_keys[i]);
+
+		cur = &next_key;
+
+		err = bpf_map_delete_elem(fd, cur);
+		CHECK(err, "delete", "#%u error %d\n", i, err);
+	}
+	err = bpf_map_get_next_key(fd, cur, &next_key);
+	CHECK(err != -ENOENT, "non-empty qp-trie", "error %d\n", err);
+
+	close(fd);
+}
+
+/* Use the fixed prefixlen (32) and save integers in LPM trie. The iteration of
+ * LPM trie will return these integers in big-endian order, therefore, convert
+ * these integers to big-endian before update. After each iteration, delete the
+ * found key (the smallest integer) and expect the next iteration will return
+ * the second smallest number.
+ */
+static void test_lpm_trie_iterate_ints(void)
+{
+	struct lpm_trie_int_key key, next_key;
+	unsigned int i, max_entries;
+	struct lpm_trie_int_key *cur;
+	unsigned int *data_set;
+	int fd, err;
+	bool value;
+
+	max_entries = 4096;
+	data_set = calloc(max_entries, sizeof(*data_set));
+	CHECK(!data_set, "malloc", "no mem\n");
+	for (i = 0; i < max_entries; i++)
+		data_set[i] = i;
+
+	fd = lpm_trie_create(sizeof(key), sizeof(value), max_entries);
+	value = true;
+	for (i = 0; i < max_entries; i++) {
+		key.prefixlen = 32;
+		key.data = htobe32(data_set[i]);
+
+		err = bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
+		CHECK(err, "add elem", "#%u error %d\n", i, err);
+	}
+
+	cur = NULL;
+	for (i = 0; i < max_entries; i++) {
+		err = bpf_map_get_next_key(fd, cur, &next_key);
+		CHECK(err, "iterate", "#%u error %d\n", i, err);
+		CHECK(next_key.prefixlen != 32, "iterate", "#%u invalid len %u\n",
+		      i, next_key.prefixlen);
+		CHECK(be32toh(next_key.data) != data_set[i], "iterate", "#%u got 0x%x exp 0x%x\n",
+		      i, be32toh(next_key.data), data_set[i]);
+		cur = &next_key;
+
+		/*
+		 * Delete the minimal key, the next call of bpf_get_next_key()
+		 * will return the second minimal key.
+		 */
+		err = bpf_map_delete_elem(fd, &next_key);
+		CHECK(err, "del elem", "#%u elem error %d\n", i, err);
+	}
+	err = bpf_map_get_next_key(fd, cur, &next_key);
+	CHECK(err != -ENOENT, "more element", "error %d\n", err);
+
+	err = bpf_map_get_next_key(fd, NULL, &next_key);
+	CHECK(err != -ENOENT, "no-empty qp-trie", "error %d\n", err);
+
+	free(data_set);
+
+	close(fd);
+}
+
 void test_lpm_trie_map_basic_ops(void)
 {
 	int i;
@@ -789,5 +1179,10 @@ void test_lpm_trie_map_basic_ops(void)
 	test_lpm_get_next_key();
 	test_lpm_multi_thread();
 
+	test_lpm_trie_update_flags();
+	test_lpm_trie_update_full_map();
+	test_lpm_trie_iterate_strs();
+	test_lpm_trie_iterate_ints();
+
 	printf("%s: PASS\n", __func__);
 }
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH bpf v3 0/9] Fixes for LPM trie
  2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
                   ` (8 preceding siblings ...)
  2024-12-06 11:06 ` [PATCH bpf v3 9/9] selftests/bpf: Add more test cases for LPM trie Hou Tao
@ 2024-12-06 17:40 ` patchwork-bot+netdevbpf
  9 siblings, 0 replies; 17+ messages in thread
From: patchwork-bot+netdevbpf @ 2024-12-06 17:40 UTC (permalink / raw)
  To: Hou Tao
  Cc: bpf, martin.lau, alexei.starovoitov, andrii, eddyz87, song,
	haoluo, yonghong.song, daniel, kpsingh, sdf, jolsa,
	john.fastabend, toke, bigeasy, tglx, linux, houtao1, xukuohai

Hello:

This series was applied to bpf/bpf.git (master)
by Alexei Starovoitov <ast@kernel.org>:

On Fri,  6 Dec 2024 19:06:13 +0800 you wrote:
> From: Hou Tao <houtao1@huawei.com>
> 
> Hi,
> 
> This patch set fixes several issues for LPM trie. These issues were
> found during adding new test cases or were reported by syzbot.
> 
> [...]

Here is the summary with links:
  - [bpf,v3,1/9] bpf: Remove unnecessary check when updating LPM trie
    https://git.kernel.org/bpf/bpf/c/156c977c539e
  - [bpf,v3,2/9] bpf: Remove unnecessary kfree(im_node) in lpm_trie_update_elem
    https://git.kernel.org/bpf/bpf/c/3d5611b4d7ef
  - [bpf,v3,3/9] bpf: Handle BPF_EXIST and BPF_NOEXIST for LPM trie
    https://git.kernel.org/bpf/bpf/c/eae6a075e953
  - [bpf,v3,4/9] bpf: Handle in-place update for full LPM trie correctly
    https://git.kernel.org/bpf/bpf/c/532d6b36b2bf
  - [bpf,v3,5/9] bpf: Fix exact match conditions in trie_get_next_key()
    https://git.kernel.org/bpf/bpf/c/27abc7b3fa2e
  - [bpf,v3,6/9] bpf: Switch to bpf mem allocator for LPM trie
    https://git.kernel.org/bpf/bpf/c/3d8dc43eb2a3
  - [bpf,v3,7/9] bpf: Use raw_spinlock_t for LPM trie
    https://git.kernel.org/bpf/bpf/c/6a5c63d43c02
  - [bpf,v3,8/9] selftests/bpf: Move test_lpm_map.c to map_tests
    https://git.kernel.org/bpf/bpf/c/3e18f5f1e5a1
  - [bpf,v3,9/9] selftests/bpf: Add more test cases for LPM trie
    https://git.kernel.org/bpf/bpf/c/04d4ce91b0be

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie
  2024-12-06 11:06 ` [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie Hou Tao
@ 2025-09-19 21:28   ` Andrii Nakryiko
  2025-09-23  1:33     ` Hou Tao
  0 siblings, 1 reply; 17+ messages in thread
From: Andrii Nakryiko @ 2025-09-19 21:28 UTC (permalink / raw)
  To: Hou Tao
  Cc: bpf, Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

On Fri, Dec 6, 2024 at 2:54 AM Hou Tao <houtao@huaweicloud.com> wrote:
>
> From: Hou Tao <houtao1@huawei.com>
>
> Multiple syzbot warnings have been reported. These warnings are mainly
> about the lock order between trie->lock and kmalloc()'s internal lock.
> See report [1] as an example:
>
> ======================================================
> WARNING: possible circular locking dependency detected
> 6.10.0-rc7-syzkaller-00003-g4376e966ecb7 #0 Not tainted
> ------------------------------------------------------
> syz.3.2069/15008 is trying to acquire lock:
> ffff88801544e6d8 (&n->list_lock){-.-.}-{2:2}, at: get_partial_node ...
>
> but task is already holding lock:
> ffff88802dcc89f8 (&trie->lock){-.-.}-{2:2}, at: trie_update_elem ...
>
> which lock already depends on the new lock.
>
> the existing dependency chain (in reverse order) is:
>
> -> #1 (&trie->lock){-.-.}-{2:2}:
>        __raw_spin_lock_irqsave
>        _raw_spin_lock_irqsave+0x3a/0x60
>        trie_delete_elem+0xb0/0x820
>        ___bpf_prog_run+0x3e51/0xabd0
>        __bpf_prog_run32+0xc1/0x100
>        bpf_dispatcher_nop_func
>        ......
>        bpf_trace_run2+0x231/0x590
>        __bpf_trace_contention_end+0xca/0x110
>        trace_contention_end.constprop.0+0xea/0x170
>        __pv_queued_spin_lock_slowpath+0x28e/0xcc0
>        pv_queued_spin_lock_slowpath
>        queued_spin_lock_slowpath
>        queued_spin_lock
>        do_raw_spin_lock+0x210/0x2c0
>        __raw_spin_lock_irqsave
>        _raw_spin_lock_irqsave+0x42/0x60
>        __put_partials+0xc3/0x170
>        qlink_free
>        qlist_free_all+0x4e/0x140
>        kasan_quarantine_reduce+0x192/0x1e0
>        __kasan_slab_alloc+0x69/0x90
>        kasan_slab_alloc
>        slab_post_alloc_hook
>        slab_alloc_node
>        kmem_cache_alloc_node_noprof+0x153/0x310
>        __alloc_skb+0x2b1/0x380
>        ......
>
> -> #0 (&n->list_lock){-.-.}-{2:2}:
>        check_prev_add
>        check_prevs_add
>        validate_chain
>        __lock_acquire+0x2478/0x3b30
>        lock_acquire
>        lock_acquire+0x1b1/0x560
>        __raw_spin_lock_irqsave
>        _raw_spin_lock_irqsave+0x3a/0x60
>        get_partial_node.part.0+0x20/0x350
>        get_partial_node
>        get_partial
>        ___slab_alloc+0x65b/0x1870
>        __slab_alloc.constprop.0+0x56/0xb0
>        __slab_alloc_node
>        slab_alloc_node
>        __do_kmalloc_node
>        __kmalloc_node_noprof+0x35c/0x440
>        kmalloc_node_noprof
>        bpf_map_kmalloc_node+0x98/0x4a0
>        lpm_trie_node_alloc
>        trie_update_elem+0x1ef/0xe00
>        bpf_map_update_value+0x2c1/0x6c0
>        map_update_elem+0x623/0x910
>        __sys_bpf+0x90c/0x49a0
>        ...
>
> other info that might help us debug this:
>
>  Possible unsafe locking scenario:
>
>        CPU0                    CPU1
>        ----                    ----
>   lock(&trie->lock);
>                                lock(&n->list_lock);
>                                lock(&trie->lock);
>   lock(&n->list_lock);
>
>  *** DEADLOCK ***
>
> [1]: https://syzkaller.appspot.com/bug?extid=9045c0a3d5a7f1b119f7
>
> A bpf program attached to trace_contention_end() triggers after
> acquiring &n->list_lock. The program invokes trie_delete_elem(), which
> then acquires trie->lock. However, it is possible that another
> process is invoking trie_update_elem(). trie_update_elem() will acquire
> trie->lock first, then invoke kmalloc_node(). kmalloc_node() may invoke
> get_partial_node() and try to acquire &n->list_lock (not necessarily the
> same lock object). Therefore, lockdep warns about the circular locking
> dependency.
>
> Invoking kmalloc() before acquiring trie->lock could fix the warning.
> However, since BPF programs call be invoked from any context (e.g.,
> through kprobe/tracepoint/fentry), there may still be lock ordering
> problems for internal locks in kmalloc() or trie->lock itself.
>
> To eliminate these potential lock ordering problems with kmalloc()'s
> internal locks, replacing kmalloc()/kfree()/kfree_rcu() with equivalent
> BPF memory allocator APIs that can be invoked in any context. The lock
> ordering problems with trie->lock (e.g., reentrance) will be handled
> separately.
>
> Three aspects of this change require explanation:
>
> 1. Intermediate and leaf nodes are allocated from the same allocator.
> Since the value size of LPM trie is usually small, using a single
> alocator reduces the memory overhead of the BPF memory allocator.
>
> 2. Leaf nodes are allocated before disabling IRQs. This handles cases
> where leaf_size is large (e.g., > 4KB - 8) and updates require
> intermediate node allocation. If leaf nodes were allocated in
> IRQ-disabled region, the free objects in BPF memory allocator would not
> be refilled timely and the intermediate node allocation may fail.
>
> 3. Paired migrate_{disable|enable}() calls for node alloc and free. The
> BPF memory allocator uses per-CPU struct internally, these paired calls
> are necessary to guarantee correctness.
>
> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
> Signed-off-by: Hou Tao <houtao1@huawei.com>
> ---
>  kernel/bpf/lpm_trie.c | 71 +++++++++++++++++++++++++++++--------------
>  1 file changed, 48 insertions(+), 23 deletions(-)
>
> diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
> index 9ba6ae145239..f850360e75ce 100644
> --- a/kernel/bpf/lpm_trie.c
> +++ b/kernel/bpf/lpm_trie.c
> @@ -15,6 +15,7 @@
>  #include <net/ipv6.h>
>  #include <uapi/linux/btf.h>
>  #include <linux/btf_ids.h>
> +#include <linux/bpf_mem_alloc.h>
>
>  /* Intermediate node */
>  #define LPM_TREE_NODE_FLAG_IM BIT(0)
> @@ -22,7 +23,6 @@
>  struct lpm_trie_node;
>
>  struct lpm_trie_node {
> -       struct rcu_head rcu;
>         struct lpm_trie_node __rcu      *child[2];
>         u32                             prefixlen;
>         u32                             flags;
> @@ -32,6 +32,7 @@ struct lpm_trie_node {
>  struct lpm_trie {
>         struct bpf_map                  map;
>         struct lpm_trie_node __rcu      *root;
> +       struct bpf_mem_alloc            ma;
>         size_t                          n_entries;
>         size_t                          max_prefixlen;
>         size_t                          data_size;
> @@ -287,17 +288,18 @@ static void *trie_lookup_elem(struct bpf_map *map, void *_key)

Hey Hao,

We recently got a warning from trie_lookup_elem() triggered by

rcu_dereference_check(trie->root, rcu_read_lock_bh_held())

check in trie_lookup_elem, when LPM trie map was used from a sleepable
BPF program.

It seems like it can be just converted to bpf_rcu_lock_held(), because
with your switch to bpf_mem_alloc all the nodes are now both RCU and
RCU Tasks Trace protected, is my thinking correct?

Can you please double check? Thanks!

[...]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie
  2025-09-19 21:28   ` Andrii Nakryiko
@ 2025-09-23  1:33     ` Hou Tao
  2025-09-24 23:31       ` Andrii Nakryiko
  0 siblings, 1 reply; 17+ messages in thread
From: Hou Tao @ 2025-09-23  1:33 UTC (permalink / raw)
  To: Andrii Nakryiko
  Cc: bpf, Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai



On 9/20/2025 5:28 AM, Andrii Nakryiko wrote:
> On Fri, Dec 6, 2024 at 2:54 AM Hou Tao <houtao@huaweicloud.com> wrote:
>> From: Hou Tao <houtao1@huawei.com>
>>
>> Multiple syzbot warnings have been reported. These warnings are mainly
>> about the lock order between trie->lock and kmalloc()'s internal lock.
>> See report [1] as an example:
>>
>> ======================================================
>> WARNING: possible circular locking dependency detected
>> 6.10.0-rc7-syzkaller-00003-g4376e966ecb7 #0 Not tainted
>> ------------------------------------------------------
>> syz.3.2069/15008 is trying to acquire lock:
>> ffff88801544e6d8 (&n->list_lock){-.-.}-{2:2}, at: get_partial_node ...
>>
>> but task is already holding lock:
>> ffff88802dcc89f8 (&trie->lock){-.-.}-{2:2}, at: trie_update_elem ...
>>
>> which lock already depends on the new lock.
>>
>> the existing dependency chain (in reverse order) is:
>>
>> -> #1 (&trie->lock){-.-.}-{2:2}:
>>        __raw_spin_lock_irqsave
>>        _raw_spin_lock_irqsave+0x3a/0x60
>>        trie_delete_elem+0xb0/0x820
>>        ___bpf_prog_run+0x3e51/0xabd0
>>        __bpf_prog_run32+0xc1/0x100
>>        bpf_dispatcher_nop_func
>>        ......
>>        bpf_trace_run2+0x231/0x590
>>        __bpf_trace_contention_end+0xca/0x110
>>        trace_contention_end.constprop.0+0xea/0x170
>>        __pv_queued_spin_lock_slowpath+0x28e/0xcc0
>>        pv_queued_spin_lock_slowpath
>>        queued_spin_lock_slowpath
>>        queued_spin_lock
>>        do_raw_spin_lock+0x210/0x2c0
>>        __raw_spin_lock_irqsave
>>        _raw_spin_lock_irqsave+0x42/0x60
>>        __put_partials+0xc3/0x170
>>        qlink_free
>>        qlist_free_all+0x4e/0x140
>>        kasan_quarantine_reduce+0x192/0x1e0
>>        __kasan_slab_alloc+0x69/0x90
>>        kasan_slab_alloc
>>        slab_post_alloc_hook
>>        slab_alloc_node
>>        kmem_cache_alloc_node_noprof+0x153/0x310
>>        __alloc_skb+0x2b1/0x380
>>        ......
>>
>> -> #0 (&n->list_lock){-.-.}-{2:2}:
>>        check_prev_add
>>        check_prevs_add
>>        validate_chain
>>        __lock_acquire+0x2478/0x3b30
>>        lock_acquire
>>        lock_acquire+0x1b1/0x560
>>        __raw_spin_lock_irqsave
>>        _raw_spin_lock_irqsave+0x3a/0x60
>>        get_partial_node.part.0+0x20/0x350
>>        get_partial_node
>>        get_partial
>>        ___slab_alloc+0x65b/0x1870
>>        __slab_alloc.constprop.0+0x56/0xb0
>>        __slab_alloc_node
>>        slab_alloc_node
>>        __do_kmalloc_node
>>        __kmalloc_node_noprof+0x35c/0x440
>>        kmalloc_node_noprof
>>        bpf_map_kmalloc_node+0x98/0x4a0
>>        lpm_trie_node_alloc
>>        trie_update_elem+0x1ef/0xe00
>>        bpf_map_update_value+0x2c1/0x6c0
>>        map_update_elem+0x623/0x910
>>        __sys_bpf+0x90c/0x49a0
>>        ...
>>
>> other info that might help us debug this:
>>
>>  Possible unsafe locking scenario:
>>
>>        CPU0                    CPU1
>>        ----                    ----
>>   lock(&trie->lock);
>>                                lock(&n->list_lock);
>>                                lock(&trie->lock);
>>   lock(&n->list_lock);
>>
>>  *** DEADLOCK ***
>>
>> [1]: https://syzkaller.appspot.com/bug?extid=9045c0a3d5a7f1b119f7
>>
>> A bpf program attached to trace_contention_end() triggers after
>> acquiring &n->list_lock. The program invokes trie_delete_elem(), which
>> then acquires trie->lock. However, it is possible that another
>> process is invoking trie_update_elem(). trie_update_elem() will acquire
>> trie->lock first, then invoke kmalloc_node(). kmalloc_node() may invoke
>> get_partial_node() and try to acquire &n->list_lock (not necessarily the
>> same lock object). Therefore, lockdep warns about the circular locking
>> dependency.
>>
>> Invoking kmalloc() before acquiring trie->lock could fix the warning.
>> However, since BPF programs call be invoked from any context (e.g.,
>> through kprobe/tracepoint/fentry), there may still be lock ordering
>> problems for internal locks in kmalloc() or trie->lock itself.
>>
>> To eliminate these potential lock ordering problems with kmalloc()'s
>> internal locks, replacing kmalloc()/kfree()/kfree_rcu() with equivalent
>> BPF memory allocator APIs that can be invoked in any context. The lock
>> ordering problems with trie->lock (e.g., reentrance) will be handled
>> separately.
>>
>> Three aspects of this change require explanation:
>>
>> 1. Intermediate and leaf nodes are allocated from the same allocator.
>> Since the value size of LPM trie is usually small, using a single
>> alocator reduces the memory overhead of the BPF memory allocator.
>>
>> 2. Leaf nodes are allocated before disabling IRQs. This handles cases
>> where leaf_size is large (e.g., > 4KB - 8) and updates require
>> intermediate node allocation. If leaf nodes were allocated in
>> IRQ-disabled region, the free objects in BPF memory allocator would not
>> be refilled timely and the intermediate node allocation may fail.
>>
>> 3. Paired migrate_{disable|enable}() calls for node alloc and free. The
>> BPF memory allocator uses per-CPU struct internally, these paired calls
>> are necessary to guarantee correctness.
>>
>> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
>> Signed-off-by: Hou Tao <houtao1@huawei.com>
>> ---
>>  kernel/bpf/lpm_trie.c | 71 +++++++++++++++++++++++++++++--------------
>>  1 file changed, 48 insertions(+), 23 deletions(-)
>>
>> diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
>> index 9ba6ae145239..f850360e75ce 100644
>> --- a/kernel/bpf/lpm_trie.c
>> +++ b/kernel/bpf/lpm_trie.c
>> @@ -15,6 +15,7 @@
>>  #include <net/ipv6.h>
>>  #include <uapi/linux/btf.h>
>>  #include <linux/btf_ids.h>
>> +#include <linux/bpf_mem_alloc.h>
>>
>>  /* Intermediate node */
>>  #define LPM_TREE_NODE_FLAG_IM BIT(0)
>> @@ -22,7 +23,6 @@
>>  struct lpm_trie_node;
>>
>>  struct lpm_trie_node {
>> -       struct rcu_head rcu;
>>         struct lpm_trie_node __rcu      *child[2];
>>         u32                             prefixlen;
>>         u32                             flags;
>> @@ -32,6 +32,7 @@ struct lpm_trie_node {
>>  struct lpm_trie {
>>         struct bpf_map                  map;
>>         struct lpm_trie_node __rcu      *root;
>> +       struct bpf_mem_alloc            ma;
>>         size_t                          n_entries;
>>         size_t                          max_prefixlen;
>>         size_t                          data_size;
>> @@ -287,17 +288,18 @@ static void *trie_lookup_elem(struct bpf_map *map, void *_key)
> Hey Hao,

Hi Andrii,

Actually my name is Hou Tao :)
>
> We recently got a warning from trie_lookup_elem() triggered by
>
> rcu_dereference_check(trie->root, rcu_read_lock_bh_held())
>
> check in trie_lookup_elem, when LPM trie map was used from a sleepable
> BPF program.
>
> It seems like it can be just converted to bpf_rcu_lock_held(), because
> with your switch to bpf_mem_alloc all the nodes are now both RCU and
> RCU Tasks Trace protected, is my thinking correct?
>
> Can you please double check? Thanks!

No. Although the node is freed after one RCU GP and one RCU Task Trace
GP, the reuse of node happens after one RCU GP. Therefore, for the
sleepable program when it looks up the trie, it may find and try to read
a reused node, the returned result will be unexpected due to the reuse.

>
> [...]


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie
  2025-09-23  1:33     ` Hou Tao
@ 2025-09-24 23:31       ` Andrii Nakryiko
  2025-10-10 17:04         ` Andrii Nakryiko
  0 siblings, 1 reply; 17+ messages in thread
From: Andrii Nakryiko @ 2025-09-24 23:31 UTC (permalink / raw)
  To: Hou Tao
  Cc: bpf, Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

On Mon, Sep 22, 2025 at 6:33 PM Hou Tao <houtao@huaweicloud.com> wrote:
>
>
>
> On 9/20/2025 5:28 AM, Andrii Nakryiko wrote:
> > On Fri, Dec 6, 2024 at 2:54 AM Hou Tao <houtao@huaweicloud.com> wrote:
> >> From: Hou Tao <houtao1@huawei.com>
> >>
> >> Multiple syzbot warnings have been reported. These warnings are mainly
> >> about the lock order between trie->lock and kmalloc()'s internal lock.
> >> See report [1] as an example:
> >>
> >> ======================================================
> >> WARNING: possible circular locking dependency detected
> >> 6.10.0-rc7-syzkaller-00003-g4376e966ecb7 #0 Not tainted
> >> ------------------------------------------------------
> >> syz.3.2069/15008 is trying to acquire lock:
> >> ffff88801544e6d8 (&n->list_lock){-.-.}-{2:2}, at: get_partial_node ...
> >>
> >> but task is already holding lock:
> >> ffff88802dcc89f8 (&trie->lock){-.-.}-{2:2}, at: trie_update_elem ...
> >>
> >> which lock already depends on the new lock.
> >>
> >> the existing dependency chain (in reverse order) is:
> >>
> >> -> #1 (&trie->lock){-.-.}-{2:2}:
> >>        __raw_spin_lock_irqsave
> >>        _raw_spin_lock_irqsave+0x3a/0x60
> >>        trie_delete_elem+0xb0/0x820
> >>        ___bpf_prog_run+0x3e51/0xabd0
> >>        __bpf_prog_run32+0xc1/0x100
> >>        bpf_dispatcher_nop_func
> >>        ......
> >>        bpf_trace_run2+0x231/0x590
> >>        __bpf_trace_contention_end+0xca/0x110
> >>        trace_contention_end.constprop.0+0xea/0x170
> >>        __pv_queued_spin_lock_slowpath+0x28e/0xcc0
> >>        pv_queued_spin_lock_slowpath
> >>        queued_spin_lock_slowpath
> >>        queued_spin_lock
> >>        do_raw_spin_lock+0x210/0x2c0
> >>        __raw_spin_lock_irqsave
> >>        _raw_spin_lock_irqsave+0x42/0x60
> >>        __put_partials+0xc3/0x170
> >>        qlink_free
> >>        qlist_free_all+0x4e/0x140
> >>        kasan_quarantine_reduce+0x192/0x1e0
> >>        __kasan_slab_alloc+0x69/0x90
> >>        kasan_slab_alloc
> >>        slab_post_alloc_hook
> >>        slab_alloc_node
> >>        kmem_cache_alloc_node_noprof+0x153/0x310
> >>        __alloc_skb+0x2b1/0x380
> >>        ......
> >>
> >> -> #0 (&n->list_lock){-.-.}-{2:2}:
> >>        check_prev_add
> >>        check_prevs_add
> >>        validate_chain
> >>        __lock_acquire+0x2478/0x3b30
> >>        lock_acquire
> >>        lock_acquire+0x1b1/0x560
> >>        __raw_spin_lock_irqsave
> >>        _raw_spin_lock_irqsave+0x3a/0x60
> >>        get_partial_node.part.0+0x20/0x350
> >>        get_partial_node
> >>        get_partial
> >>        ___slab_alloc+0x65b/0x1870
> >>        __slab_alloc.constprop.0+0x56/0xb0
> >>        __slab_alloc_node
> >>        slab_alloc_node
> >>        __do_kmalloc_node
> >>        __kmalloc_node_noprof+0x35c/0x440
> >>        kmalloc_node_noprof
> >>        bpf_map_kmalloc_node+0x98/0x4a0
> >>        lpm_trie_node_alloc
> >>        trie_update_elem+0x1ef/0xe00
> >>        bpf_map_update_value+0x2c1/0x6c0
> >>        map_update_elem+0x623/0x910
> >>        __sys_bpf+0x90c/0x49a0
> >>        ...
> >>
> >> other info that might help us debug this:
> >>
> >>  Possible unsafe locking scenario:
> >>
> >>        CPU0                    CPU1
> >>        ----                    ----
> >>   lock(&trie->lock);
> >>                                lock(&n->list_lock);
> >>                                lock(&trie->lock);
> >>   lock(&n->list_lock);
> >>
> >>  *** DEADLOCK ***
> >>
> >> [1]: https://syzkaller.appspot.com/bug?extid=9045c0a3d5a7f1b119f7
> >>
> >> A bpf program attached to trace_contention_end() triggers after
> >> acquiring &n->list_lock. The program invokes trie_delete_elem(), which
> >> then acquires trie->lock. However, it is possible that another
> >> process is invoking trie_update_elem(). trie_update_elem() will acquire
> >> trie->lock first, then invoke kmalloc_node(). kmalloc_node() may invoke
> >> get_partial_node() and try to acquire &n->list_lock (not necessarily the
> >> same lock object). Therefore, lockdep warns about the circular locking
> >> dependency.
> >>
> >> Invoking kmalloc() before acquiring trie->lock could fix the warning.
> >> However, since BPF programs call be invoked from any context (e.g.,
> >> through kprobe/tracepoint/fentry), there may still be lock ordering
> >> problems for internal locks in kmalloc() or trie->lock itself.
> >>
> >> To eliminate these potential lock ordering problems with kmalloc()'s
> >> internal locks, replacing kmalloc()/kfree()/kfree_rcu() with equivalent
> >> BPF memory allocator APIs that can be invoked in any context. The lock
> >> ordering problems with trie->lock (e.g., reentrance) will be handled
> >> separately.
> >>
> >> Three aspects of this change require explanation:
> >>
> >> 1. Intermediate and leaf nodes are allocated from the same allocator.
> >> Since the value size of LPM trie is usually small, using a single
> >> alocator reduces the memory overhead of the BPF memory allocator.
> >>
> >> 2. Leaf nodes are allocated before disabling IRQs. This handles cases
> >> where leaf_size is large (e.g., > 4KB - 8) and updates require
> >> intermediate node allocation. If leaf nodes were allocated in
> >> IRQ-disabled region, the free objects in BPF memory allocator would not
> >> be refilled timely and the intermediate node allocation may fail.
> >>
> >> 3. Paired migrate_{disable|enable}() calls for node alloc and free. The
> >> BPF memory allocator uses per-CPU struct internally, these paired calls
> >> are necessary to guarantee correctness.
> >>
> >> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
> >> Signed-off-by: Hou Tao <houtao1@huawei.com>
> >> ---
> >>  kernel/bpf/lpm_trie.c | 71 +++++++++++++++++++++++++++++--------------
> >>  1 file changed, 48 insertions(+), 23 deletions(-)
> >>
> >> diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
> >> index 9ba6ae145239..f850360e75ce 100644
> >> --- a/kernel/bpf/lpm_trie.c
> >> +++ b/kernel/bpf/lpm_trie.c
> >> @@ -15,6 +15,7 @@
> >>  #include <net/ipv6.h>
> >>  #include <uapi/linux/btf.h>
> >>  #include <linux/btf_ids.h>
> >> +#include <linux/bpf_mem_alloc.h>
> >>
> >>  /* Intermediate node */
> >>  #define LPM_TREE_NODE_FLAG_IM BIT(0)
> >> @@ -22,7 +23,6 @@
> >>  struct lpm_trie_node;
> >>
> >>  struct lpm_trie_node {
> >> -       struct rcu_head rcu;
> >>         struct lpm_trie_node __rcu      *child[2];
> >>         u32                             prefixlen;
> >>         u32                             flags;
> >> @@ -32,6 +32,7 @@ struct lpm_trie_node {
> >>  struct lpm_trie {
> >>         struct bpf_map                  map;
> >>         struct lpm_trie_node __rcu      *root;
> >> +       struct bpf_mem_alloc            ma;
> >>         size_t                          n_entries;
> >>         size_t                          max_prefixlen;
> >>         size_t                          data_size;
> >> @@ -287,17 +288,18 @@ static void *trie_lookup_elem(struct bpf_map *map, void *_key)
> > Hey Hao,
>
> Hi Andrii,
>
> Actually my name is Hou Tao :)

Oops, I'm sorry for butchering your name, Hou!

> >
> > We recently got a warning from trie_lookup_elem() triggered by
> >
> > rcu_dereference_check(trie->root, rcu_read_lock_bh_held())
> >
> > check in trie_lookup_elem, when LPM trie map was used from a sleepable
> > BPF program.
> >
> > It seems like it can be just converted to bpf_rcu_lock_held(), because
> > with your switch to bpf_mem_alloc all the nodes are now both RCU and
> > RCU Tasks Trace protected, is my thinking correct?
> >
> > Can you please double check? Thanks!
>
> No. Although the node is freed after one RCU GP and one RCU Task Trace
> GP, the reuse of node happens after one RCU GP. Therefore, for the
> sleepable program when it looks up the trie, it may find and try to read
> a reused node, the returned result will be unexpected due to the reuse.
>

That's two different things, no? Because we do lookup without lock,
it's possible for (non-sleepable or sleepable, doesn't matter) BPF
program to update/delete trie node while some other BPF program looks
it up without locking. I don't think anything fundamentally changes
here. We have similar behavior for other BPF maps (hashmap, for
example), regardless of sleepable or not.

But the problem here is specifically about overly eager
rcu_dereference_check check. That memory is not going to be freed, so
it's "safe" to dereference it, even if it might be reused for another
node.

Either that, or we need to disable LPM trie map for sleepable until we
somehow fix this memory reuse, no?

> >
> > [...]
>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie
  2025-09-24 23:31       ` Andrii Nakryiko
@ 2025-10-10 17:04         ` Andrii Nakryiko
  2025-10-13  0:59           ` Hou Tao
  0 siblings, 1 reply; 17+ messages in thread
From: Andrii Nakryiko @ 2025-10-10 17:04 UTC (permalink / raw)
  To: Hou Tao
  Cc: bpf, Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

On Wed, Sep 24, 2025 at 4:31 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Mon, Sep 22, 2025 at 6:33 PM Hou Tao <houtao@huaweicloud.com> wrote:
> >
> >
> >
> > On 9/20/2025 5:28 AM, Andrii Nakryiko wrote:
> > > On Fri, Dec 6, 2024 at 2:54 AM Hou Tao <houtao@huaweicloud.com> wrote:
> > >> From: Hou Tao <houtao1@huawei.com>
> > >>
> > >> Multiple syzbot warnings have been reported. These warnings are mainly
> > >> about the lock order between trie->lock and kmalloc()'s internal lock.
> > >> See report [1] as an example:
> > >>
> > >> ======================================================
> > >> WARNING: possible circular locking dependency detected
> > >> 6.10.0-rc7-syzkaller-00003-g4376e966ecb7 #0 Not tainted
> > >> ------------------------------------------------------
> > >> syz.3.2069/15008 is trying to acquire lock:
> > >> ffff88801544e6d8 (&n->list_lock){-.-.}-{2:2}, at: get_partial_node ...
> > >>
> > >> but task is already holding lock:
> > >> ffff88802dcc89f8 (&trie->lock){-.-.}-{2:2}, at: trie_update_elem ...
> > >>
> > >> which lock already depends on the new lock.
> > >>
> > >> the existing dependency chain (in reverse order) is:
> > >>
> > >> -> #1 (&trie->lock){-.-.}-{2:2}:
> > >>        __raw_spin_lock_irqsave
> > >>        _raw_spin_lock_irqsave+0x3a/0x60
> > >>        trie_delete_elem+0xb0/0x820
> > >>        ___bpf_prog_run+0x3e51/0xabd0
> > >>        __bpf_prog_run32+0xc1/0x100
> > >>        bpf_dispatcher_nop_func
> > >>        ......
> > >>        bpf_trace_run2+0x231/0x590
> > >>        __bpf_trace_contention_end+0xca/0x110
> > >>        trace_contention_end.constprop.0+0xea/0x170
> > >>        __pv_queued_spin_lock_slowpath+0x28e/0xcc0
> > >>        pv_queued_spin_lock_slowpath
> > >>        queued_spin_lock_slowpath
> > >>        queued_spin_lock
> > >>        do_raw_spin_lock+0x210/0x2c0
> > >>        __raw_spin_lock_irqsave
> > >>        _raw_spin_lock_irqsave+0x42/0x60
> > >>        __put_partials+0xc3/0x170
> > >>        qlink_free
> > >>        qlist_free_all+0x4e/0x140
> > >>        kasan_quarantine_reduce+0x192/0x1e0
> > >>        __kasan_slab_alloc+0x69/0x90
> > >>        kasan_slab_alloc
> > >>        slab_post_alloc_hook
> > >>        slab_alloc_node
> > >>        kmem_cache_alloc_node_noprof+0x153/0x310
> > >>        __alloc_skb+0x2b1/0x380
> > >>        ......
> > >>
> > >> -> #0 (&n->list_lock){-.-.}-{2:2}:
> > >>        check_prev_add
> > >>        check_prevs_add
> > >>        validate_chain
> > >>        __lock_acquire+0x2478/0x3b30
> > >>        lock_acquire
> > >>        lock_acquire+0x1b1/0x560
> > >>        __raw_spin_lock_irqsave
> > >>        _raw_spin_lock_irqsave+0x3a/0x60
> > >>        get_partial_node.part.0+0x20/0x350
> > >>        get_partial_node
> > >>        get_partial
> > >>        ___slab_alloc+0x65b/0x1870
> > >>        __slab_alloc.constprop.0+0x56/0xb0
> > >>        __slab_alloc_node
> > >>        slab_alloc_node
> > >>        __do_kmalloc_node
> > >>        __kmalloc_node_noprof+0x35c/0x440
> > >>        kmalloc_node_noprof
> > >>        bpf_map_kmalloc_node+0x98/0x4a0
> > >>        lpm_trie_node_alloc
> > >>        trie_update_elem+0x1ef/0xe00
> > >>        bpf_map_update_value+0x2c1/0x6c0
> > >>        map_update_elem+0x623/0x910
> > >>        __sys_bpf+0x90c/0x49a0
> > >>        ...
> > >>
> > >> other info that might help us debug this:
> > >>
> > >>  Possible unsafe locking scenario:
> > >>
> > >>        CPU0                    CPU1
> > >>        ----                    ----
> > >>   lock(&trie->lock);
> > >>                                lock(&n->list_lock);
> > >>                                lock(&trie->lock);
> > >>   lock(&n->list_lock);
> > >>
> > >>  *** DEADLOCK ***
> > >>
> > >> [1]: https://syzkaller.appspot.com/bug?extid=9045c0a3d5a7f1b119f7
> > >>
> > >> A bpf program attached to trace_contention_end() triggers after
> > >> acquiring &n->list_lock. The program invokes trie_delete_elem(), which
> > >> then acquires trie->lock. However, it is possible that another
> > >> process is invoking trie_update_elem(). trie_update_elem() will acquire
> > >> trie->lock first, then invoke kmalloc_node(). kmalloc_node() may invoke
> > >> get_partial_node() and try to acquire &n->list_lock (not necessarily the
> > >> same lock object). Therefore, lockdep warns about the circular locking
> > >> dependency.
> > >>
> > >> Invoking kmalloc() before acquiring trie->lock could fix the warning.
> > >> However, since BPF programs call be invoked from any context (e.g.,
> > >> through kprobe/tracepoint/fentry), there may still be lock ordering
> > >> problems for internal locks in kmalloc() or trie->lock itself.
> > >>
> > >> To eliminate these potential lock ordering problems with kmalloc()'s
> > >> internal locks, replacing kmalloc()/kfree()/kfree_rcu() with equivalent
> > >> BPF memory allocator APIs that can be invoked in any context. The lock
> > >> ordering problems with trie->lock (e.g., reentrance) will be handled
> > >> separately.
> > >>
> > >> Three aspects of this change require explanation:
> > >>
> > >> 1. Intermediate and leaf nodes are allocated from the same allocator.
> > >> Since the value size of LPM trie is usually small, using a single
> > >> alocator reduces the memory overhead of the BPF memory allocator.
> > >>
> > >> 2. Leaf nodes are allocated before disabling IRQs. This handles cases
> > >> where leaf_size is large (e.g., > 4KB - 8) and updates require
> > >> intermediate node allocation. If leaf nodes were allocated in
> > >> IRQ-disabled region, the free objects in BPF memory allocator would not
> > >> be refilled timely and the intermediate node allocation may fail.
> > >>
> > >> 3. Paired migrate_{disable|enable}() calls for node alloc and free. The
> > >> BPF memory allocator uses per-CPU struct internally, these paired calls
> > >> are necessary to guarantee correctness.
> > >>
> > >> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
> > >> Signed-off-by: Hou Tao <houtao1@huawei.com>
> > >> ---
> > >>  kernel/bpf/lpm_trie.c | 71 +++++++++++++++++++++++++++++--------------
> > >>  1 file changed, 48 insertions(+), 23 deletions(-)
> > >>
> > >> diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
> > >> index 9ba6ae145239..f850360e75ce 100644
> > >> --- a/kernel/bpf/lpm_trie.c
> > >> +++ b/kernel/bpf/lpm_trie.c
> > >> @@ -15,6 +15,7 @@
> > >>  #include <net/ipv6.h>
> > >>  #include <uapi/linux/btf.h>
> > >>  #include <linux/btf_ids.h>
> > >> +#include <linux/bpf_mem_alloc.h>
> > >>
> > >>  /* Intermediate node */
> > >>  #define LPM_TREE_NODE_FLAG_IM BIT(0)
> > >> @@ -22,7 +23,6 @@
> > >>  struct lpm_trie_node;
> > >>
> > >>  struct lpm_trie_node {
> > >> -       struct rcu_head rcu;
> > >>         struct lpm_trie_node __rcu      *child[2];
> > >>         u32                             prefixlen;
> > >>         u32                             flags;
> > >> @@ -32,6 +32,7 @@ struct lpm_trie_node {
> > >>  struct lpm_trie {
> > >>         struct bpf_map                  map;
> > >>         struct lpm_trie_node __rcu      *root;
> > >> +       struct bpf_mem_alloc            ma;
> > >>         size_t                          n_entries;
> > >>         size_t                          max_prefixlen;
> > >>         size_t                          data_size;
> > >> @@ -287,17 +288,18 @@ static void *trie_lookup_elem(struct bpf_map *map, void *_key)
> > > Hey Hao,
> >
> > Hi Andrii,
> >
> > Actually my name is Hou Tao :)
>
> Oops, I'm sorry for butchering your name, Hou!
>
> > >
> > > We recently got a warning from trie_lookup_elem() triggered by
> > >
> > > rcu_dereference_check(trie->root, rcu_read_lock_bh_held())
> > >
> > > check in trie_lookup_elem, when LPM trie map was used from a sleepable
> > > BPF program.
> > >
> > > It seems like it can be just converted to bpf_rcu_lock_held(), because
> > > with your switch to bpf_mem_alloc all the nodes are now both RCU and
> > > RCU Tasks Trace protected, is my thinking correct?
> > >
> > > Can you please double check? Thanks!
> >
> > No. Although the node is freed after one RCU GP and one RCU Task Trace
> > GP, the reuse of node happens after one RCU GP. Therefore, for the
> > sleepable program when it looks up the trie, it may find and try to read
> > a reused node, the returned result will be unexpected due to the reuse.
> >
>
> That's two different things, no? Because we do lookup without lock,
> it's possible for (non-sleepable or sleepable, doesn't matter) BPF
> program to update/delete trie node while some other BPF program looks
> it up without locking. I don't think anything fundamentally changes
> here. We have similar behavior for other BPF maps (hashmap, for
> example), regardless of sleepable or not.
>
> But the problem here is specifically about overly eager
> rcu_dereference_check check. That memory is not going to be freed, so
> it's "safe" to dereference it, even if it might be reused for another
> node.
>
> Either that, or we need to disable LPM trie map for sleepable until we
> somehow fix this memory reuse, no?

Hey Hou,

So, it's actually more interesting than what I initially thought.
LPM_TRIE is not currently allowed for sleepable programs, but we have
a bug in the verifier where you can sneak in such map into a sleepable
program through map-in-map. It seems like we are missing checking the
inner map for sleepable compatibility.

The question for you is how hard is it to make LPM_TRIE support
sleepable program? You mentioned some unintended memory reused, can
that be fixed/changed? Can you please take a look? Thanks!

>
> > >
> > > [...]
> >

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie
  2025-10-10 17:04         ` Andrii Nakryiko
@ 2025-10-13  0:59           ` Hou Tao
  2025-10-13 23:18             ` Andrii Nakryiko
  0 siblings, 1 reply; 17+ messages in thread
From: Hou Tao @ 2025-10-13  0:59 UTC (permalink / raw)
  To: Andrii Nakryiko
  Cc: bpf, Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

Hi Andrii,

On 10/11/2025 1:04 AM, Andrii Nakryiko wrote:
> On Wed, Sep 24, 2025 at 4:31 PM Andrii Nakryiko
> <andrii.nakryiko@gmail.com> wrote:
>> On Mon, Sep 22, 2025 at 6:33 PM Hou Tao <houtao@huaweicloud.com> wrote:
>>>
>>>
>>> On 9/20/2025 5:28 AM, Andrii Nakryiko wrote:
>>>> On Fri, Dec 6, 2024 at 2:54 AM Hou Tao <houtao@huaweicloud.com> wrote:
>>>>> From: Hou Tao <houtao1@huawei.com>
>>>>>
>>>>> Multiple syzbot warnings have been reported. These warnings are mainly
>>>>> about the lock order between trie->lock and kmalloc()'s internal lock.
>>>>> See report [1] as an example:
>>>>>
>>>>> ======================================================
>>>>> WARNING: possible circular locking dependency detected
>>>>> 6.10.0-rc7-syzkaller-00003-g4376e966ecb7 #0 Not tainted
>>>>> ------------------------------------------------------
>>>>> syz.3.2069/15008 is trying to acquire lock:
>>>>> ffff88801544e6d8 (&n->list_lock){-.-.}-{2:2}, at: get_partial_node ...
>>>>>
>>>>> but task is already holding lock:
>>>>> ffff88802dcc89f8 (&trie->lock){-.-.}-{2:2}, at: trie_update_elem ...
>>>>>
>>>>> which lock already depends on the new lock.
>>>>>
>>>>> the existing dependency chain (in reverse order) is:
>>>>>
>>>>> -> #1 (&trie->lock){-.-.}-{2:2}:
>>>>>        __raw_spin_lock_irqsave
>>>>>        _raw_spin_lock_irqsave+0x3a/0x60
>>>>>        trie_delete_elem+0xb0/0x820
>>>>>        ___bpf_prog_run+0x3e51/0xabd0
>>>>>        __bpf_prog_run32+0xc1/0x100
>>>>>        bpf_dispatcher_nop_func
>>>>>        ......
>>>>>        bpf_trace_run2+0x231/0x590
>>>>>        __bpf_trace_contention_end+0xca/0x110
>>>>>        trace_contention_end.constprop.0+0xea/0x170
>>>>>        __pv_queued_spin_lock_slowpath+0x28e/0xcc0
>>>>>        pv_queued_spin_lock_slowpath
>>>>>        queued_spin_lock_slowpath
>>>>>        queued_spin_lock
>>>>>        do_raw_spin_lock+0x210/0x2c0
>>>>>        __raw_spin_lock_irqsave
>>>>>        _raw_spin_lock_irqsave+0x42/0x60
>>>>>        __put_partials+0xc3/0x170
>>>>>        qlink_free
>>>>>        qlist_free_all+0x4e/0x140
>>>>>        kasan_quarantine_reduce+0x192/0x1e0
>>>>>        __kasan_slab_alloc+0x69/0x90
>>>>>        kasan_slab_alloc
>>>>>        slab_post_alloc_hook
>>>>>        slab_alloc_node
>>>>>        kmem_cache_alloc_node_noprof+0x153/0x310
>>>>>        __alloc_skb+0x2b1/0x380
>>>>>        ......
>>>>>
>>>>> -> #0 (&n->list_lock){-.-.}-{2:2}:
>>>>>        check_prev_add
>>>>>        check_prevs_add
>>>>>        validate_chain
>>>>>        __lock_acquire+0x2478/0x3b30
>>>>>        lock_acquire
>>>>>        lock_acquire+0x1b1/0x560
>>>>>        __raw_spin_lock_irqsave
>>>>>        _raw_spin_lock_irqsave+0x3a/0x60
>>>>>        get_partial_node.part.0+0x20/0x350
>>>>>        get_partial_node
>>>>>        get_partial
>>>>>        ___slab_alloc+0x65b/0x1870
>>>>>        __slab_alloc.constprop.0+0x56/0xb0
>>>>>        __slab_alloc_node
>>>>>        slab_alloc_node
>>>>>        __do_kmalloc_node
>>>>>        __kmalloc_node_noprof+0x35c/0x440
>>>>>        kmalloc_node_noprof
>>>>>        bpf_map_kmalloc_node+0x98/0x4a0
>>>>>        lpm_trie_node_alloc
>>>>>        trie_update_elem+0x1ef/0xe00
>>>>>        bpf_map_update_value+0x2c1/0x6c0
>>>>>        map_update_elem+0x623/0x910
>>>>>        __sys_bpf+0x90c/0x49a0
>>>>>        ...
>>>>>
>>>>> other info that might help us debug this:
>>>>>
>>>>>  Possible unsafe locking scenario:
>>>>>
>>>>>        CPU0                    CPU1
>>>>>        ----                    ----
>>>>>   lock(&trie->lock);
>>>>>                                lock(&n->list_lock);
>>>>>                                lock(&trie->lock);
>>>>>   lock(&n->list_lock);
>>>>>
>>>>>  *** DEADLOCK ***
>>>>>
>>>>> [1]: https://syzkaller.appspot.com/bug?extid=9045c0a3d5a7f1b119f7
>>>>>
>>>>> A bpf program attached to trace_contention_end() triggers after
>>>>> acquiring &n->list_lock. The program invokes trie_delete_elem(), which
>>>>> then acquires trie->lock. However, it is possible that another
>>>>> process is invoking trie_update_elem(). trie_update_elem() will acquire
>>>>> trie->lock first, then invoke kmalloc_node(). kmalloc_node() may invoke
>>>>> get_partial_node() and try to acquire &n->list_lock (not necessarily the
>>>>> same lock object). Therefore, lockdep warns about the circular locking
>>>>> dependency.
>>>>>
>>>>> Invoking kmalloc() before acquiring trie->lock could fix the warning.
>>>>> However, since BPF programs call be invoked from any context (e.g.,
>>>>> through kprobe/tracepoint/fentry), there may still be lock ordering
>>>>> problems for internal locks in kmalloc() or trie->lock itself.
>>>>>
>>>>> To eliminate these potential lock ordering problems with kmalloc()'s
>>>>> internal locks, replacing kmalloc()/kfree()/kfree_rcu() with equivalent
>>>>> BPF memory allocator APIs that can be invoked in any context. The lock
>>>>> ordering problems with trie->lock (e.g., reentrance) will be handled
>>>>> separately.
>>>>>
>>>>> Three aspects of this change require explanation:
>>>>>
>>>>> 1. Intermediate and leaf nodes are allocated from the same allocator.
>>>>> Since the value size of LPM trie is usually small, using a single
>>>>> alocator reduces the memory overhead of the BPF memory allocator.
>>>>>
>>>>> 2. Leaf nodes are allocated before disabling IRQs. This handles cases
>>>>> where leaf_size is large (e.g., > 4KB - 8) and updates require
>>>>> intermediate node allocation. If leaf nodes were allocated in
>>>>> IRQ-disabled region, the free objects in BPF memory allocator would not
>>>>> be refilled timely and the intermediate node allocation may fail.
>>>>>
>>>>> 3. Paired migrate_{disable|enable}() calls for node alloc and free. The
>>>>> BPF memory allocator uses per-CPU struct internally, these paired calls
>>>>> are necessary to guarantee correctness.
>>>>>
>>>>> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
>>>>> Signed-off-by: Hou Tao <houtao1@huawei.com>
>>>>> ---
>>>>>  kernel/bpf/lpm_trie.c | 71 +++++++++++++++++++++++++++++--------------
>>>>>  1 file changed, 48 insertions(+), 23 deletions(-)
>>>>>
>>>>> diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
>>>>> index 9ba6ae145239..f850360e75ce 100644
>>>>> --- a/kernel/bpf/lpm_trie.c
>>>>> +++ b/kernel/bpf/lpm_trie.c
>>>>> @@ -15,6 +15,7 @@
>>>>>  #include <net/ipv6.h>
>>>>>  #include <uapi/linux/btf.h>
>>>>>  #include <linux/btf_ids.h>
>>>>> +#include <linux/bpf_mem_alloc.h>
>>>>>
>>>>>  /* Intermediate node */
>>>>>  #define LPM_TREE_NODE_FLAG_IM BIT(0)
>>>>> @@ -22,7 +23,6 @@
>>>>>  struct lpm_trie_node;
>>>>>
>>>>>  struct lpm_trie_node {
>>>>> -       struct rcu_head rcu;
>>>>>         struct lpm_trie_node __rcu      *child[2];
>>>>>         u32                             prefixlen;
>>>>>         u32                             flags;
>>>>> @@ -32,6 +32,7 @@ struct lpm_trie_node {
>>>>>  struct lpm_trie {
>>>>>         struct bpf_map                  map;
>>>>>         struct lpm_trie_node __rcu      *root;
>>>>> +       struct bpf_mem_alloc            ma;
>>>>>         size_t                          n_entries;
>>>>>         size_t                          max_prefixlen;
>>>>>         size_t                          data_size;
>>>>> @@ -287,17 +288,18 @@ static void *trie_lookup_elem(struct bpf_map *map, void *_key)
>>>> Hey Hao,
>>> Hi Andrii,
>>>
>>> Actually my name is Hou Tao :)
>> Oops, I'm sorry for butchering your name, Hou!
>>
>>>> We recently got a warning from trie_lookup_elem() triggered by
>>>>
>>>> rcu_dereference_check(trie->root, rcu_read_lock_bh_held())
>>>>
>>>> check in trie_lookup_elem, when LPM trie map was used from a sleepable
>>>> BPF program.
>>>>
>>>> It seems like it can be just converted to bpf_rcu_lock_held(), because
>>>> with your switch to bpf_mem_alloc all the nodes are now both RCU and
>>>> RCU Tasks Trace protected, is my thinking correct?
>>>>
>>>> Can you please double check? Thanks!
>>> No. Although the node is freed after one RCU GP and one RCU Task Trace
>>> GP, the reuse of node happens after one RCU GP. Therefore, for the
>>> sleepable program when it looks up the trie, it may find and try to read
>>> a reused node, the returned result will be unexpected due to the reuse.
>>>

Sorry for the delayed response.
>> That's two different things, no? Because we do lookup without lock,
>> it's possible for (non-sleepable or sleepable, doesn't matter) BPF
>> program to update/delete trie node while some other BPF program looks
>> it up without locking. I don't think anything fundamentally changes
>> here. We have similar behavior for other BPF maps (hashmap, for
>> example), regardless of sleepable or not.
>>
>> But the problem here is specifically about overly eager
>> rcu_dereference_check check. That memory is not going to be freed, so
>> it's "safe" to dereference it, even if it might be reused for another
>> node.
>>
>> Either that, or we need to disable LPM trie map for sleepable until we
>> somehow fix this memory reuse, no?
> Hey Hou,
>
> So, it's actually more interesting than what I initially thought.
> LPM_TRIE is not currently allowed for sleepable programs, but we have
> a bug in the verifier where you can sneak in such map into a sleepable
> program through map-in-map. It seems like we are missing checking the
> inner map for sleepable compatibility.
>
> The question for you is how hard is it to make LPM_TRIE support
> sleepable program? You mentioned some unintended memory reused, can
> that be fixed/changed? Can you please take a look? Thanks!

Instead of updating LPM_TRIE to handle memory reuse, why not use
bpf_rcu_read_lock/bpf_rcu_read_unlock to ensure the reuse will not
happen during the lookup ?
>
>>>> [...]
> .


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie
  2025-10-13  0:59           ` Hou Tao
@ 2025-10-13 23:18             ` Andrii Nakryiko
  0 siblings, 0 replies; 17+ messages in thread
From: Andrii Nakryiko @ 2025-10-13 23:18 UTC (permalink / raw)
  To: Hou Tao
  Cc: bpf, Martin KaFai Lau, Alexei Starovoitov, Andrii Nakryiko,
	Eduard Zingerman, Song Liu, Hao Luo, Yonghong Song,
	Daniel Borkmann, KP Singh, Stanislav Fomichev, Jiri Olsa,
	John Fastabend, Toke Høiland-Jørgensen,
	Sebastian Andrzej Siewior, Thomas Gleixner, Thomas Weißschuh,
	houtao1, xukuohai

On Sun, Oct 12, 2025 at 5:59 PM Hou Tao <houtao@huaweicloud.com> wrote:
>
> Hi Andrii,
>
> On 10/11/2025 1:04 AM, Andrii Nakryiko wrote:
> > On Wed, Sep 24, 2025 at 4:31 PM Andrii Nakryiko
> > <andrii.nakryiko@gmail.com> wrote:
> >> On Mon, Sep 22, 2025 at 6:33 PM Hou Tao <houtao@huaweicloud.com> wrote:
> >>>
> >>>
> >>> On 9/20/2025 5:28 AM, Andrii Nakryiko wrote:
> >>>> On Fri, Dec 6, 2024 at 2:54 AM Hou Tao <houtao@huaweicloud.com> wrote:
> >>>>> From: Hou Tao <houtao1@huawei.com>
> >>>>>
> >>>>> Multiple syzbot warnings have been reported. These warnings are mainly
> >>>>> about the lock order between trie->lock and kmalloc()'s internal lock.
> >>>>> See report [1] as an example:
> >>>>>
> >>>>> ======================================================
> >>>>> WARNING: possible circular locking dependency detected
> >>>>> 6.10.0-rc7-syzkaller-00003-g4376e966ecb7 #0 Not tainted
> >>>>> ------------------------------------------------------
> >>>>> syz.3.2069/15008 is trying to acquire lock:
> >>>>> ffff88801544e6d8 (&n->list_lock){-.-.}-{2:2}, at: get_partial_node ...
> >>>>>
> >>>>> but task is already holding lock:
> >>>>> ffff88802dcc89f8 (&trie->lock){-.-.}-{2:2}, at: trie_update_elem ...
> >>>>>
> >>>>> which lock already depends on the new lock.
> >>>>>
> >>>>> the existing dependency chain (in reverse order) is:
> >>>>>
> >>>>> -> #1 (&trie->lock){-.-.}-{2:2}:
> >>>>>        __raw_spin_lock_irqsave
> >>>>>        _raw_spin_lock_irqsave+0x3a/0x60
> >>>>>        trie_delete_elem+0xb0/0x820
> >>>>>        ___bpf_prog_run+0x3e51/0xabd0
> >>>>>        __bpf_prog_run32+0xc1/0x100
> >>>>>        bpf_dispatcher_nop_func
> >>>>>        ......
> >>>>>        bpf_trace_run2+0x231/0x590
> >>>>>        __bpf_trace_contention_end+0xca/0x110
> >>>>>        trace_contention_end.constprop.0+0xea/0x170
> >>>>>        __pv_queued_spin_lock_slowpath+0x28e/0xcc0
> >>>>>        pv_queued_spin_lock_slowpath
> >>>>>        queued_spin_lock_slowpath
> >>>>>        queued_spin_lock
> >>>>>        do_raw_spin_lock+0x210/0x2c0
> >>>>>        __raw_spin_lock_irqsave
> >>>>>        _raw_spin_lock_irqsave+0x42/0x60
> >>>>>        __put_partials+0xc3/0x170
> >>>>>        qlink_free
> >>>>>        qlist_free_all+0x4e/0x140
> >>>>>        kasan_quarantine_reduce+0x192/0x1e0
> >>>>>        __kasan_slab_alloc+0x69/0x90
> >>>>>        kasan_slab_alloc
> >>>>>        slab_post_alloc_hook
> >>>>>        slab_alloc_node
> >>>>>        kmem_cache_alloc_node_noprof+0x153/0x310
> >>>>>        __alloc_skb+0x2b1/0x380
> >>>>>        ......
> >>>>>
> >>>>> -> #0 (&n->list_lock){-.-.}-{2:2}:
> >>>>>        check_prev_add
> >>>>>        check_prevs_add
> >>>>>        validate_chain
> >>>>>        __lock_acquire+0x2478/0x3b30
> >>>>>        lock_acquire
> >>>>>        lock_acquire+0x1b1/0x560
> >>>>>        __raw_spin_lock_irqsave
> >>>>>        _raw_spin_lock_irqsave+0x3a/0x60
> >>>>>        get_partial_node.part.0+0x20/0x350
> >>>>>        get_partial_node
> >>>>>        get_partial
> >>>>>        ___slab_alloc+0x65b/0x1870
> >>>>>        __slab_alloc.constprop.0+0x56/0xb0
> >>>>>        __slab_alloc_node
> >>>>>        slab_alloc_node
> >>>>>        __do_kmalloc_node
> >>>>>        __kmalloc_node_noprof+0x35c/0x440
> >>>>>        kmalloc_node_noprof
> >>>>>        bpf_map_kmalloc_node+0x98/0x4a0
> >>>>>        lpm_trie_node_alloc
> >>>>>        trie_update_elem+0x1ef/0xe00
> >>>>>        bpf_map_update_value+0x2c1/0x6c0
> >>>>>        map_update_elem+0x623/0x910
> >>>>>        __sys_bpf+0x90c/0x49a0
> >>>>>        ...
> >>>>>
> >>>>> other info that might help us debug this:
> >>>>>
> >>>>>  Possible unsafe locking scenario:
> >>>>>
> >>>>>        CPU0                    CPU1
> >>>>>        ----                    ----
> >>>>>   lock(&trie->lock);
> >>>>>                                lock(&n->list_lock);
> >>>>>                                lock(&trie->lock);
> >>>>>   lock(&n->list_lock);
> >>>>>
> >>>>>  *** DEADLOCK ***
> >>>>>
> >>>>> [1]: https://syzkaller.appspot.com/bug?extid=9045c0a3d5a7f1b119f7
> >>>>>
> >>>>> A bpf program attached to trace_contention_end() triggers after
> >>>>> acquiring &n->list_lock. The program invokes trie_delete_elem(), which
> >>>>> then acquires trie->lock. However, it is possible that another
> >>>>> process is invoking trie_update_elem(). trie_update_elem() will acquire
> >>>>> trie->lock first, then invoke kmalloc_node(). kmalloc_node() may invoke
> >>>>> get_partial_node() and try to acquire &n->list_lock (not necessarily the
> >>>>> same lock object). Therefore, lockdep warns about the circular locking
> >>>>> dependency.
> >>>>>
> >>>>> Invoking kmalloc() before acquiring trie->lock could fix the warning.
> >>>>> However, since BPF programs call be invoked from any context (e.g.,
> >>>>> through kprobe/tracepoint/fentry), there may still be lock ordering
> >>>>> problems for internal locks in kmalloc() or trie->lock itself.
> >>>>>
> >>>>> To eliminate these potential lock ordering problems with kmalloc()'s
> >>>>> internal locks, replacing kmalloc()/kfree()/kfree_rcu() with equivalent
> >>>>> BPF memory allocator APIs that can be invoked in any context. The lock
> >>>>> ordering problems with trie->lock (e.g., reentrance) will be handled
> >>>>> separately.
> >>>>>
> >>>>> Three aspects of this change require explanation:
> >>>>>
> >>>>> 1. Intermediate and leaf nodes are allocated from the same allocator.
> >>>>> Since the value size of LPM trie is usually small, using a single
> >>>>> alocator reduces the memory overhead of the BPF memory allocator.
> >>>>>
> >>>>> 2. Leaf nodes are allocated before disabling IRQs. This handles cases
> >>>>> where leaf_size is large (e.g., > 4KB - 8) and updates require
> >>>>> intermediate node allocation. If leaf nodes were allocated in
> >>>>> IRQ-disabled region, the free objects in BPF memory allocator would not
> >>>>> be refilled timely and the intermediate node allocation may fail.
> >>>>>
> >>>>> 3. Paired migrate_{disable|enable}() calls for node alloc and free. The
> >>>>> BPF memory allocator uses per-CPU struct internally, these paired calls
> >>>>> are necessary to guarantee correctness.
> >>>>>
> >>>>> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
> >>>>> Signed-off-by: Hou Tao <houtao1@huawei.com>
> >>>>> ---
> >>>>>  kernel/bpf/lpm_trie.c | 71 +++++++++++++++++++++++++++++--------------
> >>>>>  1 file changed, 48 insertions(+), 23 deletions(-)
> >>>>>
> >>>>> diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c
> >>>>> index 9ba6ae145239..f850360e75ce 100644
> >>>>> --- a/kernel/bpf/lpm_trie.c
> >>>>> +++ b/kernel/bpf/lpm_trie.c
> >>>>> @@ -15,6 +15,7 @@
> >>>>>  #include <net/ipv6.h>
> >>>>>  #include <uapi/linux/btf.h>
> >>>>>  #include <linux/btf_ids.h>
> >>>>> +#include <linux/bpf_mem_alloc.h>
> >>>>>
> >>>>>  /* Intermediate node */
> >>>>>  #define LPM_TREE_NODE_FLAG_IM BIT(0)
> >>>>> @@ -22,7 +23,6 @@
> >>>>>  struct lpm_trie_node;
> >>>>>
> >>>>>  struct lpm_trie_node {
> >>>>> -       struct rcu_head rcu;
> >>>>>         struct lpm_trie_node __rcu      *child[2];
> >>>>>         u32                             prefixlen;
> >>>>>         u32                             flags;
> >>>>> @@ -32,6 +32,7 @@ struct lpm_trie_node {
> >>>>>  struct lpm_trie {
> >>>>>         struct bpf_map                  map;
> >>>>>         struct lpm_trie_node __rcu      *root;
> >>>>> +       struct bpf_mem_alloc            ma;
> >>>>>         size_t                          n_entries;
> >>>>>         size_t                          max_prefixlen;
> >>>>>         size_t                          data_size;
> >>>>> @@ -287,17 +288,18 @@ static void *trie_lookup_elem(struct bpf_map *map, void *_key)
> >>>> Hey Hao,
> >>> Hi Andrii,
> >>>
> >>> Actually my name is Hou Tao :)
> >> Oops, I'm sorry for butchering your name, Hou!
> >>
> >>>> We recently got a warning from trie_lookup_elem() triggered by
> >>>>
> >>>> rcu_dereference_check(trie->root, rcu_read_lock_bh_held())
> >>>>
> >>>> check in trie_lookup_elem, when LPM trie map was used from a sleepable
> >>>> BPF program.
> >>>>
> >>>> It seems like it can be just converted to bpf_rcu_lock_held(), because
> >>>> with your switch to bpf_mem_alloc all the nodes are now both RCU and
> >>>> RCU Tasks Trace protected, is my thinking correct?
> >>>>
> >>>> Can you please double check? Thanks!
> >>> No. Although the node is freed after one RCU GP and one RCU Task Trace
> >>> GP, the reuse of node happens after one RCU GP. Therefore, for the
> >>> sleepable program when it looks up the trie, it may find and try to read
> >>> a reused node, the returned result will be unexpected due to the reuse.
> >>>
>
> Sorry for the delayed response.
> >> That's two different things, no? Because we do lookup without lock,
> >> it's possible for (non-sleepable or sleepable, doesn't matter) BPF
> >> program to update/delete trie node while some other BPF program looks
> >> it up without locking. I don't think anything fundamentally changes
> >> here. We have similar behavior for other BPF maps (hashmap, for
> >> example), regardless of sleepable or not.
> >>
> >> But the problem here is specifically about overly eager
> >> rcu_dereference_check check. That memory is not going to be freed, so
> >> it's "safe" to dereference it, even if it might be reused for another
> >> node.
> >>
> >> Either that, or we need to disable LPM trie map for sleepable until we
> >> somehow fix this memory reuse, no?
> > Hey Hou,
> >
> > So, it's actually more interesting than what I initially thought.
> > LPM_TRIE is not currently allowed for sleepable programs, but we have
> > a bug in the verifier where you can sneak in such map into a sleepable
> > program through map-in-map. It seems like we are missing checking the
> > inner map for sleepable compatibility.
> >
> > The question for you is how hard is it to make LPM_TRIE support
> > sleepable program? You mentioned some unintended memory reused, can
> > that be fixed/changed? Can you please take a look? Thanks!
>
> Instead of updating LPM_TRIE to handle memory reuse, why not use
> bpf_rcu_read_lock/bpf_rcu_read_unlock to ensure the reuse will not
> happen during the lookup ?

That should work as well, I suppose. We'll need some changes on the
verifier side to support this kind of enforcement for map operations,
is that right? Do you plan to work on this?

> >
> >>>> [...]
> > .
>

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2025-10-13 23:19 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-06 11:06 [PATCH bpf v3 0/9] Fixes for LPM trie Hou Tao
2024-12-06 11:06 ` [PATCH bpf v3 1/9] bpf: Remove unnecessary check when updating " Hou Tao
2024-12-06 11:06 ` [PATCH bpf v3 2/9] bpf: Remove unnecessary kfree(im_node) in lpm_trie_update_elem Hou Tao
2024-12-06 11:06 ` [PATCH bpf v3 3/9] bpf: Handle BPF_EXIST and BPF_NOEXIST for LPM trie Hou Tao
2024-12-06 11:06 ` [PATCH bpf v3 4/9] bpf: Handle in-place update for full LPM trie correctly Hou Tao
2024-12-06 11:06 ` [PATCH bpf v3 5/9] bpf: Fix exact match conditions in trie_get_next_key() Hou Tao
2024-12-06 11:06 ` [PATCH bpf v3 6/9] bpf: Switch to bpf mem allocator for LPM trie Hou Tao
2025-09-19 21:28   ` Andrii Nakryiko
2025-09-23  1:33     ` Hou Tao
2025-09-24 23:31       ` Andrii Nakryiko
2025-10-10 17:04         ` Andrii Nakryiko
2025-10-13  0:59           ` Hou Tao
2025-10-13 23:18             ` Andrii Nakryiko
2024-12-06 11:06 ` [PATCH bpf v3 7/9] bpf: Use raw_spinlock_t " Hou Tao
2024-12-06 11:06 ` [PATCH bpf v3 8/9] selftests/bpf: Move test_lpm_map.c to map_tests Hou Tao
2024-12-06 11:06 ` [PATCH bpf v3 9/9] selftests/bpf: Add more test cases for LPM trie Hou Tao
2024-12-06 17:40 ` [PATCH bpf v3 0/9] Fixes " patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox