Skip to content

Latest commit

 

History

History
87 lines (51 loc) · 5.21 KB

078.md

File metadata and controls

87 lines (51 loc) · 5.21 KB

Unique Peach Cow

High

Unchecked type Assertion in Injected Transaction Handling Triggers Node Panic

Affceted Lines of Code

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/checkpointing/proposal.go#L106-L114

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/checkpointing/proposal.go#L294-L302

Details

Background and Context:

When a checkpoint proposal is processed, part of the logic involves reading an “injected” transaction. This transaction contains extra vote extension checkpoint data, which is used for internal protocol operations. The code expects this injected transaction to carry exactly one message and, crucially, that this single message is of a specific type—MsgInjectedCheckpoint (a type that represents checkpoint metadata).

Issue:

In the proposal processing logic within babylon/x/checkpointing/proposal.go, specifically around lines 338-344, the code assumes that the injected transaction (a "fake" transaction used to carry vote extension checkpoint data) contains exactly one message that can be type‐asserted to *ckpttypes.MsgInjectedCheckpoint. The function uses a direct type assertion on msgs[0] without prior explicit type checking. If an attacker crafts a transaction containing exactly one message of a different type—or an improperly formatted message—the type assertion will fail, causing a runtime panic instead of returning an error gracefully.

The relevant code snippet is:

	injectedTx, err := h.txDecoder(injectedTxBytes)
	if err != nil {
		return nil, fmt.Errorf("failed to decode injected vote extension tx: %w", err)
	}
	msgs := injectedTx.GetMsgs()
	if len(msgs) != 1 {
		return nil, fmt.Errorf("injected tx must have exact one message, got %d", len(msgs))
	}
	injectedCkpt := msgs[0].(*ckpttypes.MsgInjectedCheckpoint)

The code assumes that after ensuring there is exactly one message, this single message is guaranteed to be a *MsgInjectedCheckpoint. The problem is that it uses a direct type assertion without any further check:

injectedCkpt := msgs[0].(*ckpttypes.MsgInjectedCheckpoint)

However, if a validator injects a malicious transaction that has exactly one message but is not of the expected type, the type assertion will result in a panic.

Impact

  1. A panic in the proposal processing function can cause the node to crash or restart unexpectedly. In a blockchain network, especially one using consensus protocols that rely on uninterrupted operation, even a single node crashing can have cascading effects.

  2. The state machine (and, by extension, the consensus mechanism) might come to a halt as error handling for such a panic is not gracefully managed. This means the affected node could fall out of consensus, potentially disrupting block production or delaying finality.

  3. If exploited by multiple validators or in a coordinated attack, this vulnerability could lead to a denial-of-service (DoS) scenario that disrupts the block proposal process and potentially hinders consensus progress. Such a panic-driven DoS may require manual intervention to restart nodes and can affect trust in the network’s resilience.

Illustration

Consider a scenario where a validator injects a specially crafted transaction into the proposal block that contains a single message. Instead of the message being of type *ckpttypes.MsgInjectedCheckpoint, it is an object of another type that passes the length check (i.e., exactly one message exists). When the code reaches:

injectedCkpt := msgs[0].(*ckpttypes.MsgInjectedCheckpoint)

the type assertion fails because msgs[0] does not match the expected type, leading to an immediate runtime panic. This panic is unhandled in the context, and the error propagates in a way that causes the node to crash rather than merely rejecting the erroneous transaction.

Attack Path

  • An attacker—who could be a malicious validator or any party able to craft transactions that are processed during the proposal phase—could deliberately construct an injected transaction that contains exactly one message, but with a message type other than *MsgInjectedCheckpoint.

  • Because the function only performs a length check (ensuring there is one message) and skips any type verification, the malicious message bypasses the initial check.

  • When the code reaches the type assertion, it will attempt to forcibly cast the message to *MsgInjectedCheckpoint. If the message is of a different type, the cast fails, and a runtime panic occurs.

  • During ProcessProposal, as the node attempts to extract and type-assert the injected transaction, the type assertion fails.

  • The resulting panic causes the node to crash or stall, potentially resulting in consensus or availability issues.

Mitigation

  1. Replace the direct type assertion (msgs[0].(*ckpttypes.MsgInjectedCheckpoint)) with a type check using a type assertion with an "ok" variable. For example:
	injectedCkpt, ok := msgs[0].(*ckpttypes.MsgInjectedCheckpoint)
	if !ok {
		return nil, fmt.Errorf("injected tx message is not of type MsgInjectedCheckpoint")
	}
  1. Implement additional validation on the content and structure of the injected transaction before processing it to ensure that only transactions with strictly conforming message types are accepted.