Skip to content

pnio_dcp: compute block length and padding when building response blocks - #5233

Open
bIackr0se wants to merge 1 commit into
secdev:masterfrom
bIackr0se:fix/pnio-dcp-block-length-padding
Open

bIackr0se wants to merge 1 commit into
secdev:masterfrom
bIackr0se:fix/pnio-dcp-block-length-padding

Conversation

@bIackr0se

Copy link
Copy Markdown

Description

Building PROFINET DCP response blocks writes wrong block lengths and stray padding bytes:

  • DCPIPBlock, DCPFullIPBlock, DCPDeviceIDBlock, DCPDeviceOptionsBlock and DCPOEMIDBlock use LenField("dcp_block_length", None), which counts the payload. A block alone gets length 0, and a block stacked before other blocks gets their length.
  • DCPMACBlock and DCPManufacturerSpecificBlock use FieldLenField without length_of, so building raises Field should have either length_of or count_of.
  • DCPDeviceInitiativeBlock leaves BlockInfo out of its length (2 instead of 4).
  • The padding default b"\x00" is written after every block, also when the block length is even.
p = ProfinetDCP(service_id=DCP_SERVICE_ID_IDENTIFY, service_type=DCP_RESPONSE) / \
    DCPNameOfStationBlock(name_of_station="plc-1") / \
    DCPIPBlock(ip="192.168.0.10") / DCPDeviceIDBlock() / DCPDeviceRoleBlock()

Before, the IP block has length 20 and is followed by a zero byte, and Wireshark decodes the IP block and then reports [Malformed Packet: PN-DCP]. After, the block lengths are 7, 14, 6 and 4, the only padding byte follows the odd-length NameOfStation block, and Wireshark decodes all four blocks.

Fixed-size blocks now default to their constant length, as DCPDeviceRoleBlock already does, and variable-size blocks use FieldLenField with length_of, as DCPNameOfStationBlock does. padding defaults to b"" and is written as one zero byte only after an odd-length block; a value set by hand is cut or zero-filled to that length. DCPFullIPBlock now defaults to four DNS addresses, the number its dissector reads. Dissection is unchanged.

This message was written with the help of AI (Claude Opus 5.5).

DCP response blocks built with scapy had wrong lengths: LenField counts
the payload, so IP, Full IP, DeviceID, DeviceOptions and OEMID blocks got
0 or the length of the blocks stacked after them; the MAC and
ManufacturerSpecific blocks raised because their FieldLenField had no
length_of; DeviceInitiative left BlockInfo out. Every block also wrote
its b"\x00" padding default, also after an even-length block.

Fixed-size blocks now default to their constant length, variable-size
blocks count their value plus BlockInfo, and padding is one zero byte
after an odd-length block only. DCPFullIPBlock defaults to four DNS
addresses, the number its dissector reads.

AI-Assisted: yes (Claude Opus 5.5, GPT-6 Astra)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant