fix(ssz): reject zero first offset in variable-size list decoder#1177
Merged
tcoratger merged 1 commit intoJul 4, 2026
Merged
Conversation
The variable-size list decoder validated that the first offset was in range and aligned, but never that it was at least one offset word wide. A first offset of zero slipped through: zero is a multiple of the offset width and below any positive scope. That value is contradictory. The element count computes as zero (an empty list) while the boundary list becomes [0, scope], which describes one element spanning the whole scope. The decoder only failed closed by accident, through a per-element short read deeper in the stack. For a wire format that must decode identically byte-for-byte across clients, the malformed offset must be rejected up front. Fold a lower-bound clause into the existing offset check so a first offset below one offset word, zero included, is rejected before the boundary list is built. This mirrors the vector decoder, which already requires its first offset to equal the fixed-part width. Closes leanEthereum#1175 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The
SSZListvariable-size decoder now rejects a first offset below one offset word (four bytes), which closes thefirst_offset == 0gap reported in #1175.The bug
The decoder validated that the first offset was in range (
first_offset > scope) and aligned (first_offset % 4 != 0), but never that it was at least one offset word wide. A first offset of zero slipped through both checks: zero is a multiple of four, and it is below any positive scope.That value is contradictory:
The count says zero while the boundary says one. The decoder then tries to parse an element spanning the whole scope while believing the count is zero. Today the input does fail — but only by accident, through a per-element short read deeper in the stack, not because the malformed offset was rejected.
For a wire format that must decode identically byte-for-byte across clients, this must fail closed by construction. consensus-specs lists a first offset with a mismatching minimum element size as something a decoder MUST harden against.
The fix
Fold a lower-bound clause into the existing offset check so a first offset below one offset word — zero included — is rejected before the boundary list is built:
This mirrors the vector decoder in the same module, which already requires its first offset to equal the fixed-part width, and the container decoder, which requires its first offset to equal the fixed-part end.
Test
Added a regression test that decodes the exact payload from the issue (
00000000aabbccdd) and asserts the complete error message with string equality, so the message cannot drift unnoticed.Credit
Found via the Lean 4 formalization of this spec (NyxFoundation/formal-leanSpec), examining the decode direction of SSZ round-tripping.
Closes #1175
🤖 Generated with Claude Code