Skip to content

[Security][tentative] Medium (attackable): Typed Finalized Headers Can Bypass Signed Field Binding #261

@evonide

Description

@evonide

Context

Finalized headers carry consensus header fields together with a finalization certificate that should authenticate the header digest. Summit also exposes typed Rust structures and verifier helpers for checkpoint-chain validation, so those typed APIs form a trust boundary for integrations that operate on already-decoded values.

The intended invariant is that any finalized header accepted by the verifier has its public header fields bound to the certificate payload. This finding concerns the gap between decode-time validation and later typed-object use.

Claim

SSZ decoding verifies that a FinalizedHeader certificate signs the header digest, but the public typed struct and constructor do not preserve that invariant. verify_checkpoint_chain() accepts already-constructed FinalizedHeader values and trusts their mutable header fields without rechecking that the certificate payload still equals the header digest.

A malicious checkpoint supplier targeting a downstream typed-API integration can provide or induce an already-constructed FinalizedHeader with a valid finalization certificate but mutated public header fields. verify_checkpoint_chain() then trusts those unsigned fields because the payload/header binding was enforced only during SSZ decode.

Flow

The issue is reachable for typed API callers that bypass SSZ decode-time validation, including tests or integrations that construct FinalizedHeader directly or mutate its public fields after construction. It is not currently shown to be reachable through the stock startup checkpoint-file path, because that path decodes finalized headers with FinalizedHeader::from_ssz_bytes before verification.

Impact

A caller that treats verify_checkpoint_chain() as the trust boundary for untrusted typed artifacts can pass a valid finalization certificate paired with mutated header fields. In particular, checkpoint_hash can be changed to match attacker-controlled checkpoint data while the certificate still verifies against the old digest. Startup SSZ import is partially protected by decode-time checks, but the typed verifier API can still break trustless checkpoint validation for downstream callers or tests.

Root Cause

FinalizedHeader exposes mutable public fields and a constructor that does not bind finalization.proposal.payload to header.digest; the checkpoint verifier accepts typed values without recomputing that binding.

Code

Related Issues/PRs

Related issues cover adjacent signed-field, checkpoint, state-proof, and SSZ root binding gaps that can weaken finalized-header trust.

Fix

  • Make FinalizedHeader fields private or validate invariants in constructors.
  • Have verify_checkpoint_chain() recompute and enforce finalization.proposal.payload == header.digest for every typed header.
  • Prefer deriving trusted fields from the signed digest path rather than trusting mutable struct fields.
  • Add a negative test that mutates checkpoint_hash after finalization construction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions