mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 03:07:01 +02:00
fix firewall port/proto bypass in parseV6 from uint8 extension-header length overflow (#1789)
This commit is contained in:
+2
-4
@@ -422,16 +422,14 @@ func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
|
|||||||
if dataLen <= offset+1 {
|
if dataLen <= offset+1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
next = (int(data[offset+1]) + 2) << 2
|
||||||
next = int(data[offset+1]+2) << 2
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Normal ipv6 header length processing
|
// Normal ipv6 header length processing
|
||||||
if dataLen <= offset+1 {
|
if dataLen <= offset+1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
next = (int(data[offset+1]) + 1) << 3
|
||||||
next = int(data[offset+1]+1) << 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if next <= 0 {
|
if next <= 0 {
|
||||||
|
|||||||
@@ -640,3 +640,38 @@ func serializeAH(ah *layers.IPSecAH) []byte {
|
|||||||
|
|
||||||
return buf.Bytes()
|
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")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user