flows: Flow.FastHash is commutative by design — any tool keying connections on it merges four 4-tuples into one #5
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#5
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: medium (as a gopacket issue) / high for any caller that keys on it ·
flows.go:161-175This is
daisy-findings.mdfinding 8. Confirmed, and it collapses more than the two flows that document describes.Mechanism
The commutativity is deliberate, documented, and correct for its stated purpose — pinning both directions of a conversation to the same worker. gopacket is not doing anything wrong here in isolation.
The problem is that it is the obvious-looking thing to reach for when building a connection key, and the result is silently wrong. The common idiom is:
Because both halves are commutative, this collapses not two but four distinct 4-tuples onto one key:
A:49152→B:8080andA:8080→B:49152are two genuinely different connections. So areA:49152→B:8080andB:49152→A:8080. All four share a key, found with zero brute force — the attacker just picks their source port.Impact
An attribution primitive that works in both directions:
Pagainst service portQ, then also open a connection from source portQto service portP(or bind the mirrored pair). Both fold into one flow record; the exploit's messages are indistinguishable from the decoy's.Costs one extra connection.
What to change
In callers: stop keying on any hash. Key on the ordered tuple itself:
gopacket.Flowis a comparable struct, so amap[connKey]is exact and no slower in practice. If a canonical direction-insensitive key is genuinely wanted, canonicalise explicitly —min(endpoint)/max(endpoint)— rather than relying on a hash's collision behaviour to do it implicitly.In gopacket: the doc comment already warns the output "is not guaranteed to remain the same through future code revisions, so should not be used to key values in persistent storage." That warning is about stability, not collisions, and it reads as though in-memory keying is fine. Worth adding a sentence that says outright: this is not a connection identifier, and
A:p→B:qandA:q→B:pwill collide. AFlow.Key()or exported comparable-tuple helper would give callers the obvious right thing to reach for.Verified against
b7d9dbdon Go 1.24.4. PoC:flowsyn.Independent verification — reproduces in
gopacket/gopacket v1.7.0, including the exact reported hashConfirmed against the maintained fork. The
FastHashvalue matches the one in this issue byte for byte:(The key differs from the issue's
0x269c…only because the IP endpoints differ — the issue doesn't state which addresses it used. The collapse behaviour is identical, and the control with a single incremented source port separates correctly.)The idiom is not hypothetical
This issue frames the risk as "the obvious-looking thing to reach for". Worth recording that the consumer audited alongside it does exactly that, verbatim, as its connection key:
Same two-commutative-halves construction, same four-way collapse. It reached production without anyone noticing, which is the strongest argument for the severity split this issue proposes (medium for gopacket, high for the caller).
Suggested doc change
The doc comment is accurate about what
FastHashguarantees, but the guarantee is stated as a feature ("guaranteed to collide with its reverse flow") without a corresponding warning about what that rules out. A single added sentence would likely have prevented this:Also worth considering a
Flow.OrderedHash()(or exporting the endpoint bytes in a canonical order) so the correct thing is as easy to reach for as the wrong one. Right now every caller has to invent it.PoC
Verified on Go 1.24.4 against
gopacket/gopacket v1.7.0.