OptionField.unpack never returns for a well-formed HOPOPT header. Confirmed on
main (31b08b0ac) — 15 seconds of CPU with no return, interrupted by an alarm:
from pcapkit.protocols.internet.hopopt import HOPOPT
buf = bytes.fromhex('3b00' '0803810203' '00') # next=59, len=0; SMF_DPD len=3 + one Pad1
HOPOPT(buf, len(buf)) # never returns
Eight octets. Nothing malformed about it: len=0 means an 8-octet header, and the
6-octet option area holds an SMF_DPD option of length 3 followed by one Pad1.
Mechanism
Instrumenting the loop body:
step 0: length=6 code=SMF_DPD schema=SMFHashBasedDPDOption len(data)=5 stream advanced 6 -> length 1
step 1: length=1 code=Pad1 schema=PadOption len(data)=0 stream advanced 0 -> length 1
step 2..n: identical, forever
The loop decrements its remaining length by len(data), but len(data) is not the
number of octets consumed. On step 0 the unpack advanced the stream 6 octets while
reporting len(data) == 5, so length fell to 1 with the stream already exhausted.
From then on every iteration reads nothing, len(data) is 0, and length -= 0 makes
no progress.
The accounting mismatch comes from _SMFDPDOption.post_process returning the nested
schema, so len(data) measures the nested schema rather than the octets the wrapper
consumed. Why the wrapper over-reads by exactly one octet is a layer deeper, in
_SMFDPDOption's test-bit offsets, and is not traced here.
Note this rules out the obvious substitute fix: a file.tell() delta is not
equivalent to len(data) for the wrapper schemas whose post_process returns a nested
schema, which is the same root cause behind the len(data)/len(meta) accounting noted
on #427.
Why it matters
pcapkit is a parser fed untrusted input. A remote peer that can put an SMF_DPD option
in a Hop-by-Hop header can hang the process — no exception, no progress, no diagnostic.
Of everything surfaced in this round of work, this is the one I would fix first.
Suggested shape
The loop needs a progress guarantee independent of len(data) — the octets actually
consumed from the stream, or a check that each iteration advances and a
ProtocolError if it does not. The _SMFDPDOption over-read is a separate defect
underneath it and wants its own fix; a progress guard would turn the hang into a
diagnosable error either way.
Found while addressing review on #427. The mechanism first reported there — a lone
trailing Pad1 — was wrong: bytes([59, 0]) + bytes(6) parses fine. The bytes above
are the actual reproducer.
OptionField.unpacknever returns for a well-formed HOPOPT header. Confirmed onmain(31b08b0ac) — 15 seconds of CPU with no return, interrupted by an alarm:Eight octets. Nothing malformed about it:
len=0means an 8-octet header, and the6-octet option area holds an SMF_DPD option of length 3 followed by one
Pad1.Mechanism
Instrumenting the loop body:
The loop decrements its remaining
lengthbylen(data), butlen(data)is not thenumber of octets consumed. On step 0 the unpack advanced the stream 6 octets while
reporting
len(data) == 5, solengthfell to 1 with the stream already exhausted.From then on every iteration reads nothing,
len(data)is 0, andlength -= 0makesno progress.
The accounting mismatch comes from
_SMFDPDOption.post_processreturning the nestedschema, so
len(data)measures the nested schema rather than the octets the wrapperconsumed. Why the wrapper over-reads by exactly one octet is a layer deeper, in
_SMFDPDOption'stest-bit offsets, and is not traced here.Note this rules out the obvious substitute fix: a
file.tell()delta is notequivalent to
len(data)for the wrapper schemas whosepost_processreturns a nestedschema, which is the same root cause behind the
len(data)/len(meta)accounting notedon #427.
Why it matters
pcapkitis a parser fed untrusted input. A remote peer that can put an SMF_DPD optionin a Hop-by-Hop header can hang the process — no exception, no progress, no diagnostic.
Of everything surfaced in this round of work, this is the one I would fix first.
Suggested shape
The loop needs a progress guarantee independent of
len(data)— the octets actuallyconsumed from the stream, or a check that each iteration advances and a
ProtocolErrorif it does not. The_SMFDPDOptionover-read is a separate defectunderneath it and wants its own fix; a progress guard would turn the hang into a
diagnosable error either way.
Found while addressing review on #427. The mechanism first reported there — a lone
trailing
Pad1— was wrong:bytes([59, 0]) + bytes(6)parses fine. The bytes aboveare the actual reproducer.