From: Mohsin Bashir <mohsin.bashr@gmail.com>
To: Yehyeong Lee <yhlee@isslab.korea.ac.kr>,
Alexander Duyck <alexanderduyck@fb.com>,
Jakub Kicinski <kuba@kernel.org>,
netdev@vger.kernel.org
Cc: kernel-team@meta.com, Andrew Lunn <andrew+netdev@lunn.ch>,
davem@davemloft.net, Eric Dumazet <edumazet@kernel.org>,
Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
Dimitri Daskalakis <daskald@meta.com>,
Bobby Eshleman <bobbyeshleman@meta.com>,
Daniel Zahka <daniel.zahka@gmail.com>,
stable@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH net v3 1/5] eth: fbnic: only act on Rx completions naming a posted buffer
Date: Mon, 12 Oct 2026 01:18:54 -0700 [thread overview]
Message-ID: <40b3432d-b597-423f-9279-ab4aa1157f44@gmail.com> (raw)
In-Reply-To: <25ce85c9821fba5d94932b36b9c9afb9a2fbd12a.1791775409.git.yhlee@isslab.korea.ac.kr>
On 10/11/26 9:15 PM, Yehyeong Lee wrote:
> An Rx completion descriptor carries a 16-bit buffer id that the driver
> uses to index the rx_buf[] array (FBNIC_RCD_AL_BUFF_PAGE_MASK). The fill
> path masks the id to the ring size, but the completion path does not, so
> the device can hand back an id that is past the array or that names a
> slot which was never posted. An unposted in-range slot holds netmem 0,
> and netmem_to_page(0) returns NULL, which fbnic_pkt_prepare() and
> fbnic_add_rx_frag() then dereference.
>
> Masking the id would not fix this: it aliases an out-of-range id onto a
> posted buffer and corrupts that buffer's page_pool reference count.
> Reject a completion whose id is outside the array or outside the posted
> window and take the existing drop path.
>
> The same id is used as the BDQ head in fbnic_clean_bdq(), where an
> out-of-range value spins the cleanup loop; bound it there with the same
> test. Validate head0/head1 in fbnic_clean_rcq() before they are assigned,
> and drop a META completion that arrives with no header buffer rather than
> building an skb around a NULL pointer.
>
> Oops: general protection fault, probably for non-canonical address
> 0xdffffc0000000004: 0000 [#1] SMP KASAN NOPTI
> KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027]
> RIP: 0010:fbnic_pkt_prepare+0x1a2/0x5a0 [fbnic]
> Call Trace:
> <TASK>
> fbnic_clean_rcq+0x1ba/0x1350 [fbnic]
>
> Fixes: a29b8eb6e533 ("eth: fbnic: Add basic Rx handling")
> Cc: stable@vger.kernel.org
> Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
> ---
> drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 87 ++++++++++++++++++--
> 1 file changed, 82 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
> index 10caacffee0f0..305fe6d2f511f 100644
> --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
> +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
> @@ -797,6 +797,34 @@ static void fbnic_page_pool_drain(struct fbnic_ring *ring, unsigned int idx,
> rx_buf->netmem = 0;
> }
>
> +/* The device names an Rx buffer by its index in the buffer descriptor
> + * ring. A completion may only name a buffer we posted, that is one in
> + * [head, tail), so test the index against the array and against the
> + * posted window. The window test is not enough on its own: it masks the
> + * index, so an index past the end of the array can fold back into the
> + * window and run off rx_buf[].
> + */
> +static bool fbnic_rx_buf_posted(struct fbnic_ring *ring, unsigned int idx)
> +{
> + if (idx > ring->size_mask)
> + return false;
> +
> + return ((idx - ring->head) & ring->size_mask) < fbnic_desc_used(ring);
> +}
> +
> +/* fbnic_clean_bdq() is given a position to consume up to rather than a
> + * buffer to use, so unlike fbnic_rx_buf_posted() it also accepts tail,
> + * which is how the teardown path asks for the whole ring to be drained.
> + */
> +static bool fbnic_bdq_head_valid(struct fbnic_ring *ring, unsigned int hw_head)
> +{
> + if (hw_head > ring->size_mask)
> + return false;
> +
> + return ((hw_head - ring->head) & ring->size_mask) <=
> + fbnic_desc_used(ring);
> +}
> +
> static void fbnic_clean_twq(struct fbnic_napi_vector *nv, int napi_budget,
> struct fbnic_q_triad *qt, s32 ts_head, s32 head0,
> s32 head1)
> @@ -882,6 +910,13 @@ static void fbnic_clean_bdq(struct fbnic_ring *ring, unsigned int hw_head,
> {
> unsigned int head = ring->head;
>
> + /* A head we never posted cannot name a buffer we own, and the loop
> + * below masks head to the ring so it would never reach it. Leave
> + * the ring alone and wait for a head we can act on.
> + */
> + if (unlikely(!fbnic_bdq_head_valid(ring, hw_head)))
> + return;
> +
> if (head == hw_head)
> return;
>
> @@ -985,14 +1020,25 @@ static void fbnic_pkt_prepare(struct fbnic_napi_vector *nv, u64 rcd,
> {
> unsigned int hdr_pg_idx = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
> unsigned int hdr_pg_off = FIELD_GET(FBNIC_RCD_AL_BUFF_OFF_MASK, rcd);
> - struct page *page = fbnic_page_pool_get_head(qt, hdr_pg_idx);
> unsigned int len = FIELD_GET(FBNIC_RCD_AL_BUFF_LEN_MASK, rcd);
> unsigned int frame_sz, hdr_pg_start, hdr_pg_end, headroom;
> unsigned char *hdr_start;
> + struct page *page;
>
> /* data_hard_start should always be NULL when this is called */
> WARN_ON_ONCE(pkt->buff.data_hard_start);
>
> + /* A buffer ID we never posted cannot name a buffer we own, so drop
> + * the packet instead of running off the end of rx_buf[] or picking
> + * up a slot we have already drained.
> + */
> + if (unlikely(!fbnic_rx_buf_posted(&qt->sub0, hdr_pg_idx))) {
> + pkt->add_frag_failed = true;
> + return;
> + }
> +
> + page = fbnic_page_pool_get_head(qt, hdr_pg_idx);
> +
> /* Short-cut the end calculation if we know page is fully consumed */
> hdr_pg_end = FIELD_GET(FBNIC_RCD_AL_PAGE_FIN, rcd) ?
> FBNIC_BD_FRAG_SIZE : fbnic_hdr_pg_end(hdr_pg_off, len);
> @@ -1026,10 +1072,27 @@ static void fbnic_add_rx_frag(struct fbnic_napi_vector *nv, u64 rcd,
> unsigned int pg_idx = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
> unsigned int pg_off = FIELD_GET(FBNIC_RCD_AL_BUFF_OFF_MASK, rcd);
> unsigned int len = FIELD_GET(FBNIC_RCD_AL_BUFF_LEN_MASK, rcd);
> - netmem_ref netmem = fbnic_page_pool_get_data(qt, pg_idx);
> unsigned int truesize;
> + netmem_ref netmem;
> bool added;
>
> + /* No header completion has set buff up, either because one was
> + * rejected or because the device sent none, so there is nothing to
> + * add a fragment to. Mark the packet so the metadata descriptor
> + * drops it instead of building an skb on an empty buffer.
> + */
> + if (unlikely(!pkt->buff.data_hard_start)) {
> + pkt->add_frag_failed = true;
> + return;
> + }
> +
> + if (unlikely(!fbnic_rx_buf_posted(&qt->sub1, pg_idx))) {
> + pkt->add_frag_failed = true;
> + return;
> + }
> +
> + netmem = fbnic_page_pool_get_data(qt, pg_idx);
> +
> truesize = FIELD_GET(FBNIC_RCD_AL_PAGE_FIN, rcd) ?
> FBNIC_BD_FRAG_SIZE - pg_off : ALIGN(len, 128);
>
> @@ -1270,6 +1333,7 @@ static int fbnic_clean_rcq(struct fbnic_napi_vector *nv,
> while (likely(packets < budget)) {
> struct sk_buff *skb = ERR_PTR(-EINVAL);
> u32 pkt_bytes;
> + u32 pg_idx;
> u64 rcd;
>
> if ((*raw_rcd & cpu_to_le64(FBNIC_RCD_DONE)) == done)
> @@ -1281,12 +1345,21 @@ static int fbnic_clean_rcq(struct fbnic_napi_vector *nv,
>
> switch (FIELD_GET(FBNIC_RCD_TYPE_MASK, rcd)) {
> case FBNIC_RCD_TYPE_HDR_AL:
> - head0 = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
> + pg_idx = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
> + /* Only take the head from a completion we are going
> + * to act on, so that one rejected ID does not discard
> + * the progress an earlier completion in this poll has
> + * already reported.
> + */
> + if (likely(fbnic_rx_buf_posted(&qt->sub0, pg_idx)))
> + head0 = pg_idx;
> fbnic_pkt_prepare(nv, rcd, pkt, qt);
>
> break;
> case FBNIC_RCD_TYPE_PAY_AL:
> - head1 = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
> + pg_idx = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
> + if (likely(fbnic_rx_buf_posted(&qt->sub1, pg_idx)))
> + head1 = pg_idx;
> fbnic_add_rx_frag(nv, rcd, pkt, qt);
>
> break;
> @@ -1300,7 +1373,11 @@ static int fbnic_clean_rcq(struct fbnic_napi_vector *nv,
> /* We currently ignore the action table index */
> break;
> case FBNIC_RCD_TYPE_META:
> - if (likely(!fbnic_rcd_metadata_err(rcd) &&
> + /* A metadata completion with no header completion
> + * ahead of it leaves nothing to build a frame from.
> + */
> + if (likely(pkt->buff.data_hard_start &&
> + !fbnic_rcd_metadata_err(rcd) &&
> !pkt->add_frag_failed)) {
> pkt_bytes = xdp_get_buff_len(&pkt->buff);
> skb = fbnic_run_xdp(nv, pkt);
As Daniel pointed-out on V1, the device does not make up the buffer
ID's. Rather, it only echoes them back as provided by the SW. A
completion with ID's that SW did not post would indicate a bad device or
an inconsistency between SW and HW state. Either of the two would
indicate that the issue is somewhere else. These checks would only mask
the issue.
next prev parent reply other threads:[~2026-10-12 8:18 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 4:15 [PATCH net v3 0/5] eth: fbnic: harden Rx and Tx completion handling Yehyeong Lee
2026-10-12 4:15 ` [PATCH net v3 1/5] eth: fbnic: only act on Rx completions naming a posted buffer Yehyeong Lee
2026-10-12 8:18 ` Mohsin Bashir [this message]
2026-10-12 8:42 ` Yehyeong Lee
2026-10-12 4:15 ` [PATCH net v3 2/5] eth: fbnic: validate the Rx completion offset and length Yehyeong Lee
2026-10-12 8:22 ` Mohsin Bashir
2026-10-12 4:15 ` [PATCH net v3 3/5] eth: fbnic: return a pending Rx packet before reusing its buffer Yehyeong Lee
2026-10-12 8:24 ` Mohsin Bashir
2026-10-12 4:15 ` [PATCH net v3 4/5] eth: fbnic: return the page reference taken for a dropped Rx fragment Yehyeong Lee
2026-10-12 8:27 ` Mohsin Bashir
2026-10-12 4:16 ` [PATCH net v3 5/5] eth: fbnic: bound the XDP Tx completion head to the ring Yehyeong Lee
2026-10-12 8:34 ` Mohsin Bashir
2026-10-12 4:20 ` [PATCH net v3 0/5] eth: fbnic: harden Rx and Tx completion handling netdev-bot+sinfo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=40b3432d-b597-423f-9279-ab4aa1157f44@gmail.com \
--to=mohsin.bashr@gmail.com \
--cc=alexanderduyck@fb.com \
--cc=andrew+netdev@lunn.ch \
--cc=bobbyeshleman@meta.com \
--cc=daniel.zahka@gmail.com \
--cc=daskald@meta.com \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kernel-team@meta.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
--cc=yhlee@isslab.korea.ac.kr \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®