Skip to content

Unbounded allocation in FieldBase.unpack: a wire-declared length drives rjust() with no bound #554

Description

@JarryShaw

pcapkit/corekit/fields/field.py:240-244:

length = self.length

if not isinstance(buffer, bytes):
    buffer = buffer.read(length)
value = struct.unpack(self.template, buffer[:length].rjust(length, b'\x00'))[0]

length derives from the field's configured length, which for several schemas is a callable resolved against the packet under parse — so it can be attacker-influenced by a length written on the wire. rjust(length, b'\x00') then allocates length bytes regardless of how much data actually arrived.

The consequence is that a small crafted input can force a very large allocation. A roughly 40-octet PCAP-NG Decryption Secrets Block is enough to drive a multi-gigabyte rjust, because the block's own declared length feeds the field length while the file supplies almost no bytes.

This is the only memory-safety-shaped finding from a sweep of deferred work across this release's 68 merged PRs, and it was never raised in review.

Why it is worth a real fix rather than a cap

pcapkit parses untrusted capture files by design — that is the whole use case — so "the input is hostile" is the normal condition rather than an edge case. A plain upper bound would stop the allocation but silently truncate legitimate large fields; better is to refuse when the declared length exceeds the bytes actually available, which is a genuine format error and already has an in-library exception for it.

Note only in-library exceptions from pcapkit.utilities.exceptions should be used, and FieldValueError is the existing precedent for an impossible field value.

Coverage

A test that feeds a short buffer declaring a huge length and asserts a bounded in-library exception rather than an allocation. It must not actually attempt the allocation when run against the fixed code, and should be proven to fail — by raising MemoryError or hanging under a timeout — against the current code.

Activity

  1. JarryShaw commented on Sep 21, 2026

    @JarryShaw
    OwnerAuthor

    Fixed, in two stages, and ready to close.

    Both constants are present in main at fa6d18e31.

    This issue stayed open after the first fix only because #569's body referenced it as a bare #554 rather than Fixes #554, so GitHub never closed it. Every pull request in the batch since has carried the keyword explicitly and closed its issue within a second of merging.

    One residual is deliberately left open as #594, and it is not part of this issue's scope: a 16-bit shortfall band is padded unconditionally, so a crafted capture still amplifies 1,637x. It stayed open because a legitimately truncated offload frame amplifies by the same ratio — 1637.375x crafted versus 1637.393x legitimate — so nothing inside pcapkit/corekit/fields can separate them. Distinguishing them needs the frame's own incl_len/orig_len, which lives at the PCAP-NG layer. #594 records the measurement and what a real fix would require.

  2. JarryShaw commented on Sep 21, 2026

    @JarryShaw
    OwnerAuthor

    Closing: the fix shipped in 4c0bcf9b7 (#569) and 13fd1860c (#593), both verified present in main at fa6d18e31. The residual 16-bit band is tracked separately as #594.

  3. added
    bugIssues reporting a defect (set by the bug report template; a default, not an assessment)
    on Sep 22, 2026
  4. added this to the 1.5 milestone on Oct 6, 2026
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

    bugIssues reporting a defect (set by the bug report template; a default, not an assessment)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions