Heimdall security bug Fix Review (v0.6.0)

Summary

On January 23rd 2026 at 15 00 UTC the Polygon team rolled out Heimdall v2 release v0.6.0.

This version updates the CometBFT dependency to include the upstream Tachyon fix and addresses several additional security and liveness issues in the consensus reactor and Heimdall v2 application layer.

The main issues are

  1. Adoption of the CometBFT Tachyon fix for BFT Time timestamp handling CSA 2026 001

  2. A CometBFT fork level unbounded allocation in the consensus reactor on crafted ProposalMessage fields leading to validator out of memory and chain halt

  3. A Heimdall v2 bug in the fee withdraw workflow where MsgWithdrawFeeTx debited the wrong account format, freezing Heimdall to Ethereum fee exits

  4. A Heimdall v2 pubkey validation bug in the x/stake side transaction pipeline allowing malformed public keys to cause consensus failures

  5. Duplication and consolidation of critical security checks in side message handlers to harden the side transaction pipeline against a class of potential attacks

Given the size of the release, v0.6.0 also includes log refactoring, improved error management, better documentation and updated dependencies and CI configuration.

Root Cause

Issue 1: CometBFT BFT Time vulnerability Tachyon

The CometBFT project disclosed a consensus level vulnerability in its BFT Time implementation CSA 2026 001 Tachyon.

The problem stems from an inconsistency between

  1. How commit signatures are verified

  2. How the final block timestamp is derived from those votes

This breaks a core BFT Time guarantee that a faulty process cannot arbitrarily increase the block time value.

Any module or contract that relies on block timestamps can be impacted. Attacks can bias block times within the allowed consensus envelope in ways that violate protocol or application expectations.

Heimdall v2 relied on affected CometBFT versions prior to the patched releases. Without upgrading the underlying consensus engine Heimdall inherits the same timestamp level risk.

The CometBFT team fixed this in releases v0.38.21 and v0.37.18 by tightening the relationship between commit verification and time derivation.

Issue 2: unbounded allocation in CometBFT fork consensus reactor

In the Polygon CometBFT fork used by Heimdall v2 a remote unauthenticated peer was able to cause large memory allocations in the consensus reactor by sending a crafted ProposalMessage with an oversized BlobID.PartSetHeader.Total.

The receive path in the consensus reactor handled proposals roughly as follows

  1. On receiving a ProposalMessage it validated the block size via ValidateBlockSize

  2. It then called PeerState.SetHasProposal with the untrusted proposal

Inside SetHasProposal the code used proposal.BlobID.PartSetHeader.Total directly to allocate a BitArray for tracking blob parts.

This field was never bounded in the receive path

  1. Proposal.ValidateBasic only called BlobID.ValidateBasic

  2. BlobID.ValidateBasic and PartSetHeader.ValidateBasic checked hashes but did not bound Total

  3. A safe upper bound for blob parts existed in the consensus state logic but was checked only later after signature verification

As a result a single malicious ProposalMessage could force a receiving validator to allocate a BitArray proportional to an attacker chosen Total value. Large values for Total cause hundreds of megabytes of memory to be allocated per incoming proposal before any signature or proposer validation. With enough peers or repeated messages this can drive validators out of memory and halt block production when voting power drops below the required threshold.

Issue 3: MsgWithdrawFeeTx debits the wrong account, freezing exits

In Heimdall v2 the fee withdraw workflow in the x/topup module had a bug in MsgWithdrawFeeTx that debited the wrong type of address.

The proposer field in MsgWithdrawFeeTx is a string containing a hex encoded Ethereum address for the validator, for example 0x1111....

The handler used this field directly as an SDK account address via sdk.AccAddress(msg.Proposer). This function interprets the raw bytes of the string rather than decoding the hex address, so the resulting account is derived from the ASCII bytes of the string rather than the intended 20 byte address.

The vulnerable code was in x/topup/keeper/msg_server.go .

At the same time ValidateBasic only checked that the proposer string is a valid hex address, but did not actually decode or normalise it.

In practice this meant that Heimdall to Ethereum fee exits were effectively frozen until the handler logic was corrected and the affected state could be updated through a protocol level fix.

Issue 4: malformed pubkey chain halt in Heimdall v2

A liveness vulnerability existed in the x/stake side transaction pipeline that runs inside vote extensions.

Heimdall v2 treats staking public keys as Ethereum style uncompressed secp256k1 keys

  1. Total length sixty five bytes

  2. First byte equal to 0x04

  3. Remaining sixty four bytes representing X and Y coordinates

This is reflected in x/stake/types/keys.go and enforced by the Cosmos SDK fork via a panic in PubKey.Address when the key length is not exactly sixty five bytes.

The root cause was that MsgValidatorJoin and MsgSignerUpdate never enforced the expected length in their ValidateBasic methods. They only rejected

  1. Nil public keys

  2. Keys equal to a sixty five byte zero array

Any other non nil length including empty slices and very short byte sequences passed validation.

