Thread (2 messages) flat view 2 messages, 2 authors, 4d ago

Re: [PATCH net] octeontx2-af: Fix BPID leak in nix_bp_enable()

From: netdev-bot+sashiko@kernel.org
Date: 2026-09-04 10:25:24
Also in: lkml

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 1 (2) · Medium: 0 (3) · Low: 1

- [High] Incomplete fix: the BPID leak the commit claims to close is
  still reachable through the *pre-loop* rvu_nix_get_bpid() call.
- [Low] Inaccurate Fixes tag.

Pre-existing issues:
- [High] nix_bp_disable() frees a BPID derived from a hardware register
  with no validation.
- [High] Unvalidated mailbox channel range reaches the MMIO register
  index for LBK interfaces.
- [Medium] For LBK interfaces the mailbox contract and the response
  advertise BPIDs that do not match what is programmed into hardware.
- [Medium] Off-by-one BPID range check for SDP interfaces.
- [Medium] nix_bp_enable() has no unwind on a mid-loop failure.

--

octeontx2-af: Fix BPID leak in nix_bp_enable()

The patch stops nix_bp_enable() from calling rvu_nix_get_bpid() once the
last channel has been programmed, since for LBK interfaces every call
allocates a BPID from the shared free pool. A few questions about the fix
and about the surrounding BPID lifecycle follow.
Fixes: 27150bc4286c ("octeontx2-af: Interface backpressure configuration")
[Severity: Low]
Is this the right commit to point at?  The behaviour the commit message
reasons about, "each call allocates a BPID", comes from the free-pool
allocation in the NIX_INTF_TYPE_LBK arm of rvu_nix_get_bpid():

	case NIX_INTF_TYPE_LBK:
		/* Alloc bpid from the free pool */
		mutex_lock(&rvu->rsrc_lock);
		bpid = rvu_alloc_rsrc(&bp->bpids);

