|WARNING| lpm: fix tbl8 reuse after delete all with RCU defer queue

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Mon Nov 9 09:08:08 CET 2026


Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/172737

_AI Code Review_

=== Patch Review: series.patch (via Claude) ===
Review date: 2026-11-09

# DPDK Patch Review

## Analysis

This patch fixes a use-after-free issue in the LPM library when using RCU defer queue mode. The bug occurs when `rte_lpm_delete_all()` clears tbl8 groups that are still waiting in the defer queue for reclamation, allowing them to be reallocated immediately. When the deferred reclamation runs later, it invalidates these reused groups.

## Errors

**1. Missing error check on rte_rcu_qsbr_dq_reclaim return value**

In `lib/lpm/rte_lpm.c`, line 1279:
```c
rte_rcu_qsbr_dq_reclaim(i_lpm->dq, i_lpm->number_tbl8s,
	NULL, NULL, NULL);
```

The function `rte_rcu_qsbr_dq_reclaim()` returns `int` and can fail. The return value should be checked or at least cast to `(void)` to indicate intentional discard if failure cannot happen here. In the context of `delete_all`, failure to reclaim may leave the defer queue in an inconsistent state relative to the cleared tbl8 groups.

Suggested fix:
```c
(void)rte_rcu_qsbr_dq_reclaim(i_lpm->dq, i_lpm->number_tbl8s,
	NULL, NULL, NULL);
```
Or verify that errors cannot occur and document why, or handle the error appropriately.

**2. Potential partial reclamation leaves inconsistent state**

In `lib/lpm/rte_lpm.c`, line 1279:
```c
rte_rcu_qsbr_dq_reclaim(i_lpm->dq, i_lpm->number_tbl8s,
	NULL, NULL, NULL);
```

`rte_rcu_qsbr_dq_reclaim()` is documented to reclaim "a maximum of `max` resources". If the defer queue holds more than `i_lpm->number_tbl8s` entries, some cleared tbl8 groups remain in the queue and can still be invalidated later when they are reclaimed. This is the same bug the patch is trying to fix, just with a higher threshold.

The correct approach is to drain the entire defer queue, not cap reclamation at `number_tbl8s`. Use `UINT_MAX` or `~0U` as the max parameter, or call `rte_rcu_qsbr_dq_reclaim()` in a loop until it returns 0.

Suggested fix:
```c
/* Drain the entire defer queue */
rte_rcu_qsbr_synchronize(i_lpm->v, RTE_QSBR_THRID_INVALID);
while (rte_rcu_qsbr_dq_reclaim(i_lpm->dq, ~0U,
		NULL, NULL, NULL) > 0)
	;
```

## Warnings

None.

## Info

**1. Test does not verify the actual bug was fixed**

In `app/test/test_lpm.c`, the test adds `ip1`, deletes it (pushing to defer queue), calls `delete_all`, adds `ip2`, then adds `ip3` expecting it to fail due to no tbl8 groups left. It then verifies `ip2` is still found.

However, this only tests that `ip2` is found *immediately*. The original bug manifests when the deferred reclamation runs *later* and invalidates the reused group. The test should trigger a reclamation after adding `ip2` to verify the group is not invalidated. For example:
```c
status = rte_lpm_add(lpm, ip2, depth, 2);
TEST_LPM_ASSERT(status == 0);

/* Force deferred reclamation - without the fix, ip2 becomes unfindable */
rte_rcu_qsbr_quiescent(qsv, 0);
rte_rcu_qsbr_synchronize(qsv, RTE_QSBR_THRID_INVALID);
rte_rcu_qsbr_dq_reclaim(rcu_cfg.dq, ~0U, NULL, NULL, NULL);

status = rte_lpm_lookup(lpm, ip2, &next_hop_return);
TEST_LPM_ASSERT(status == 0 && next_hop_return == 2);
```

Without this, the test passes even without the fix if reclamation does not happen to run during the test.

**2. Test does not register thread with QSBR**

In `app/test/test_lpm.c`, the test creates a QSBR variable but never calls `rte_rcu_qsbr_thread_register()`. While this may work for this specific test, it is incomplete usage of the QSBR API. For completeness:
```c
status = rte_rcu_qsbr_init(qsv, 1);
TEST_LPM_ASSERT(status == 0);

status = rte_rcu_qsbr_thread_register(qsv, 0);
TEST_LPM_ASSERT(status == 0);

rcu_cfg.v = qsv;
```

And before freeing:
```c
status = rte_rcu_qsbr_thread_unregister(qsv, 0);
TEST_LPM_ASSERT(status == 0);

rte_lpm_free(lpm);
rte_free(qsv);
```

Review-Result: ERROR


More information about the test-report mailing list