lnmsg: fix parsing of nested complex types where the leaf objects
What changed, and why it matters
This commit fixes a bug in Electrum's Lightning Network message parser. When reading nested structured data, the parser could keep consuming more bytes than intended because it didn't stop after reaching the expected number of items. The fix makes it stop at the right count and raises an error if too few items are found. This is a correctness fix that likely prevents malformed or crafted Lightning messages from being misinterpreted.
Treat as a security-relevant correctness fix. Review whether the over-consumption could have led to state corruption, incorrect routing/HTLC handling, or denial of service via malformed Lightning messages. Consider requesting a CVE if further analysis shows remote exploitability. Users should upgrade to a version containing this fix.
Security signals we found
Parser over-consumption bug in network message deserialization
Nested complex type boundary enforcement added
UnexpectedEndOfStream validation added for under-read scenarios
Lightning Network protocol message parsing code affected
Evidence from the diff
In electrum/lnmsg.py, LNSerializer.read_tlv_stream (or a related parsing helper) iterates reading subtyped/structured fields. Previously, when a fixed ‘count’ was supplied, the loop did not break after reaching that count, so nested complex types could over-consume from the file descriptor. The patch adds an explicit break when len(parsedlist) == count and raises UnexpectedEndOfStream if the loop ends without reaching count. This enforces expected item counts and prevents trailing bytes of a nested structure from being incorrectly parsed as additional items.
Changed components
electrum/lnmsg.pyLNSerializer parsing methodsLightning Network message deserializationInspect captured patch +8 / −0
diff --git a/electrum/lnmsg.py b/electrum/lnmsg.py
index 7faa737..5996b9e 100644
--- a/electrum/lnmsg.py
+++ b/electrum/lnmsg.py
@@ -580,6 +580,14 @@ class LNSerializer:
count=subtype_field_count)
parsedlist.append(parsed)
+ # fd might contain more bytes, but we got passed a count. break when we have 'count' items.
+ # (e.g. nested complex types)
+ if isinstance(count, int) and len(parsedlist) == count:
+ break
+
+ if isinstance(count, int) and len(parsedlist) != count:
+ raise UnexpectedEndOfStream(f"Expected {count} items, but found only {len(parsedlist)}.")
+
return parsedlist if count == '...' or count > 1 else parsedlist[0]
def write_tlv_stream(self, *, fd: io.BytesIO, tlv_stream_name: str, signing_key: Optional[bytes] = None, **kwargs) -> None:
Why this scored 57/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.