diff --git a/iputil/packet.go b/iputil/packet.go index ce9eeb93..88f72dcc 100644 --- a/iputil/packet.go +++ b/iputil/packet.go @@ -351,8 +351,8 @@ func ipv6CreateRejectTCPPacket(packet []byte, out []byte, offset int) []byte { // 253/254, and real upper layer protocols like SCTP or GRE, is terminal. Walking those as extension headers // is a firewall bypass, so they fail closed. For a non-first fragment the returned protocol is the fragmented // protocol and offset points at the fragment header, there is no transport header to locate. Returns -// ErrIPv6CouldNotFindPayload if packet is smaller than an ipv6 header or the chain is truncated before a -// terminal protocol is reached. +// ErrIPv6CouldNotFindPayload if packet is smaller than an ipv6 header, or the chain is truncated or longer +// than the walk's budget before a terminal protocol is reached. func IPv6FindUpperProtocol(packet []byte) (nextHeader uint8, offset int, isFragment bool, anyFragment bool, err error) { const maxIPv6ExtHeaders = 8 if len(packet) < ipv6.HeaderLen { @@ -398,7 +398,9 @@ func IPv6FindUpperProtocol(packet []byte) (nextHeader uint8, offset int, isFragm return nextHeader, offset, isFragment, anyFragment, nil } } - return nextHeader, offset, isFragment, anyFragment, nil + // Budget spent without reaching a terminal protocol. We don't know where the transport starts, so fail + // closed instead of handing back a mid-chain offset a caller would read ports from. + return nextHeader, offset, isFragment, anyFragment, ErrIPv6CouldNotFindPayload } func CreateICMPEchoResponse(packet, out []byte) []byte { diff --git a/iputil/packet_test.go b/iputil/packet_test.go index 3baaef21..38c5da10 100644 --- a/iputil/packet_test.go +++ b/iputil/packet_test.go @@ -529,6 +529,17 @@ func Test_IPv6FindUpperProtocol(t *testing.T) { firstFragToUDP := []byte{17, 0, 0, 1, 0, 0, 0, 1} // frag offset 0, M=1, next = UDP nonFirstFrag := []byte{17, 0, 0, 9, 0, 0, 0, 1} // frag offset non-zero, next = UDP transport := []byte{0, 80, 1, 187, 0, 0, 0, 0} // stand-in bytes, IPv6FindUpperProtocol never reads ports + extToHbH := []byte{0, 0, 0, 0, 0, 0, 0, 0} // len 0 -> 8 bytes, next = Hop-by-Hop + + // 8 walked headers spend the budget before the 9th slot's terminal is ever classified. This is the + // report 4040546 shape: without failing closed the walk hands back offset 104, and a caller reads ports + // from the attacker's bytes there. + var budgetChain []byte + for i := 0; i < 7; i++ { + budgetChain = append(budgetChain, extToHbH...) + } + budgetChain = append(budgetChain, extToTCP...) + budgetChain = append(budgetChain, transport...) tests := []struct { name string @@ -552,6 +563,7 @@ func Test_IPv6FindUpperProtocol(t *testing.T) { {"truncated extension header", 0, nil, 0, ipv6.HeaderLen, false, false, ErrIPv6CouldNotFindPayload}, // Destination Options with a declared length (255+1)*8 = 2048 that runs past the 48 byte buffer, next = SCTP {"extension length past buffer", 60, []byte{132, 255, 0, 0, 0, 0, 0, 0}, 132, ipv6.HeaderLen + 2048, false, false, ErrIPv6CouldNotFindPayload}, + {"budget exhausted fails closed", 0, budgetChain, 0, 0, false, false, ErrIPv6CouldNotFindPayload}, } for _, tt := range tests {