Skip to content

NSE] iec61850mms.lua: fix infinite loop on malformed TPKT length] - #3494

Closed
Zasnit1 wants to merge 1 commit into
nmap:masterfrom
Zasnit1:fix-iec61850-infinite-loop
Closed

Zasnit1 wants to merge 1 commit into
nmap:masterfrom
Zasnit1:fix-iec61850-infinite-loop

Conversation

@Zasnit1

@Zasnit1 Zasnit1 commented Sep 16, 2026

Copy link
Copy Markdown

[NSE] iec61850mms.lua: fix infinite loop on malformed TPKT length

What

MMSDecoder:unpackmmsFromTPKT() in nselib/iec61850mms.lua walks the TPKT segments
of an MMS response with a loop that advances by TPKT_pos = TPKT_pos + TPKT_len, where
TPKT_len is the 16-bit length field taken directly from the response. The loop only
ends when it sees a COTP TPDU byte equal to 0x80.

If a target returns a segment whose TPKT length field is 0 (and whose COTP TPDU
byte is not 0x80), TPKT_pos never advances, the same bytes are re-read every
iteration, and the loop spins forever. There is also no check that the buffer actually
contains the TPKT+COTP headers being unpacked.

Why it matters

unpackmmsFromTPKT is reached from scripts/iec61850-mms.nse, which calls
decoder:unpackAndDecode(recv) on data received from the target (an IEC 61850 / MMS
device on TCP 102) with no surrounding pcall. A malicious or compromised device can
therefore hang the script. Because the loop is pure CPU with no I/O / yield, NSE's
cooperative scheduler has no point at which to regain control, so the hang can stall the
whole --script phase rather than just the one host. This is low-level severity in
reach (only the iec61850-mms script) but the impact — a remotely triggered hang — is
worse than a clean error.

Fix

Two guards in the loop:

  1. Before unpacking, verify the buffer holds the TPKT (4 bytes) + COTP (3 bytes)
    headers, so a truncated response can't read past the end of the string.
  2. On the non-last (advancing) path, reject a TPKT_len too small to move past the
    header (< 7), which is exactly the case that failed to make progress.

Well-formed single- and multi-segment responses parse exactly as before; the malformed
zero-length segment is now rejected (returns nil, consistent with the function's other
error paths) instead of looping forever.

Reproduction

Parsing a single TPKT+COTP segment with bytes 03 00 00 00 02 f0 00 … (TPKT length = 0,
COTP TPDU = 0x00) loops forever before this change and returns nil after it. A
well-formed segment (03 00 00 08 02 f0 80 …) and valid multi-segment responses are
unaffected.

Note

The same file has several while (newpos < pos + elen) TLV-parsing loops (around lines
~230–630) that are worth a look for the same "sub-element fails to advance" class of
non-termination; this PR addresses the TPKT loop specifically.

Reporter

Ibrahim Maqtoush (@Zasnit1)

@nnposter

Copy link
Copy Markdown

A more refactored version was committed as r39800. Thank you for contributing to Nmap.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants