|WARNING| net/bnxt: fix truflow out-of-bounds accesses
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Tue Oct 6 04:42:54 CEST 2026
Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/170621
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-10-06
# DPDK Patch Review: net/bnxt: fix truflow out-of-bounds accesses
## Summary
This patch hardens TruFlow code paths that trusted unvalidated indices from firmware responses, adding bounds checks to prevent out-of-bounds memory accesses in the dpool allocator and EM hash delete paths. It also fixes PCI name parsing in `tf_attach_session()` to accept DPDK's optional-domain format.
---
## Errors
### 1. Inconsistent bounds check logic: `(uint32_t)start >= dpool->size` vs `(parms->index - pool->start_index) < pool->size`
**File:** `drivers/net/bnxt/tf_core/dpool.c` and `drivers/net/bnxt/tf_core/tf_em_hash_internal.c`
The patch adds `(uint32_t)start >= dpool->size` checks in `dpool_free()` and `dpool_set_entry_data()`,
but uses the inverted form `(parms->index - pool->start_index) < pool->size` in `tf_em_hash_delete_int_entry()`.
Both forms are logically equivalent (given `start = parms->index - pool->start_index`),
but using inconsistent patterns in the same patch when checking the same condition reduces readability
and suggests one may be wrong.
The subtraction `parms->index - pool->start_index` can underflow when `parms->index < pool->start_index`,
producing a large unsigned value. In the dpool functions, this is caught by the `start < 0` check first
(converting to signed then testing negative). In `tf_em_hash_delete_int_entry()`, the expression
`(parms->index - pool->start_index) < pool->size` performs unsigned arithmetic:
when `parms->index < pool->start_index`, the subtraction wraps,
and the comparison against `pool->size` is unlikely to catch it (depends on exact values).
**Recommended fix:** Use the same pattern as `dpool_free()` for clarity and correctness:
```c
/* tf_em_hash_delete_int_entry(): validate index before dereferencing pool->entry[] */
int offset = (int)(parms->index - pool->start_index);
if (offset < 0 || (uint32_t)offset >= pool->size)
goto skip_usage_update;
size = DP_FLAGS_SIZE(pool->entry[offset].flags);
tf_em_usage_update(tfp, parms->dir, size, TF_RESC_FREE);
skip_usage_update:
```
This ensures underflow is caught by the `offset < 0` check rather than relying on unsigned wraparound.
---
### 2. Missing bounds check in `TF_FLOW_SCALE_QUERY` path remains a vulnerability
**File:** `drivers/net/bnxt/tf_core/tf_em_hash_internal.c`
The patch adds a bounds check around the `pool->entry[]` dereference inside the `#ifdef TF_FLOW_SCALE_QUERY` block,
but it then **silently continues** to call `dpool_free(pool, parms->index)` even when the index is out of range.
`dpool_free()` now has an upper-bound check and will return `-1` on invalid index,
but that return value is **not checked** by the caller.
If firmware returns an out-of-bounds `parms->index`, the patch correctly skips the usage update
but then passes the bogus index to `dpool_free()`, which will fail silently (return `-1` is dropped).
The function then returns 0 (success), and the caller believes the entry was deleted when it was not.
This is a **logic error**: a corrupt firmware response can cause a resource leak
(the entry remains allocated but the application thinks it's freed).
**Recommended fix:** Check the return value of `dpool_free()` and propagate the error:
```c
int offset = (int)(parms->index - pool->start_index);
if (offset >= 0 && (uint32_t)offset < pool->size) {
size = DP_FLAGS_SIZE(pool->entry[offset].flags);
tf_em_usage_update(tfp, parms->dir, size, TF_RESC_FREE);
}
rc = dpool_free(pool, parms->index);
if (rc != 0) {
TFP_DRV_LOG(ERR, "Failed to free EM entry index %u\n", parms->index);
return -EINVAL;
}
return 0;
```
This prevents the function from reporting success when the index was invalid.
---
## Warnings
### 3. Implicit zero comparison: `size == 0`
**File:** `drivers/net/bnxt/tf_core/dpool.c` line 353
```c
if (size > dpool->max_alloc_size || size == 0 ||
```
The existing code (not introduced by this patch) uses implicit zero comparison.
DPDK style requires explicit comparison:
```c
if (size > dpool->max_alloc_size || size == 0 ||
```
**Wait--this IS explicit.** No issue here. (Checking my notes: explicit `== 0` is correct. Disregard.)
---
### 4. `sscanf()` return value overwritten without checking intermediate results
**File:** `drivers/net/bnxt/tf_core/tf_core.c` lines 121 and 144
```c
rc = sscanf(parms->ctrl_chan_name, "%x:%x:%x.%u", &domain, &bus, &slot, &device);
if (rc != 4) {
domain = 0;
rc = sscanf(parms->ctrl_chan_name, "%x:%x.%u", &bus, &slot, &device);
if (rc != 3) {
TFP_DRV_LOG(ERR, "Failed to scan device ctrl_chan_name\n");
return -EINVAL;
}
}
```
When the first `sscanf()` returns `rc != 4`, the code immediately overwrites `rc` with the second `sscanf()` result.
This is acceptable because the first `rc` has already been tested (`!= 4` branch taken),
and the variable is reused for the second parse attempt.
However, the pattern is potentially fragile: if a third parse attempt were added,
it would be easy to forget to test `rc` before overwriting.
This is a **style observation**, not a bug--the current code is correct.
**No change required**, but consider using separate variables (`rc1`, `rc2`) or testing `rc` immediately
if this pattern is extended in the future.
---
### 5. Domain variable reused after failed 4-field parse
**File:** `drivers/net/bnxt/tf_core/tf_core.c` lines 115 and 141
When the 4-field `sscanf()` fails and the fallback 3-field parse is attempted,
`domain` is unconditionally set to `0` but `bus`, `slot`, and `device` are **not reset**.
If the first `sscanf()` partially succeeded (e.g., `rc == 2`),
those variables may contain stale values from the failed parse.
The fallback `sscanf()` writes to `bus`, `slot`, `device` again,
so if it succeeds (`rc == 3`), the stale values are overwritten.
But if the fallback also fails, the variables retain partial results from the first parse attempt.
Since the function returns `-EINVAL` on fallback failure, the stale values are never used.
**This is not a bug** because the error path does not use `bus`/`slot`/`device`.
However, initializing them to 0 before the first `sscanf()` would make the code more robust.
**Suggested improvement** (not required):
```c
unsigned int domain = 0, bus = 0, slot = 0, device = 0;
```
---
## Info
### 6. Consider extracting PCI name parsing into a helper function
**File:** `drivers/net/bnxt/tf_core/tf_core.c`
The patch duplicates the 4-field-then-3-field `sscanf()` fallback pattern twice
(once for `ctrl_chan_name`, once for `attach_chan_name`).
The same pattern appears in `tf_open_session()` (mentioned in the commit message).
**Suggestion:** Extract this into a helper function:
```c
static int
parse_pci_name(const char *name, unsigned int *domain, unsigned int *bus,
unsigned int *slot, unsigned int *device)
{
int rc;
rc = sscanf(name, "%x:%x:%x.%u", domain, bus, slot, device);
if (rc == 4)
return 0;
*domain = 0;
rc = sscanf(name, "%x:%x.%u", bus, slot, device);
if (rc == 3)
return 0;
return -EINVAL;
}
```
Then use:
```c
if (parse_pci_name(parms->ctrl_chan_name, &domain, &bus, &slot, &device) != 0) {
TFP_DRV_LOG(ERR, "Failed to scan device ctrl_chan_name\n");
return -EINVAL;
}
```
This eliminates duplication and makes future changes (e.g., supporting extended PCI formats) easier.
---
### 7. Consider logging the invalid index when bounds check fails
**File:** `drivers/net/bnxt/tf_core/dpool.c`
When `dpool_free()` or `dpool_set_entry_data()` detect an out-of-range index,
they return `-1` but do not log the invalid value.
Adding a log statement would aid debugging firmware issues:
```c
if (start < 0 || (uint32_t)start >= dpool->size) {
TFP_DRV_LOG(ERR, "dpool index %d out of range [0, %u)\n",
index, dpool->start_index + dpool->size);
return -1;
}
```
---
### 8. `TF_FLOW_SCALE_QUERY` comment could be clearer
**File:** `drivers/net/bnxt/tf_core/tf_em_hash_internal.c` line 156
The added comment:
```c
/* Update usage state buffer for EM.
* parms->index is populated from the (untrusted) HWRM response by
* tf_msg_delete_em_entry(); validate it before indexing the pool.
*/
```
is helpful but could explicitly state what happens on validation failure:
```c
/* Update usage state buffer for EM.
* parms->index originates from firmware (untrusted). Validate it before
* indexing pool->entry[]; skip usage update if out of range.
*/
```
---
## Overall Assessment
The patch correctly identifies and mitigates out-of-bounds access vulnerabilities in TruFlow.
The dpool bounds checks are sound (modulo the style inconsistency noted),
and the PCI name parsing fix is correct.
**Critical issue:** Error propagation from `dpool_free()` is missing in the delete path,
allowing a corrupt firmware response to silently leak resources.
This should be fixed before merging.
**Style issue:** Inconsistent bounds-check pattern between dpool and em_hash_delete creates confusion;
unifying the approach improves clarity and catches underflow correctly.
More information about the test-report
mailing list