Skip to content

Latest commit

 

History

History
142 lines (107 loc) · 5.55 KB

081.md

File metadata and controls

142 lines (107 loc) · 5.55 KB

Scruffy Gingerbread Cormorant

High

Malicious proposer can delay block processing substantially by including malicious votes

Summary

Lack of limits on votes and correspnding BLS signature verification will cause delayed processing for all validators as a malicious proposer will include excessive invalid vote extensions requiring expensive cryptographic verification during ProcessProposal.

Root Cause

In https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/checkpointing/proposal.go#L271 , the ProcessProposal handler and its verification flow lack any limit on the number of vote extensions processed:

  1. When a validator receives a block proposal at epoch boundaries, the ProcessProposal() handler executes:

    // In ProcessProposal()
    injectedCkpt, err := h.ExtractInjectedCheckpoint(req.Txs)
  2. After extracting the checkpoint, it verifies vote extensions with baseapp.ValidateVoteExtensions(), which ensures 2/3 majority is achieved but doesn't protect against excessive signature processing:

    err = baseapp.ValidateVoteExtensions(ctx, h.ckptKeeper, req.Height, ctx.ChainID(), *injectedCkpt.ExtendedCommitInfo)
  3. The validator then rebuilds the checkpoint from the provided vote extensions:

    ckpt, err := h.buildCheckpointFromVoteExtensions(ctx, epoch.EpochNumber, injectedCkpt.ExtendedCommitInfo.Votes)
  4. Inside buildCheckpointFromVoteExtensions(), it first calls findLastBlockHash() which processes all votes:

    prevBlockID, err := h.findLastBlockHash(extendedVotes)
  5. Then it calls getValidBlsSigs() to filter valid signatures:

    validBLSSigs := h.getValidBlsSigs(ctx, extendedVotes, prevBlockID)
  6. The getValidBlsSigs() function iterates through every vote extension without any limit, skipping invalid ones:

    for _, voteInfo := range extendedVotes {
        veBytes := voteInfo.VoteExtension
        if len(veBytes) == 0 {
            h.logger.Error("received empty vote extension", "validator", voteInfo.Validator.String())
            continue
        }
        var ve ckpttypes.VoteExtension
        if err := ve.Unmarshal(veBytes); err != nil {
            h.logger.Error("failed to unmarshal vote extension", "err", err)
            continue
        }
    
        if !bytes.Equal(*ve.BlockHash, blockHash) {
            h.logger.Error("the BLS sig is signed over unexpected block hash",
                "expected", hex.EncodeToString(blockHash),
                "got", ve.BlockHash.String())
            continue
        }
    
        sig := ve.ToBLSSig()
    
        if err := k.VerifyBLSSig(ctx, sig); err != nil {
            h.logger.Error("invalid BLS signature", "err", err)
            continue
        }
    
        validBLSSigs = append(validBLSSigs, *sig)
    }
  7. After filtering valid signatures, it accumulates them until sufficient voting power is reached:

    for _, sig := range validBLSSigs {
        // [...]
        err = ckpt.Accumulate(vals, signerAddress, signerBlsKey, *sig.BlsSig, totalPower)
        // [...]
        // sufficient voting power is accumulated
        if ckpt.Status == ckpttypes.Sealed {
            break
        }
    }
  8. Critically, the filtering of valid signatures happens on the entire list of vote extensions before any accumulation occurs. This means all signatures must be verified.

  9. Every validator in the network must independently perform this full verification process, causing the attack impact and delay across the entire network.

This means a malicious proposer can include lots of fake votes with random BLS signatures and significantly delay block processing.

Internal Pre-conditions

NA

External Pre-conditions

NA

Attack Path

  1. Malicious proposer waits until they are selected to propose the first block of a new epoch
  2. In PrepareProposal(), proposer collects all legitimate vote extensions from the previous block
  3. Proposer creates thousands (or tens of thousands) of fake vote extension objects with invalid BLS signatures
  4. Proposer includes both legitimate and fake vote extensions in a MsgInjectedCheckpoint message:
    injectedCkpt := &ckpttypes.MsgInjectedCheckpoint{
        Ckpt:               ckpt,
        ExtendedCommitInfo: &req.LocalLastCommit,
    }
  5. Proposer packages this into an injected checkpoint transaction and includes it at index 0 in the block proposal:
    injectedVoteExtTx, err := h.buildInjectedTxBytes(injectedCkpt)
    proposalTxs = slices.Insert(proposalTxs, defaultInjectedTxIndex, [][]byte{injectedVoteExtTx}...)
  6. Proposer broadcasts this proposal to all validators in the network
  7. Every validator receives this proposal and executes ProcessProposal():
    • Extracts the injected checkpoint with ExtractInjectedCheckpoint()
    • Calls ValidateVoteExtensions() which passes because there are enough valid votes to reach 2/3
    • Calls buildCheckpointFromVoteExtensions() which processes all vote extensions
    • Each validator must verify all BLS signatures, including thousands of invalid ones
  8. The BLS signature verification (VerifyBLSSig) is computationally expensive:
    • Requires elliptic curve operations
    • Must be performed on every signature before valid ones can be identified
  9. Network block processing is significantly delayed
  10. The attack can be repeated whenever the malicious validator becomes the proposer at an epoch transition

Impact

Malicious proposer can delay the block processing substantially causing chain instability.

PoC

No response

Mitigation

Validate the number of votes present before doing more expensive checks.