net/packet,wgengine/filter: handle IPv6 fragment extension header
decode6 didn't parse the IPv6 Fragment extension header (Next Header 44), so any source-fragmented IPv6 packet was classified as an unknown protocol and matched no ACL rule. The filter then silently dropped it and counted it as an "acl" drop, even on allow-all tailnets, blackholing large UDP (DNS, WebRTC, etc.) over a tailnet's IPv6 addresses. IPv4 fragments were already handled by decode4. Parse the fragment header the same way: read the first fragment's transport ports so the filter matches it like an unfragmented packet, pass later fragments through as ipproto.Fragment, and reject overlapping-fragment offsets (RFC 1858) and first fragments too short to hold the transport header as unknown. Fixes #20083 Signed-off-by: Steve Avery <hello@stevenavery.com>
This commit is contained in:
committed by
James Tucker
parent
f002f6bb3a
commit
4c4ec3d468
@@ -232,6 +232,131 @@ var udp6RequestDecode = Parsed{
|
||||
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:443"),
|
||||
}
|
||||
|
||||
// First fragment of a source-fragmented UDP datagram over IPv6.
|
||||
//
|
||||
// The base header's next-header is 44 (IPv6 Fragment extension header),
|
||||
// which carries the real upper-layer protocol (UDP) and the fragment
|
||||
// offset (0 here, with the More-Fragments flag set). Like decode4's
|
||||
// first-fragment handling, decode6 should reach past the 8-byte fragment
|
||||
// header and parse the transport ports.
|
||||
var udp6FirstFragmentBuffer = []byte{
|
||||
// IPv6 header up to hop limit. Next header = 44 (Fragment), payload len = 24.
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2c, 0x40,
|
||||
// Src addr
|
||||
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
|
||||
// Dst addr
|
||||
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
|
||||
// Fragment extension header (8 bytes):
|
||||
// NextHeader=UDP(0x11), Reserved, FragmentOffset=0 + M flag, Identification.
|
||||
0x11, 0x00, 0x00, 0x01, 0xde, 0xad, 0xbe, 0xef,
|
||||
// UDP header
|
||||
0xd4, 0x04, 0x01, 0xbb, 0x00, 0x29, 0x96, 0x84,
|
||||
// Payload (start of the datagram, carried by the first fragment)
|
||||
0x5c, 0x06, 0xae, 0x85, 0x02, 0xf5, 0xdb, 0x90,
|
||||
}
|
||||
|
||||
var udp6FirstFragmentDecode = Parsed{
|
||||
b: udp6FirstFragmentBuffer,
|
||||
subofs: 48, // 40-byte IPv6 header + 8-byte fragment extension header
|
||||
dataofs: 56, // subofs + 8-byte UDP header
|
||||
length: len(udp6FirstFragmentBuffer),
|
||||
|
||||
IPVersion: 6,
|
||||
IPProto: UDP,
|
||||
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:54276"),
|
||||
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:443"),
|
||||
}
|
||||
|
||||
// A non-first fragment over IPv6: it carries a fragment header at a
|
||||
// nonzero offset and no transport header, so its ports are unknown. Like
|
||||
// decode4, decode6 should classify it as ipproto.Fragment so the filter's
|
||||
// pre() pass-through path accepts it.
|
||||
var udp6NonFirstFragmentBuffer = []byte{
|
||||
// IPv6 header up to hop limit. Next header = 44 (Fragment), payload len = 16.
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2c, 0x40,
|
||||
// Src addr
|
||||
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
|
||||
// Dst addr
|
||||
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
|
||||
// Fragment extension header (8 bytes):
|
||||
// NextHeader=UDP(0x11), Reserved, FragmentOffset=185 (0x05c8>>3) + M=0, Identification.
|
||||
0x11, 0x00, 0x05, 0xc8, 0xde, 0xad, 0xbe, 0xef,
|
||||
// Payload continuation (no transport header in a non-first fragment)
|
||||
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
|
||||
}
|
||||
|
||||
var udp6NonFirstFragmentDecode = Parsed{
|
||||
b: udp6NonFirstFragmentBuffer,
|
||||
subofs: 48, // past the fragment extension header, at the continued payload
|
||||
dataofs: 0, // no sub-protocol header present
|
||||
length: len(udp6NonFirstFragmentBuffer),
|
||||
|
||||
IPVersion: 6,
|
||||
IPProto: Fragment,
|
||||
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:0"),
|
||||
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:0"),
|
||||
}
|
||||
|
||||
// A first fragment (offset 0) truncated before the full transport header,
|
||||
// so the ports can't be read. Like decode4's tcp4ShortFragment case, this
|
||||
// must be rejected as Unknown rather than guessed at: a stateless filter
|
||||
// that trusted such a fragment could be bypassed by a follow-up fragment
|
||||
// that supplies the rest of the header (RFC 1858).
|
||||
var udp6ShortFirstFragmentBuffer = []byte{
|
||||
// IPv6 header. Next header = 44 (Fragment), payload len = 12.
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2c, 0x40,
|
||||
// Src addr
|
||||
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
|
||||
// Dst addr
|
||||
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
|
||||
// Fragment extension header: NextHeader=UDP, Reserved, Offset=0 + M=1, Identification.
|
||||
0x11, 0x00, 0x00, 0x01, 0xde, 0xad, 0xbe, 0xef,
|
||||
// Truncated UDP header: only 4 of the required 8 bytes.
|
||||
0xd4, 0x04, 0x01, 0xbb,
|
||||
}
|
||||
|
||||
var udp6ShortFirstFragmentDecode = Parsed{
|
||||
b: udp6ShortFirstFragmentBuffer,
|
||||
subofs: 48, // header walk reaches past the fragment header...
|
||||
dataofs: 0, // ...but the transport header is incomplete, so it's unknown
|
||||
length: len(udp6ShortFirstFragmentBuffer),
|
||||
|
||||
IPVersion: 6,
|
||||
IPProto: Unknown,
|
||||
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:0"),
|
||||
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:0"),
|
||||
}
|
||||
|
||||
// A non-first fragment whose offset is small enough that its bytes could
|
||||
// overlap the transport header on reassembly. Mirrors decode4's
|
||||
// ipv4SmallOffsetFragment: reject as Unknown to prevent overlapping-fragment
|
||||
// firewall bypass (RFC 1858), rather than passing it through as Fragment.
|
||||
var udp6SmallOffsetFragmentBuffer = []byte{
|
||||
// IPv6 header. Next header = 44 (Fragment), payload len = 16.
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2c, 0x40,
|
||||
// Src addr
|
||||
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
|
||||
// Dst addr
|
||||
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
|
||||
// Fragment extension header: NextHeader=UDP, Reserved,
|
||||
// Offset=1 block (8 bytes, below the safe minimum) + M=0, Identification.
|
||||
0x11, 0x00, 0x00, 0x08, 0xde, 0xad, 0xbe, 0xef,
|
||||
// Payload continuation that could overlap the transport header.
|
||||
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
|
||||
}
|
||||
|
||||
var udp6SmallOffsetFragmentDecode = Parsed{
|
||||
b: udp6SmallOffsetFragmentBuffer,
|
||||
subofs: 48,
|
||||
dataofs: 0,
|
||||
length: len(udp6SmallOffsetFragmentBuffer),
|
||||
|
||||
IPVersion: 6,
|
||||
IPProto: Unknown,
|
||||
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:0"),
|
||||
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:0"),
|
||||
}
|
||||
|
||||
var udp4ReplyBuffer = []byte{
|
||||
// IP header up to checksum
|
||||
0x45, 0x00, 0x00, 0x29, 0x21, 0x52, 0x00, 0x00, 0x40, 0x11, 0x49, 0x5f,
|
||||
@@ -567,6 +692,10 @@ func TestDecode(t *testing.T) {
|
||||
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
|
||||
{"ipv4_sctp", sctpBuffer, sctpDecode},
|
||||
{"ipv4_frag", tcp4MediumFragmentBuffer, tcp4MediumFragmentDecode},
|
||||
{"ipv6_frag_first", udp6FirstFragmentBuffer, udp6FirstFragmentDecode},
|
||||
{"ipv6_frag_nonfirst", udp6NonFirstFragmentBuffer, udp6NonFirstFragmentDecode},
|
||||
{"ipv6_frag_short_first", udp6ShortFirstFragmentBuffer, udp6ShortFirstFragmentDecode},
|
||||
{"ipv6_frag_small_offset", udp6SmallOffsetFragmentBuffer, udp6SmallOffsetFragmentDecode},
|
||||
{"ipv4_fragtooshort", tcp4ShortFragmentBuffer, tcp4ShortFragmentDecode},
|
||||
{"ipv4_short_first_fragment", ipv4ShortFirstFragmentBuffer, ipv4ShortFirstFragmentDecode},
|
||||
{"ipv4_small_offset_fragment", ipv4SmallOffsetFragmentBuffer, ipv4SmallOffsetFragmentDecode},
|
||||
|
||||
Reference in New Issue
Block a user