Clear the size of a packet consumed by the STUN handler

WireGuard reuses the sizes and endpoints slices across reads and skips a slot only when its size is below the minimum message size. A packet consumed as STUN left the previous read's size in place, so WireGuard processed the same buffer again under a stale length and endpoint.
This commit is contained in:
Viktor Liu
2026-08-04 17:25:45 +02:00
parent 7099f5907b
commit e950a9487f
2 changed files with 46 additions and 6 deletions
+10 -6
View File
@@ -229,6 +229,10 @@ func (s *ICEBind) createReceiverFn(pc wgConn.BatchReader, conn *net.UDPConn, rxO
if err != nil {
log.Debugf("failed to handle STUN packet from %s: %v", msg.Addr, err)
}
// WireGuard reuses sizes and eps across reads and only skips a slot whose size is
// below the minimum message size. Leaving a consumed slot untouched makes it
// process this buffer again under the previous packet's length and endpoint.
sizes[i] = 0
continue
}
sizes[i] = msg.N
@@ -366,12 +370,12 @@ func putMessages(msgs *[]ipv6.Message, msgsPool *sync.Pool) {
// isWireGuardMsg reports whether the packet carries a WireGuard message header: a little-endian
// uint32 message type in the range 1..4, which leaves the three bytes following the type byte zero.
//
// No STUN message can take that shape. The low byte of a STUN message type holds method and class
// bits and is non-zero for every method (Binding is 0x0001, 0x0101, 0x0111, 0x0011), so the two
// framings are disjoint and this test is exact rather than heuristic. That matters because
// stun.IsMessage only looks at the magic cookie, which in a WireGuard message overlaps the receiver
// index: a session whose index happens to equal the cookie would otherwise have all of its inbound
// data misrouted to the STUN handler until the next rekey.
// No STUN message that ICE exchanges can take that shape. The low byte of a STUN message type holds
// the bottom method bits and a class bit, and it is non-zero for Binding (0x0001, 0x0101, 0x0111,
// 0x0011) and for every other method pion implements, so the two framings do not overlap. That
// matters because stun.IsMessage only looks at the magic cookie, which in a WireGuard message
// overlaps the receiver index: a session whose index happens to equal the cookie would otherwise
// have all of its inbound data misrouted to the STUN handler until the next rekey.
func isWireGuardMsg(pkt []byte) bool {
if len(pkt) < 4 {
return false