git: 669cd0d90ee7 - main - rge: Preserve replacement mbufs after defragmentation

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 29 Sep 2026 20:17:45 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=669cd0d90ee7b5bed794ded5fa00b6be854a6598

commit 669cd0d90ee7b5bed794ded5fa00b6be854a6598
Author:     Andrew Griffiths <andrew@calif.io>
AuthorDate: 2026-09-29 20:11:31 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-09-29 20:14:12 +0000

    rge: Preserve replacement mbufs after defragmentation
    
    m_defrag() has pointer-return ownership semantics: success frees the
    original chain and returns a replacement, while failure returns NULL and
    leaves the original owned by the caller.  rge_encap() compared that
    pointer as an integer status and could return failure after success,
    causing rge_tx_task() to free its stale old head.
    
    Pass the mbuf by reference, retain the replacement returned by
    m_defrag(), and publish it to the caller before retrying DMA mapping.  A
    later mapping failure is then cleaned up through the current chain, and
    a successful transmission uses that same chain for BPF and TX ownership.
    
    An unprivileged process can reach the EFBIG branch in mapped-sendfile
    mode.
    
    Signed-off-by: Andrew Griffiths <andrew@calif.io>
    
    Reviewed by:    adrian, markj
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D60142
---
 sys/dev/rge/if_rge.c | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/sys/dev/rge/if_rge.c b/sys/dev/rge/if_rge.c
index 7c11da5c3f53..89287450a01d 100644
--- a/sys/dev/rge/if_rge.c
+++ b/sys/dev/rge/if_rge.c
@@ -766,18 +766,20 @@ rge_tx_list_sync(struct rge_softc *sc, struct rge_queues *q,
  *
  * @param sc	driver softc
  * @param q	TX queue ring
- * @param m	mbuf to enqueue
+ * @param mp	mbuf to enqueue, updated if defragmentation replaces it
  * @returns	if the mbuf is enqueued, it's consumed here and the number of
  * 		TX descriptors used is returned; if there's no space then 0 is
- *		returned; if the mbuf couldn't be defragged and the caller
- *		should free it then -1 is returned.
+ *		returned; if the mbuf couldn't be mapped and the caller should
+ *		free it then -1 is returned.
  */
 static int
-rge_encap(struct rge_softc *sc, struct rge_queues *q, struct mbuf *m, int idx)
+rge_encap(struct rge_softc *sc, struct rge_queues *q, struct mbuf **mp,
+    int idx)
 {
 	struct rge_tx_desc *d = NULL;
 	struct rge_txq *txq;
 	bus_dmamap_t txmap;
+	struct mbuf *m;
 	uint32_t cmdsts, cflags = 0;
 	int cur, error, i;
 	bus_dma_segment_t seg[RGE_TX_NSEGS];
@@ -785,6 +787,7 @@ rge_encap(struct rge_softc *sc, struct rge_queues *q, struct mbuf *m, int idx)
 
 	RGE_ASSERT_LOCKED(sc);
 
+	m = *mp;
 	txq = &q->q_tx.rge_txq[idx];
 	txmap = txq->txq_dmamap;
 
@@ -800,10 +803,13 @@ rge_encap(struct rge_softc *sc, struct rge_queues *q, struct mbuf *m, int idx)
 	case EFBIG: /* mbuf chain is too fragmented */
 		sc->sc_drv_stats.tx_encap_refrag_cnt++;
 		nsegs = RGE_TX_NSEGS;
-		if (m_defrag(m, M_NOWAIT) == 0 &&
-		    bus_dmamap_load_mbuf_sg(sc->sc_dmat_tx_buf, txmap, m,
-		    seg, &nsegs, BUS_DMA_NOWAIT) == 0)
-			break;
+		m = m_defrag(m, M_NOWAIT);
+		if (m != NULL) {
+			*mp = m;
+			if (bus_dmamap_load_mbuf_sg(sc->sc_dmat_tx_buf, txmap,
+			    m, seg, &nsegs, BUS_DMA_NOWAIT) == 0)
+				break;
+		}
 		/* FALLTHROUGH */
 	default:
 		sc->sc_drv_stats.tx_encap_err_toofrag++;
@@ -2498,8 +2504,8 @@ rge_tx_task(void *arg, int npending)
 		if (m == NULL)
 			break;
 
-		/* Attempt to encap */
-		used = rge_encap(sc, q, m, idx);
+		/* Attempt to encap, m might change due to defrag */
+		used = rge_encap(sc, q, &m, idx);
 		if (used < 0) {
 			if_inc_counter(sc->sc_ifp, IFCOUNTER_OQDROPS, 1);
 			m_freem(m);