Heimdall Security Bug Fix Review

Summary

A Critical Security bug on Heimdall was previously reported and confirmed via the Immunefi Bug Bounty Program. Under highly specific – and very unlikely – conditions being met, the bug could have potentially allowed a malicious validator (rogue or compromised) to take control of the network via Super Majority.

Root Cause

The bug lay in the UnpackLog method, which is a utility method for parsing certain events emitted on L1 contracts (Staking, StateSender, etc.) and executing them on Heimdall. The problem was that the function didn’t check the event type that it was parsing and thus, could errantly act to parse an event that was never emitted on Ethereum. So for example,

  • A potential malicious actor could do a signer change operation that would lead to SignerChange event being emitted on L1.
  • Based on that event, a potential malicious actor proposes a MsgStakeUpdate transaction on Heimdall. The potential malicious actor would use the transaction hash and log index of the SignerChange event on L1 (see this for how a MsgStakeUpdate is proposed).
  • The UnpackLog method in Heimdall would incorrectly parse the data in SignerChange to MsgStakeUpdate as it wouldn’t check the provided event’s signature, i.e it would set the following:
    • MsgStakeUpdate.NewAmount = SignerChange.NewSigner
    • MsgStakeUpdate.ID = SignerChange.ValidatorID
    • MsgStakeUpdate.Nonce = SignerChange.OldSigner
  • The above would cause the validator’s stake to increase dramatically.

This could’ve been done with a combination of various L1 contract events and stake update operations, albeit it would have been extremely difficult to execute and the conditions for it to occur are highly improbable due to, among other things, restrictions of data types (Nonce being uint64) and Nonce value being verified corresponding to the value stored in Heimdall DB (see this clause). This is especially relevant as it basically forces the potentially malicious actor to guess the private key of SignerChange.OldSigner that has to be matching that exact expected Nonce during the tx execution. Having an exact match of an Ethereum address (20 bytes) seems to be impossible, however the uint64 casting implemented at Heimdall would reduce the required bytes to the last 8 bytes. This is not impossible but still highly unlikely because the address should match the zero padded 0000000000000XXX, being XXX the exact Nonce.

Furthermore the Nonce value is increased each time a delegator interacts with the potentially malicious validator (migrateIn, migrateOut, sellVoucher, sellVoucher_new, buyShares) so it brings some randomness out of the potential malicious actor’s control. Additionally, the malicious validator would have had to update signers at least 2 times and between each SignerUpdate event there would be an enforced delay of 100 epochs between changes.

Last but not least, the malicious actor would have to be a legit validator so it would have had to find a way to steal the private key from an honest validator or become a rogue one.

Although the attack was not impossible, it was extremely unlikely based upon the improbability of all the above conditions being met.

Resolution and Recovery

A check was included that verifies the event type being parsed prior to unpacking it (see here). This patch was tested on Mumbai and Mainnet nodes and included in the v1.0.3 Heimdall release on 2nd November 2023 at around 3:30 PM UTC (9:00 PM IST). A release announcement was shared allowing all the validators to upgrade.

Credits to Asymmetric Research for their research and findings and working along with Polygon, link here