Skip to content

Commit

Permalink
bpf: Handle in-place update for full LPM trie correctly
Browse files Browse the repository at this point in the history
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: b95a5c4 ("bpf: add a longest prefix match trie map implementation")
Reviewed-by: Toke Høiland-Jørgensen <[email protected]>
Signed-off-by: Hou Tao <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Alexei Starovoitov <[email protected]>
  • Loading branch information
Hou Tao authored and Alexei Starovoitov committed Dec 6, 2024
1 parent eae6a07 commit 532d6b3
Showing 1 changed file with 21 additions and 23 deletions.
44 changes: 21 additions & 23 deletions kernel/bpf/lpm_trie.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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];
Expand All @@ -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.
Expand All @@ -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;
}
Expand All @@ -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);

Expand Down

0 comments on commit 532d6b3

Please sign in to comment.