⬢ DragonFlyBSD Kernel Audit
DF-3780 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/virtual/vmware/vmxnet3/if_vmx.c
+++ b/sys/dev/virtual/vmware/vmxnet3/if_vmx.c
@@ -1919,7 +1919,8 @@ static void
 vmxnet3_txq_eof(struct vmxnet3_txqueue *txq)
 {
 	struct vmxnet3_softc *sc;
 	struct ifnet *ifp;
+	struct vmxnet3_txbuf *txb;
 	struct vmxnet3_txring *txr;
 	struct vmxnet3_comp_ring *txc;
 	struct vmxnet3_txcompdesc *txcd;
@@ -1936,7 +1937,9 @@ vmxnet3_txq_eof(struct vmxnet3_txqueue *t
 	txc = &txq->vxtxq_comp_ring;
 
 	VMXNET3_TXQ_LOCK_ASSERT(txq);
 
+	u_int eop_next, dist, outstanding;
+
 	for (;;) {
 		txcd = &txc->vxcr_u.txcd[txc->vxcr_next];
 		if (txcd->gen != txc->vxcr_gen)
@@ -1946,17 +1949,35 @@ vmxnet3_txq_eof(struct vmxnet3_txqueue *t
 			txc->vxcr_gen ^= 1;
 		}
 
+		/*
+		 * DF-3780: txcd->eop_idx is host-supplied.  Only accept
+		 * completions whose end lands inside the window of
+		 * descriptors the guest actually has outstanding.
+		 */
+		eop_next = (txcd->eop_idx + 1) % txr->vxtxr_ndesc;
+		dist = (eop_next + txr->vxtxr_ndesc - txr->vxtxr_next) %
+		    txr->vxtxr_ndesc;
+		outstanding = (txr->vxtxr_head + txr->vxtxr_ndesc -
+		    txr->vxtxr_next) % txr->vxtxr_ndesc;
+		if (dist > outstanding)
+			break;
+
 		sop = txr->vxtxr_next;
 		txb = &txr->vxtxr_txbuf[sop];
 
 		if ((m = txb->vtxb_m) != NULL) {
 			bus_dmamap_sync(txr->vxtxr_txtag, txb->vtxb_dmamap,
@@ -1964,8 +1985,19 @@ vmxnet3_txq_eof(struct vmxnet3_txqueue *t
 			m_freem(m);
 			txb->vtxb_m = NULL;
 		}
 
-		txr->vxtxr_next = (txcd->eop_idx + 1) % txr->vxtxr_ndesc;
+		/*
+		 * Free every mbuf in the traversed window exactly once so
+		 * a forged eop_idx cannot strand packets on skipped SOP
+		 * slots (unbounded mbuf+cluster leak via the slot
+		 * overwrite in vmxnet3_txq_encap).
+		 */
+		while (txr->vxtxr_next != eop_next) {
+			txb = &txr->vxtxr_txbuf[txr->vxtxr_next];
+			if (txb->vtxb_m != NULL) {
+				bus_dmamap_sync(txr->vxtxr_txtag,
+				    txb->vtxb_dmamap, BUS_DMASYNC_POSTWRITE);
+				bus_dmamap_unload(txr->vxtxr_txtag,
+				    txb->vtxb_dmamap);
+				m_freem(txb->vtxb_m);
+				txb->vtxb_m = NULL;
+			}
+			if (++txr->vxtxr_next == txr->vxtxr_ndesc)
+				txr->vxtxr_next = 0;
+		}
 	}
 
 	if (txr->vxtxr_head == txr->vxtxr_next)
*** VERDICT NOTE: the window check (`dist > outstanding`) breaks the walk
*** on a forged completion; the watchdog (vmxnet3_tick) then re-inits the
*** queue, which is the intended recovery for a protocol-violating host.