pcapgo: readers size allocations from file-supplied lengths — 40-byte file → 1 GB alloc, and a 40-byte snoop file panics #10
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
noi/gopacket#10
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 ·
pcapgo/read.go:127-137,145-166,pcapgo/snoop.go:130,149,165Every
pcapgoreader sizes its packet buffer from a length field in the file.read.godoes bound that field — but only against two other fields from the same file, which is not a bound at all.Defect 1 —
read.go: the guard compares attacker input against attacker inputr.snaplenis read from bytes 16–20 of the file header and is never validated.ci.Lengthis read from the same 16-byte packet header asci.CaptureLength. Setting all three to the same large value satisfies both guards, and themakehappens beforeio.ReadFulldiscovers there is nothing to read.A 40-byte file — 24-byte file header plus one 16-byte packet header, no packet data at all — buys a 1 GB allocation.
CaptureLengthis anintfrom auint32, so on 64-bit the ceiling is 4 GB per packet, and the file can repeat the packet header to do it again.ZeroCopyReadPacketDatais worse: it caches the oversized buffer inr.packetBuf(make([]byte, snaplen)), so the peak allocation is retained for the life of the reader rather than being collectable after the failed read.ngread.go:538,566has the same shape driven by the Enhanced Packet Block's captured length and the Interface Description Block's snaplen.Defect 2 —
snoop.go: a negative length reachesmaker.padis derived by subtraction and is never checked for sign.RecordLength = 0withOriginalLength = 100givespad = -124, andCaptureLength + padis negative:maxCaptureLenboundsCaptureLengthbut nothing boundspad, so a largeRecordLengthis also an unbounded allocation on the same line.ZeroCopyReadPacketData(snoop.go:165) has both problems.Reachability — read this before rating it
In the daisy threat model the pcap files are written by our own
tcpdump, so a rival cannot set these fields and this is not remotely triggerable through the normal ingest path. That is why this is medium and not high.It becomes reachable if any of the following is true, and each is worth checking:
CaptureLengthread from a partially-written header);The library-level bug is real regardless: bounding a length field against another field from the same untrusted file is not a bound, and
pcapgois a general-purpose pcap reader whose callers will not all have a trusted-file threat model.Fix
Give the readers a real ceiling that does not come from the file:
and validate
snaplenonce when the file header is parsed, rather than trusting it as a bound for everything after.For
snoop.go, check the sign and the magnitude before themake:A cheap general mitigation for all of them: read into a growing buffer capped at the ceiling rather than allocating
CaptureLengthup front, so a file that lies about its size costs one failed read rather than a gigabyte.Verified against
b7d9dbdon Go 1.24.4. PoCs:pcapalloc,snoop.ngread.gowas not measured here — same shape, worth its own pass.Can this be exploited? Is there a way to reproduce this issue over the wire? Like someone would send the malicious packets to a service that's being dumped, then Daisy would parse it with gopacket. If not, close the issue.
Measured
ngread.go, which the original report left open. It is worse thanread.go.pcapgo/ngread.go:538:r.ci.CaptureLengthis the Enhanced Packet Block's 4-byte Captured Packet Length field (ngread.go:461), used with no validation at all — not even the weak snaplen/original-length comparisonread.goperforms. The Interface Description Block's snaplen is parsed and stored, and then never consulted on this path.An 80-byte file — a 28-byte Section Header Block, a 20-byte Interface Description Block declaring a perfectly ordinary 65535-byte snaplen, and a 32-byte Enhanced Packet Block header with no packet data behind it — produces a 1 GB allocation. The ceiling is 4 GB per block, and the file can repeat the block header to do it again.
Note the snaplen is honest here: a consumer that validates the IDB snaplen before trusting the file still gets hit, because the reader never compares the two. That makes this the more dangerous of the two readers.
ZeroCopyReadPacketData(ngread.go:566) does consult the interface snaplen, but only to pick the larger of it andCaptureLength:so it allocates the same amount and then caches it for the life of the reader.
One related observation that did not reproduce as a failure, recorded so it is not re-chased:
readBlockcomputesr.currentBlock.length = r.getUint32(r.buf[4:8]) - 8with no floor, so a block whose Total Length field is below 8 underflows to ~4.29e9. I built a file with an EPB of Total Length 0 and it terminated cleanly atEOFrather than misbehaving — the subsequent reads run out of file first. Worth adding the floor check anyway, since it is one line and the underflow is real:The
MaxPacketSizeceiling proposed in the issue body should be applied on this path too — it is the one that most needs it.PoC:
ngbombon branchpentest/2026-08-poc.