Skip to content

PCAP-NG obsolete Packet Block: 32-bit fields vs 32-octet arithmetic #345

Description

@JarryShaw

What is wrong

In PacketBlock — the obsolete Packet Block, 0x00000002 — the declared field widths and the option-area arithmetic describe two different layouts, so neither a spec-conformant block nor a block matching the declared widths parses.

pcapkit/protocols/schema/misc/pcapng.py:1648:

    length: 'int' = UInt32Field(callback=byteorder_callback)
    interface_id: 'int' = UInt32Field(callback=byteorder_callback)                      # line 1657
    drop_count: 'int' = UInt32Field(callback=byteorder_callback, default=0xFFFF)        # line 1659
    timestamp_high: 'int' = UInt32Field(callback=byteorder_callback)
    timestamp_low: 'int' = UInt32Field(callback=byteorder_callback)
    captured_length: 'int' = UInt32Field(callback=byteorder_callback)
    original_length: 'int' = UInt32Field(callback=byteorder_callback)
    packet_data: 'bytes' = PayloadField(length=lambda pkt: pkt['captured_length'])
    padding_data: 'bytes' = BytesField(length=lambda pkt: (4 - pkt['captured_length'] % 4) % 4)
    options: 'list[Option]' = OptionField(
        length=lambda pkt: pkt['length'] - 32 - pkt['captured_length'] - len(pkt['padding_data']),   # line 1674
        ...
    )

Both fields are 16-bit in the format. Appendix A of draft-ietf-opsawg-pcapng (Figure 19) puts them in one 32-bit word:

    8 |         Interface ID          |          Drops Count          |

with Packet Data starting at octet 28, so the fixed overhead including the trailing Block Total Length is 32 octets — which is precisely the constant line 1674 subtracts. With interface_id and drop_count declared as UInt32Field the overhead is 36, so the two disagree by four octets and every field from timestamp_high onward is read from the wrong offset.

drop_count's own default=0xFFFF is a further sign the 16-bit layout was intended: the spec reserves exactly that value ("The value xFFFF (in hexadecimal) is reserved for those systems in which this information is not available"), which is a 16-bit sentinel, not a 32-bit one.

Symptom

Both layouts fail, and confusingly — the warnings come first and the failure surfaces as an AttributeError from the protocol object rather than as a format error:

[WARNING] packet length < 0: -4
[WARNING] PCAP-NG: [Block 2] block length mismatch: 76 != 0
[WARNING] PCAP-NG: Packet Block has been obsolete! ...
AttributeError: 'PCAPNG' object has no attribute '_info'. Did you mean: 'info'?

(76 != 0 for the spec layout, 80 != 0 for the 32-bit layout; the AttributeError is identical.)

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)

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

# Appendix A layout: 16-bit Interface ID and Drops Count
pb16 = block(0x00000002, struct.pack('<HHIIII', 0, 0, 0, 0, len(ETH), len(ETH)) + pad4(ETH))
# the layout the schema declares: 32-bit Interface ID and Drops Count
pb32 = block(0x00000002, struct.pack('<IIIIII', 0, 0, 0, 0, len(ETH), len(ETH)) + pad4(ETH))

import pcapkit
for name, pb in (('pb16', pb16), ('pb32', pb32)):
    open(f'/tmp/{name}.pcapng', 'wb').write(shb() + idb() + pb)
    try:
        pcapkit.extract(fin=f'/tmp/{name}.pcapng', store=True, nofile=True)
    except Exception as exc:
        print(name, '->', type(exc).__name__, exc)

What a fix would need to touch

interface_id and drop_count at lines 1657 and 1659 become UInt16Field, which makes the existing - 32 at line 1674 correct. The corresponding _read_block_packet / _make_block_packet in pcapkit/protocols/misc/pcapng.py and the Data_PacketBlock model should be checked for the same assumption. The AttributeError: 'PCAPNG' object has no attribute '_info' is worth a look on its own — a malformed block ought to raise a format error rather than leaving a half-constructed protocol object (compare #289, which is the same symptom from a different cause).

The block is obsolete and MUST NOT appear in new files, so this is low-priority — but pcapkit claims to read it, and files containing it exist.

How it was found

While building deterministic sample captures for the test suite. The generators' module docstrings record it: examples/samples/pcapng.py lists the obsolete Packet Block among the block types deliberately left out of the fixtures, because neither layout parses.

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

    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