Skip to content

PCAP-NG ISB option area sized 4 octets too long (length - 20) #342

Description

@JarryShaw

What is wrong

pcapkit/protocols/schema/misc/pcapng.py:1335 sizes the Interface Statistics Block's option area as pkt['length'] - 20:

class InterfaceStatisticsBlock(BlockType, code=Enum_BlockType.Interface_Statistics_Block):
    length: 'int' = UInt32Field(callback=byteorder_callback)
    interface_id: 'int' = UInt32Field(callback=byteorder_callback)
    timestamp_high: 'int' = UInt32Field(callback=byteorder_callback)
    timestamp_low: 'int' = UInt32Field(callback=byteorder_callback)
    options: 'list[Option]' = OptionField(
        length=lambda pkt: pkt['length'] - 20,      # <-- should be 24
        ...
    )
    padding: 'bytes' = PaddingField(length=lambda pkt: pkt['__option_padding__'])
    length2: 'int' = UInt32Field(callback=byteorder_callback)

The ISB's fixed fields occupy 24 octets, not 20. Per § 4.6 of draft-ietf-opsawg-pcapng (Figure 14) the Options field begins at octet 20 — Block Type (4), Block Total Length (4), Interface ID (4), Timestamp (8) — and the block also ends with a second Block Total Length (4), which pkt['length'] includes. So the option area is length - 24, and as written the field is told it may read 4 octets more than exist.

That matters because Schema.unpack advances the file by each field's declared length (pcapkit/protocols/schema/schema.py:630, byte = data.read(field.length)), so the over-declaration consumes the trailing Block Total Length, and the leftover is then reported through __option_padding__ and consumed again by padding. The block ends up read 8 octets past its end. The error is unconditional — an explicit opt_endofopt does not save it, because the field still reads its declared width off the file.

Symptom

Any capture containing an ISB produces, per block:

[WARNING] packet length < 0: -8
[WARNING] PCAP-NG: [Block 5] block length mismatch: 40 != 0

The -8 is exact: the schema reads length(4) + interface_id(4) + timestamp_high(4) + timestamp_low(4) + options(20, should be 16) + padding(4, the leftover reported through __option_padding__) + length2(4) = 44 octets after the block type, i.e. 48 for a 40-octet block. The trailing length is therefore read from 8 octets beyond the block and comes back as 0.

Reproduction

import struct

def pad4(b): return b + b'\x00' * ((4 - len(b) % 4) % 4)
def block(t, body):
    body = pad4(body); n = 12 + len(body)
    return struct.pack('<II', t, n) + body + struct.pack('<I', n)
def opt(c, v): return struct.pack('<HH', c, len(v)) + pad4(v)

shb = lambda: block(0x0A0D0D0A, struct.pack('<IHHq', 0x1A2B3C4D, 1, 0, -1))
idb = lambda: block(0x00000001, struct.pack('<HHI', 1, 0, 0x40000))
epb = lambda d: block(0x00000006, struct.pack('<IIIII', 0, 0, 0, len(d), len(d)) + pad4(d))
ETH = bytes.fromhex('ffffffffffff001122334455') + b'\x08\x06' + b'\x00' * 28

# ISB with isb_starttime (option 2) and an explicit opt_endofopt
isb = block(0x00000005, struct.pack('<III', 0, 0, 0) + opt(2, struct.pack('<II', 0, 0)) + opt(0, b''))
open('/tmp/isb.pcapng', 'wb').write(shb() + idb() + epb(ETH) + isb)

import pcapkit
pcapkit.extract(fin='/tmp/isb.pcapng', store=True, nofile=True)

What a fix would need to touch

Changing the constant from 20 to 24 at line 1335 is sufficient: patching that callback at runtime and re-parsing the fixture above clears both warnings, with nothing else changed.

The generators' docstring also records this reproducing on Wireshark's own many_interfaces.pcapng (from test/captures in the Wireshark tree), as packet length < 0: -8 and [Block 5] block length mismatch. I did not have that capture to hand, so I am repeating that second-hand rather than reporting it as observed; the minimal fixture above is what I ran.

How it was found

While building deterministic sample captures for the test suite; the generators' module docstrings record it. examples/samples/pcapng.py exercises the ISB on purpose, because it warns rather than raising and a fixture covering the code path is what will catch a future crash there.

Line numbers are from main at 72f950d.

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