Skip to content

A 16-bit padding shortfall band is still unbudgeted after #593: a crafted capture amplifies 1,637x, indistinguishable from a truncated one at the field layer #594

Description

@JarryShaw

#593 bounds the 32-bit padding band that #573 measured, and deliberately leaves a 16-bit band unbudgeted. A crafted capture still amplifies 1,637x after that fix, and this issue exists so that residual is not lost when #573 auto-closes.

What #593 closes, and what it does not

#573's measured vector was a 32-bit declared length — UnknownSecrets.data's secrets_length, up to _MAX_ZERO_PAD_LENGTH = 262,144 — at ~10,900x over 200 blocks. #593 ships the per-parse budget #573 asked for and takes that scenario to 54.6x, roughly 200x tighter. That gap is genuinely closed.

The residual is a different shape: 16-bit declared lengths repeated across many blocks. An 80,048-octet PCAP-NG of 2,000 Enhanced Packet Blocks, each with one option declaring 65,535 octets against four real ones, parses through Extractor(store=True) and synthesises 125.00 MiB of padding for 216.96 MiB of RSS — 1,637x, linear in block count, therefore unbounded in the input size. Measured before and after #593; unchanged by it.

Why #593 left it open, which is a real constraint rather than an omission

A shortfall of 65,536 octets or fewer is the whole span of a 16-bit wire length, so every such shortfall is one a snapshot-truncated capture, a truncated option area or an over-long ihl can legitimately produce. Concretely: a bare 40-octet IPv4 header declaring a total length of 65,535 — a legitimate offload-sized segment cut to a small snapshot — amplifies by the same ratio. Independently computed by two parties as 1637.375x (crafted) versus 1637.393x (legitimate).

So the two are not separable by any budget applied at the pcapkit/corekit/fields layer, because that layer cannot see whether truncation was declared. Telling them apart needs the frame's own incl_len / orig_len: a crafted block claims nothing was truncated while declaring more than it holds, whereas a genuinely truncated frame says so on the wire. That information lives at the protocol/extractor layer.

A naive single running budget is not an option either, and this was demonstrated rather than argued: with one budget over all padding, the same legitimate 54-octet frame declaring an IPv4 total length of 65,535 produced four different parse results across 40 byte-identical calls, refusing on calls 26, 33 and 39 and differing only by position, with beholder swallowing the refusal into a silently different Raw layer. A guard whose answer depends on parse history is worse than the amplification it prevents.

What a fix would need

  • Plumb the frame's declared-versus-actual length from the PCAP/PCAP-NG layer down to wherever padding is charged, so a shortfall inside a frame that declares truncation can be distinguished from one inside a frame that does not.
  • Keep the guard history-independent: byte-identical input must produce byte-identical results regardless of what was parsed before it.
  • Preserve the corekit: reject short dynamic field buffers #571 bar, which is the standing constraint on all of this: a legitimately truncated capture must still parse. corekit: reject short dynamic field buffers #571 was declined for proposing a len(buffer) < length rejection that broke exactly that.

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions