Skip to content

Move progressive list checks into BeaconBlock::from_ssz_bytes - #10098

Closed
michaelsproul wants to merge 2 commits into
sigp:unstablefrom
michaelsproul:check-progressive-list-in-block-decode
Closed

michaelsproul wants to merge 2 commits into
sigp:unstablefrom
michaelsproul:check-progressive-list-in-block-decode

Conversation

@michaelsproul

@michaelsproul michaelsproul commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Issue Addressed

We are vulnerable to all sorts of DoS nonsense and memory amplification due to ProgressiveList. This PR tries to take an aggressive approach to stop oversized ProgressiveLists making it out of Decode.

Proposed Changes

  • Check the progressive list length limits for BeaconBlock inside BeaconBlock::from_ssz_bytes.
  • Check progressive list length limits for ExecutionPayloadEnvelope during decoding (warning: slop).

Additional Info

Arguably one could still decode a BeaconBlockBody{Gloas} while bypassing the length check. Putting it one level up in BeaconBlock is convenient because we already have that method, and I have checked that production decoding always goes via this codepath (we almost always decode a SignedBeaconBlock, and any_from_ssz_bytes is only used by MockBeaconNode [we should probably delete it]).

Replaces:

@michaelsproul

Copy link
Copy Markdown
Member Author

I don't really like this approach. I think it's too fragile.

It doesn't make it easy to check that we don't accidentally bypass the length limit. There could be cases like serde decoding or manual construction, or weird piece-wise SSZ decoding that end up skipping the length checks.

I think the cleanest approach is probably going to be type-level length limits again, like:

I pushed the code for execution payloads in db2d410, and even though it's slop, I think it conveys the inherent complexity and fragility of this approach.

Comment on lines +81 to +107
impl<E: EthSpec> Decode for ExecutionPayloadEnvelope<E> {
fn is_ssz_fixed_len() -> bool {
false
}

fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
let mut builder = ssz::SszDecoderBuilder::new(bytes);
builder.register_type::<ExecutionPayloadGloas<E>>()?;
builder.register_type::<ExecutionRequestsGloas<E>>()?;
builder.register_type::<u64>()?;
builder.register_type::<Hash256>()?;
builder.register_type::<Hash256>()?;

let mut decoder = builder.build()?;
let envelope = Self {
payload: decoder.decode_next()?,
execution_requests: decoder.decode_next()?,
builder_index: decoder.decode_next()?,
beacon_block_root: decoder.decode_next()?,
parent_beacon_block_root: decoder.decode_next()?,
};
verify_execution_payload_list_lengths_post_gloas(ExecutionPayloadRef::Gloas(
&envelope.payload,
))?;
verify_execution_request_list_lengths_post_gloas(&envelope.execution_requests)?;
Ok(envelope)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fairly yuck IMO.

@michaelsproul

Copy link
Copy Markdown
Member Author

Closing in favour of:

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