layers/geneve: uint8 offset overflow moves the inner-frame boundary 256 bytes — everything in the tunnel is decoded from the wrong bytes #9
Labels
No labels
core
cpu-dos
critical
dos
evasion
has-poc
high
integer-overflow
ip4defrag
layers
low
medium
memory-exhaustion
other
panic
pcapgo
pentest-2026-08
rce
tcpassembly
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
noi/gopacket#9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: high ·
layers/geneve.go:93,106-110A third defect in
Geneve.DecodeFromBytes, distinct from the two off-by-ones in #8. This one does not crash — it silently hands the wrong bytes to the inner decoder, which is worse for a monitoring tool.Mechanism
offsetis auint8. The options area starts at byte 8 andgn.OptionsLengthis(data[0] & 0x3f) * 4, so it ranges up to 252. The end of the options is therefore up to8 + 252 = 260— which does not fit in auint8.At 252 bytes of options
offsetwraps to260 - 256 = 4.gn.Contentsbecomesdata[:4]andgn.Payloadbecomesdata[4:], so the bytes handed to the inner decoder begin 256 bytes before the real encapsulated frame, in the middle of the Geneve option area.RFC 8926 gives Opt Len 6 bits, so 252 is the protocol's own maximum. This is not an exotic value — it is the top of the legal range.
Reproduction
A Geneve packet with 252 bytes of options carrying a real inner Ethernet frame at offset 260:
No error. No
SetTruncated.Optionsis populated correctly with all four options — the parse looks completely successful. Only the boundary is wrong.Impact
This is a clean tunnel evasion. Everything the attacker puts inside a Geneve tunnel becomes invisible to the analyser, while the tunnel endpoint decapsulates it correctly:
So it is not merely blinding, it is substitution — the same shape as #3, in a different subsystem.
Cost: setting the Opt Len field to
0x3fand padding to 252 bytes of options. One packet.Fix
Widen the cursor to a type that can hold the range, and check the end against the buffer:
Note the
length != 0check as well: options are 4-byte aligned andOptionsLengthis a multiple of 4, but nothing currently asserts that the options actually sum to the declared area, solengthcan go negative and the finaloffsetcan land past the declared end of the options.SerializeTohas the same shape (plen := int(gn.OptionsLength + 8)—uint8arithmetic, wraps forOptionsLength >= 248) and should be fixed alongside it.Worth a test at
OptionsLength = 252specifically; the existinggeneve_test.gofixtures use small option sets.Verified against
b7d9dbdon Go 1.24.4. PoCs:geneve,geneve2. Related: #8 (two off-by-one guards in the same function).