tun: clear virtioNetHdr when deleting item from tcpGROTable

Otherwise the deleted packet gets written to the TUN fd with an invalid
virtioNetHdr.

Fixes: 9e2f386 ("conn, device, tun: implement vectorized I/O on Linux")
Signed-off-by: Jordan Whited <jordan@tailscale.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jordan Whited
2026-05-22 23:04:24 +02:00
committed by Jason A. Donenfeld
parent f333402bd9
commit ecfc5a8d54
2 changed files with 55 additions and 15 deletions
+7 -15
View File
@@ -608,6 +608,10 @@ func tcpGRO(bufs [][]byte, offset int, pktI int, table *tcpGROTable, isV6 bool)
case coalesceItemInvalidCSum:
// delete the item with an invalid csum
table.deleteAt(item.key, i)
// The deleted item will not be re-visited in applyTCPCoalesceAccounting,
// so we must zero the virtioNetHdr. clear() is the equivalent of
// encoding a zero value virtioNetHdr.
clear(bufs[item.bufsIndex][offset-virtioNetHdrLen : offset])
case coalescePktInvalidCSum:
// no point in inserting an item that we can't coalesce
return groResultNoop
@@ -667,11 +671,7 @@ func applyTCPCoalesceAccounting(bufs [][]byte, offset int, table *tcpGROTable) e
psum := pseudoHeaderChecksumNoFold(unix.IPPROTO_TCP, srcAddr, dstAddr, uint16(len(pkt)-int(item.iphLen)))
binary.BigEndian.PutUint16(pkt[hdr.csumStart+hdr.csumOffset:], checksum([]byte{}, psum))
} else {
hdr := virtioNetHdr{}
err := hdr.encode(bufs[item.bufsIndex][offset-virtioNetHdrLen:])
if err != nil {
return err
}
clear(bufs[item.bufsIndex][offset-virtioNetHdrLen : offset])
}
}
}
@@ -727,11 +727,7 @@ func applyUDPCoalesceAccounting(bufs [][]byte, offset int, table *udpGROTable) e
psum := pseudoHeaderChecksumNoFold(unix.IPPROTO_UDP, srcAddr, dstAddr, uint16(len(pkt)-int(item.iphLen)))
binary.BigEndian.PutUint16(pkt[hdr.csumStart+hdr.csumOffset:], checksum([]byte{}, psum))
} else {
hdr := virtioNetHdr{}
err := hdr.encode(bufs[item.bufsIndex][offset-virtioNetHdrLen:])
if err != nil {
return err
}
clear(bufs[item.bufsIndex][offset-virtioNetHdrLen : offset])
}
}
}
@@ -880,11 +876,7 @@ func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGR
}
switch result {
case groResultNoop:
hdr := virtioNetHdr{}
err := hdr.encode(bufs[i][offset-virtioNetHdrLen:])
if err != nil {
return err
}
clear(bufs[i][offset-virtioNetHdrLen : offset])
fallthrough
case groResultTableInsert:
*toWrite = append(*toWrite, i)
+48
View File
@@ -750,3 +750,51 @@ func Test_udpPacketsCanCoalesce(t *testing.T) {
})
}
}
func Test_handleGRO_invalidItemCsumClearsVirtioNetHdr(t *testing.T) {
pkts := [][]byte{
flipTCP4Checksum(tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1)),
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101),
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201),
}
// Poison the virtioNetHdr region of pkts[0] so a missing clear() is detectable.
for i := 0; i < virtioNetHdrLen; i++ {
pkts[0][i] = 0xAB
}
table := newTCPGROTable()
toWrite := make([]int, 0, len(pkts))
if err := handleGRO(pkts, virtioNetHdrLen, table, newUDPGROTable(), false, &toWrite); err != nil {
t.Fatal(err)
}
// Verify pkts[0] is in toWrite where we expect
if toWrite[0] != 0 {
t.Fatal("pkts[0] not found in toWrite at expected, zero index")
}
// Verify pkts[0] is not in tcpGROTable
if len(table.itemsByFlow) != 1 {
t.Fatalf("unexpected tcpGROTable items len: %d", len(table.itemsByFlow))
}
for _, v := range table.itemsByFlow {
if len(v) != 1 {
t.Fatalf("unexpected tcpGROItems slice len: %d", len(v))
}
item := v[0]
if item.sentSeq != 101 {
t.Fatalf("unexpected starting seq num in tcpGROTable: %d", item.sentSeq)
}
if item.numMerged != 1 {
t.Fatalf("unexpected numMerged in tcpGROTable: %d", item.numMerged)
}
}
// pkt 0 is in toWrite and not present in tcpGROTable, so its virtioNetHdr
// must have been cleared.
for i, b := range pkts[0][:virtioNetHdrLen] {
if b != 0 {
t.Fatalf("pkts[0] virtioNetHdr[%d] = 0x%x, want 0", i, b)
}
}
}