net/packet: fix filtering of short IPv4 fragments

The fragment offset is an 8 byte offset rather than a byte offset, so
the short packet limit is now in fragment block size in order to compare
with the offset value.

The packet flags are in the first 3 bits of the flags/frags byte, and
so after conversion to a uint16 little endian value they are at the
start, not the end of the value - the mask for extracting "more
fragments" is adjusted to match this byte.

Extremely short fragments less than 80 bytes are dropped, but fragments
over 80 bytes are now accepted.

Fixes #5727

Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker
2022-09-22 11:08:28 -07:00
committed by James Tucker
parent c66e15772f
commit 146f51ce76
2 changed files with 72 additions and 16 deletions

View File

@ -18,7 +18,7 @@ import (
const unknown = ipproto.Unknown
// RFC1858: prevent overlapping fragment attacks.
const minFrag = 60 + 20 // max IPv4 header + basic TCP header
const minFragBlks = (60 + 20) / 8 // max IPv4 header + basic TCP header in fragment blocks (8 bytes each)
type TCPFlag uint8
@ -152,11 +152,12 @@ func (q *Parsed) decode4(b []byte) {
// it as Unknown. We can also treat any subsequent fragment that starts
// at such a low offset as Unknown.
fragFlags := binary.BigEndian.Uint16(b[6:8])
moreFrags := (fragFlags & 0x20) != 0
moreFrags := (fragFlags & 0x2000) != 0
fragOfs := fragFlags & 0x1FFF
if fragOfs == 0 {
// This is the first fragment
if moreFrags && len(sub) < minFrag {
if moreFrags && len(sub) < minFragBlks {
// Suspiciously short first fragment, dump it.
q.IPProto = unknown
return
@ -216,7 +217,7 @@ func (q *Parsed) decode4(b []byte) {
}
} else {
// This is a fragment other than the first one.
if fragOfs < minFrag {
if fragOfs < minFragBlks {
// First frag was suspiciously short, so we can't
// trust the followup either.
q.IPProto = unknown