During vote extension execution the stake side handlers in x/stake/keeper/side_msg_server.go used these keys without guarding their length

  1. The MsgValidatorJoin side handler wrapped msg.SignerPubKey into a secp256k1.PubKey and immediately called Address

  2. The MsgSignerUpdate side handler sliced msg.NewSignerPubKey[0:1] and msg.NewSignerPubKey[1:] before constructing the secp256k1.PubKey and calling Address

  3. The helper IsPubKeyFirstByteValid also sliced [0:1] without checking length

If a malformed key reached these handlers they would panic either on the slice operations or inside PubKey.Address.

Heimdall ExtendVoteHandler wraps its logic in a recover block but then re panics on any recovered error, which turns side handler panics into consensus failures in CometBFT during ExtendVote. A single malformed staking transaction included in a proposal block could therefore cause validators to crash during ExtendVote, leading to quorum loss and a chain halt.

Issue 5: duplication and consolidation of checks in side message handlers

Heimdall v2 uses side transaction handlers to run module logic coordinated via vote extensions. In several modules the security checks in the primary message handlers were stricter than those in the side message handlers used during ExtendVote and PreBlocker.

This asymmetry created space for classes of bugs where

  1. The main transaction path enforced strong validation and invariants

  2. The side transaction path trusted data based on lighter checks and could approve state changes or persist signatures even when the main handler would have rejected the same payload

As part of v0.6.0 the team reviewed the side transaction messaging layer and duplicated or consolidated critical checks into the side message handlers, so that

  1. Side handlers enforce the same preconditions as the corresponding MsgServer handlers

  2. The vote extension pipeline cannot be used to bypass validations that apply to the normal transaction flow

Resolution and Recovery

Release v0.6.0 addresses these issues with a combination of dependency upgrades, application fixes and defensive hardening. The rollout was created and published on January 23rd 2026 at 15 00 UTC.

The main fixes are

  1. Adoption of patched CometBFT versions

    Heimdall v2 now depends on a CometBFT version that includes the Tachyon fix

    a) The BFT Time implementation has been updated in line with upstream to restore the guarantee that faulty processes cannot arbitrarily increase block time

    b) This removes the known timestamp level vulnerability for Heimdall deployments that upgrade to v0.6.0

  2. Bounded allocation in the consensus reactor

    The Polygon CometBFT fork has been updated so that

    a) Proposal.BlobID.PartSetHeader.Total is validated and bounded before any allocation based on it

    b) A safe maximum number of blob parts is derived from MaxBlobSizeBytes and PartSizeBytes and enforced in the consensus reactor receive path

    c) Oversized or malformed proposal messages are rejected without causing unbounded BitArray allocations

    This closes the unauthenticated P2P DoS vector described in the unbounded allocation report.

  3. Correct handling of MsgWithdrawFeeTx proposer address

    The x/topup MsgWithdrawFeeTx handler has been fixed so that

    a) The proposer field is decoded from its hex string representation into a 20 byte address using the same codec as the rest of the system

    b) All bank operations use the decoded address rather than sdk.AccAddress of the ASCII string

    c) The dividend account and dividend root are updated correctly on withdrawal

    Together with the other fixes in the dividend and checkpoint flow this allows the Heimdall to Ethereum fee exit path to operate as intended, with Merkle proofs generated against an up to date account state root.

  4. Strict staking pubkey validation and safe usage

    The staking and side transaction code has been updated so that

    a) MsgValidatorJoin and MsgSignerUpdate now enforce in ValidateBasic that public keys are exactly sixty five bytes long and not equal to the all zero key

    b) Side handlers and helpers that operate on staking public keys check length and prefix explicitly before slicing or calling Address

    c) Malformed keys are rejected before they reach ExtendVoteHandler, preventing pubkey related panics in consensus critical code paths

  5. Duplication and consolidation of side handler checks

    Critical security checks from the main message handlers have been duplicated and aligned in the side message handlers used by the side transaction framework

    a) Side handlers now perform the same validation of fields and invariants as their MsgServer counterparts

    b) Vote extension based approval cannot bypass the checks that guard the normal transaction path

    c) This significantly reduces the attack surface around side transactions, checkpoints and other ABCI workflows

  6. Test infrastructure and coverage improvements

    The existing test suite has been extended to cover

    a) The new CometBFT integration and bounds

    b) Fee withdrawal and dividend account updates

    c) Staking pubkey validation behaviour

    d) Side handler execution and error propagation

    These tests are intended to catch regressions in consensus critical behaviour early and provide additional assurance for future releases.

  7. Operational and codebase improvements

    Given the size and importance of v0.6.0, the release also includes general improvements aimed at maintainability and observability

    a) Logs refactoring to make critical paths easier to monitor and debug, with clearer messages and more consistent structure across modules

    b) Error management improvements, including clearer error propagation and handling in ABCI flows, side transaction pipelines and CometBFT integration points

    c) Documentation updates to reflect the latest behaviour of vote extensions, fee withdrawals, staking flows and the upgraded CometBFT integration

    d) Dependency updates and CI improvements to keep the codebase aligned with upstream libraries and to strengthen automated checks and test coverage for future changes

    e) A full suite of new unit tests to drastically increase code coverage

2 Likes