Explorar el Código

fix firewall port/proto bypass in parseV6 from uint8 extension-header length overflow (#1789)

Nate Brown hace 3 meses
padre
commit
c1eea118f4
Se han modificado 2 ficheros con 37 adiciones y 4 borrados
  1. 2 4
      outside.go
  2. 35 0
      outside_test.go

+ 2 - 4
outside.go

@@ -422,16 +422,14 @@ func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
 			if dataLen <= offset+1 {
 				break
 			}
-
-			next = int(data[offset+1]+2) << 2
+			next = (int(data[offset+1]) + 2) << 2
 
 		default:
 			// Normal ipv6 header length processing
 			if dataLen <= offset+1 {
 				break
 			}
-
-			next = int(data[offset+1]+1) << 3
+			next = (int(data[offset+1]) + 1) << 3
 		}
 
 		if next <= 0 {

+ 35 - 0
outside_test.go

@@ -640,3 +640,38 @@ func serializeAH(ah *layers.IPSecAH) []byte {
 
 	return buf.Bytes()
 }
+
+// Test_newPacket_v6ExtHeaderOverflow is a regression test for the IPv6 extension-header
+// length uint8 overflow in parseV6. A Destination-Options header with HdrExtLen=255 spans
+// (255+1)*8 = 2048 bytes, so the real transport header sits at offset 2088. Before the fix
+// the advance was computed in uint8 and wrapped to 0 (then clamped to 8), so the firewall
+// read the transport header ~2KB too early from attacker-controlled option bytes while the
+// host OS parses the real header, a firewall port/proto bypass. The fix makes parseV6 land
+// on the same offset the host does.
+func Test_newPacket_v6ExtHeaderOverflow(t *testing.T) {
+	p := &firewall.Packet{}
+
+	const (
+		hdrLen      = 40              // IPv6 header
+		extLen      = 2048            // (255+1)*8, the true Destination-Options header size
+		realTCPAt   = hdrLen + extLen // 2088, where the host reads the transport header
+		forgedTCPAt = hdrLen + 8      // 48, where the pre-fix wrapped+clamped walk landed
+	)
+
+	pkt := make([]byte, realTCPAt+4)
+	pkt[0] = 0x60                                   // version 6
+	pkt[6] = byte(layers.IPProtocolIPv6Destination) // NextHeader -> Destination Options
+	pkt[40] = byte(firewall.ProtoTCP)               // Dest-Options NextHeader -> TCP
+	pkt[41] = 255                                   // HdrExtLen = 255
+
+	// Forged transport header at the pre-fix (wrong) offset: dst port 443.
+	binary.BigEndian.PutUint16(pkt[forgedTCPAt+2:forgedTCPAt+4], 443)
+	// Real transport header at the offset the host actually uses: dst port 22.
+	binary.BigEndian.PutUint16(pkt[realTCPAt+2:realTCPAt+4], 22)
+
+	require.NoError(t, newPacket(pkt, true, p))
+	assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
+	// LocalPort is the destination port for incoming traffic. It must be the real port (22)
+	// the host delivers to, not the forged 443 at the overflowed offset.
+	assert.Equal(t, uint16(22), p.LocalPort, "firewall must parse the real transport header, not the overflowed offset")
+}