git blame attributes that block to d6212d2e41a0 ("octeontx2-af: Create
BPIDs free pool"); before it the LBK arm computed a deterministic
bpid = cgx_bpid_cnt + req->chan_base with no allocation, so the surplus
trailing call could not leak anything.  Only the case label and the break
date back to 27150bc4286c.  Would Fixes: d6212d2e41a0 describe the
affected range (v6.9+) more accurately, so stable trees that lack the free
pool are not targeted?
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
index 153eb57bad067..6df9f4e512b6c 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
@@ -784,7 +784,8 @@ static int nix_bp_enable(struct rvu *rvu,
 		rvu_write64(rvu, blkaddr, NIX_AF_RX_CHANX_CFG(chan_v),
 			    cfg | (bpid & GENMASK_ULL(8, 0)) | BIT_ULL(16));
 		chan_id++;
-		bpid = rvu_nix_get_bpid(rvu, req, type, chan_id);
+		if (chan_id < req->chan_cnt)
+			bpid = rvu_nix_get_bpid(rvu, req, type, chan_id);
 	}
 
 	for (chan = 0; chan < req->chan_cnt; chan++) {
[Severity: High]
Does this guard cover the other unconditional call?  nix_bp_enable() also
allocates before the loop:

	bpid_base = rvu_nix_get_bpid(rvu, req, type, chan_id);
	chan_base = pfvf->rx_chan_base + req->chan_base;
	bpid = bpid_base;

	for (chan = chan_base; chan < (chan_base + req->chan_cnt); chan++) {
		if (bpid < 0) {
			dev_warn(rvu->dev, "Fail to enable backpressure\n");
			return -EINVAL;
		}
		...
	}

req->chan_cnt is a u8 taken verbatim from struct nix_bp_cfg_req, and
neither nix_bp_enable() nor the LBK arm of rvu_nix_get_bpid() validates
it.  With chan_cnt == 0, does this leak one pool entry per request?

	NIX_BP_ENABLE (chan_cnt = 0)
	  rvu_mbox_handler_nix_bp_enable()
	    nix_bp_enable()
	      bpid_base = rvu_nix_get_bpid()   /* rvu_alloc_rsrc(&bp->bpids) */
	      for (...)                        /* zero iterations */
	      return 0

The allocated BPID is never written into NIX_AF_RX_CHANX_CFG, so
nix_bp_disable() cannot reclaim it either, since it recovers LBK BPIDs
only by reading the field back out of the channel register:

		if (type == NIX_INTF_TYPE_LBK) {
			bpid = cfg & GENMASK(8, 0);
			mutex_lock(&rvu->rsrc_lock);
			rvu_free_rsrc(&bp->bpids, bpid - bp->free_pool_base);
			for (bpid = 0; bpid < bp->bpids.max; bpid++) {
				if (bp->fn_map[bpid] == pcifunc) {
					bp->fn_map[bpid] = 0;
					bp->ref_cnt[bpid] = 0;
				}
			}

That same block wipes fn_map[]/ref_cnt[] for every BPID owned by the
pcifunc while freeing only the one read from the register, which also
removes the orphan from the fn_map sweep in rvu_nix_flr_free_bpids().
Can an LBK VF drain the shared pool this way, with the loss surviving
FLR?

On the same zero-count path, the only bpid < 0 test lives inside the
loop, so an error return from rvu_nix_get_bpid() (NIX_AF_ERR_AF_LF_INVALID
or NIX_AF_ERR_INVALID_BPID) is discarded and success is reported.  Would
moving the allocation into the loop body, or validating chan_cnt and
checking bpid_base before the loop, be a better shape for this fix than
extending the in-loop call with a guard?

[Severity: Medium]
This isn't a bug introduced by this patch, but for LBK the reported
mapping does not match what gets programmed.  mbox.h documents:

	u8	bpid_per_chan;
	/* bpid_per_chan = 0 assigns single bp id for range of channels */
	/* bpid_per_chan = 1 assigns separate bp id for each channel */

The LBK arm of rvu_nix_get_bpid() never consults req->bpid_per_chan
(unlike the CGX and SDP arms) and always takes a fresh pool entry, so an
LBK request with chan_cnt == N still programs N distinct BPIDs into
NIX_AF_RX_CHANX_CFG.  The response loop then synthesizes a contiguous
range instead:

	rsp->chan_bpid[chan] = ((req->chan_base + chan) & 0x7F) << 10 |
				(bpid_base & 0x3FF);
	if (req->bpid_per_chan)
		bpid_base++;

rvu->rsrc_lock is taken and dropped inside each rvu_nix_get_bpid() call,
so pool fragmentation or a concurrent enable/disable/FLR makes the
allocated ids non-consecutive as well.  Does mbox_handler_nix_bp_enable()
in otx2_common.c then arm the NPA aura / NIX CQ against BPIDs the channels
are not configured with?

	chan_id = ((rsp->chan_bpid[chan] >> 10) & 0x7F);
	pfvf->bpid[chan_id] = rsp->chan_bpid[chan] & 0x3FF;

nix_interface_init() sets rx_chan_cnt = 1 for LBK VFs and
otx2_nix_config_bp() sends bpid_per_chan = 0, so the in-tree non-PFC path
appears unaffected; the mismatch needs a multi-channel LBK request.

[Severity: High]
This is a pre-existing issue, but the release side of the same BPID
lifecycle frees an unvalidated register value.  In nix_bp_disable():

		if (type == NIX_INTF_TYPE_LBK) {
			bpid = cfg & GENMASK(8, 0);
			mutex_lock(&rvu->rsrc_lock);
			rvu_free_rsrc(&bp->bpids, bpid - bp->free_pool_base);

bpid is u16 and bp->free_pool_base is u16, so the subtraction is a plain
int and goes negative whenever the channel's BPID field is below
free_pool_base, in particular 0 on a channel where backpressure was never
enabled.  rvu_free_rsrc() does no bounds or state check:

	void rvu_free_rsrc(struct rsrc_bmap *rsrc, int id)
	{
		if (!rsrc->bmap)
			return;

		__clear_bit(id, rsrc->bmap);
	}

__clear_bit() takes an unsigned long, so does a negative index turn into
a wild word offset and write outside the bitmap allocation?  The path is

	NIX_BP_DISABLE (chan_cnt >= 1, no prior successful enable)
	  rvu_mbox_handler_nix_bp_disable()
	    nix_bp_disable()          /* cfg BPID field reads 0 */
	      rvu_free_rsrc(&bp->bpids, 0 - bp->free_pool_base)

Also, nix_bp_disable() clears only BIT_ULL(16) and leaves the BPID bits in
the register:

		rvu_write64(rvu, blkaddr, NIX_AF_RX_CHANX_CFG(chan_v),
			    cfg & ~BIT_ULL(16));

so does a second NIX_BP_DISABLE free the same pool index twice, with the
fn_map/ref_cnt wipe then allowing the same BPID to be handed to another
pcifunc?

[Severity: High]
This isn't introduced by this patch either, but the channel index that
nix_bp_enable() derives from the mailbox is unbounded for LBK:

	chan_base = pfvf->rx_chan_base + req->chan_base;
	...
		chan_v = nix_get_channel(chan, cpt_link);
		cfg = rvu_read64(rvu, blkaddr, NIX_AF_RX_CHANX_CFG(chan_v));

req->chan_base is a u16 fully controlled by the requesting PF/VF and
nix_get_channel() only ORs BIT(11) for cpt_link.  rvu_nix_get_bpid()
validates chan_base + chan_cnt for CGX and SDP, but the LBK arm validates
nothing and always returns a freshly allocated BPID, so the loop reaches
MMIO with an arbitrary index:

	#define NIX_AF_RX_CHANX_CFG(a)  (0x1A30 | (a) << 15)

	static inline void rvu_write64(struct rvu *rvu, u64 block, u64 offset, u64 val)
	{
		writeq(val, rvu->afreg_base + ((block << 28) | offset));
	}

Since the offset is ORed with block << 28 rather than added, can a large
chan_v alias another RVU block's register space or land past the
ioremapped BAR, with the read-modify-write leaving bpid | BIT(16) there?
Even small values let an LBK VF rewrite other functions'
NIX_AF_RX_CHANX_CFG, while nix_interface_init() gives an LBK VF
rx_chan_cnt = 1.  Note the LBK bound check used to exist:

	if ((req->chan_base + req->chan_cnt) > 63)
		return -EINVAL;

and was removed by d6212d2e41a0 ("octeontx2-af: Create BPIDs free pool").
Should it be restored?

[Severity: Medium]

Correction to the tag above, the finding id is gpt-5-6-sol-4-16.

[Severity: Medium]
This is a pre-existing issue, but nix_bp_enable() has no unwind when an
allocation fails part way through the loop.  Hardware is armed
channel-by-channel before the next BPID is obtained, and the failure is
only detected on the following iteration:

	for (chan = chan_base; chan < (chan_base + req->chan_cnt); chan++) {
		if (bpid < 0) {
			dev_warn(rvu->dev, "Fail to enable backpressure\n");
			return -EINVAL;
		}
		...
		rvu_write64(rvu, blkaddr, NIX_AF_RX_CHANX_CFG(chan_v),
			    cfg | (bpid & GENMASK_ULL(8, 0)) | BIT_ULL(16));
		chan_id++;
		if (chan_id < req->chan_cnt)
			bpid = rvu_nix_get_bpid(rvu, req, type, chan_id);
	}

On that -EINVAL return, the BPIDs already taken for the earlier channels
are not released and the BIT_ULL(16) bits already written are not
cleared.  The requester sees a failed mbox, so it never programs the
matching aura/CQ BPIDs and never sends NIX_BP_DISABLE.  Does this leave
channels armed against BPIDs whose aura side is unconfigured, recoverable
only via FLR through bp->fn_map?  And since nix_bp_disable() does not
check BIT_ULL(16) before trusting the read-back field, would a later
full-range disable then call rvu_free_rsrc() with 0 - bp->free_pool_base
for the never-programmed channels?

[Severity: Medium]
This is a pre-existing issue as well, but the SDP bound check in
rvu_nix_get_bpid() looks off by one:

		if (bpid > (bp->cgx_bpid_cnt + bp->sdp_bpid_cnt))
			return NIX_AF_ERR_INVALID_BPID;

nix_setup_bpids() reserves NIX_BPIDS_PER_CPT (=1) BPID at exactly
cgx_bpid_cnt + sdp_bpid_cnt:

	bp->cgx_bpid_cnt = rvu->hw->cgx_links * NIX_BPIDS_PER_LMAC;
	bp->sdp_bpid_cnt = rvu->hw->sdp_links * FIELD_GET(NIX_CONST_SDP_CHANS, cfg);
	bp->free_pool_base = bp->cgx_bpid_cnt + bp->sdp_bpid_cnt +
			     NIX_BPIDS_PER_CPT;

With sdp_chan_base >= 1 (the offset SDP case) and chan_base + chan_cnt at
the upper edge allowed by the request check, bpid reaches exactly
cgx_bpid_cnt + sdp_bpid_cnt and passes the > comparison.  Should this be
= so an SDP channel cannot be programmed with the CPT-reserved BPID?
-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901102500.1534911-1-nshettyj%40marvell.com
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help