Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $140,000 USDC
Total HM: 19
Participants: 69
Period: 21 days
Judge: 0xean
Total Solo HM: 4
Id: 343
League: ETH
Rank: 45/69
Findings: 1
Award: $177.52
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: josephdara
Also found by: Aymen0909, Fassi_Security, MrPotatoMagic, Shield, grearlake, iamandreiski, josephdara, ladboy233, lanrebayode77, t0x1c
177.522 USDC - $177.52
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L82-L95 https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L310-L337
Within Taiko's bridge there is a suspendMessages()
function which serves to suspend or unsuspend invocation for a list of messages. This is a safety for the 2-step bridging, if the team notices "fantom/fake" transactions that can be proven (a bug in the Merkle tree) during the proof cooldown window, they can intervene to stop the message. By suspending the message or the list of messages, they will be setting the receivedAt
timestamp at type(uint64).max so the current timestamp can't be greater than the receivedAt
+ invocationDelay so messages can't be processed or recalled. The vulnerability is that we don't have the needed checks that recallMessage()
and processMessage()
do for retryMessage()
.
If a fantom transaction failed to be processed (e.g. due to low gas) and is with status RETRIABLE
and is noticed after the processing has failed, it can't be suspended as there are no such checks in retryMessage()
allowing for anyone to retry and pass the message as a real one.
The suspendMessages()
function can suspend/unsuspend a list of messages by suspending their invocation, meaning receivedAt
will be set to type(uint64).max
and the invocation checks block.timestamp > invocationDelay + receivedAt
will always fail:
function suspendMessages( bytes32[] calldata _msgHashes, bool _suspend ) external onlyFromOwnerOrNamed("bridge_watchdog") { uint64 _timestamp = _suspend ? type(uint64).max : uint64(block.timestamp); for (uint256 i; i < _msgHashes.length; ++i) { bytes32 msgHash = _msgHashes[i]; proofReceipt[msgHash].receivedAt = _timestamp; emit MessageSuspended(msgHash, _suspend); } }
Invocation checks are present in both processMessage()
and recallMessage()
:
if (block.timestamp >= invocationDelay + receivedAt)
The above-mentioned checks wouldn't hold true if the messages is "suspended" and receivedAt
is set to type(uint64).max
. If the above check fails, messages won't be invoked:
if (_invokeMessageCall(_message, msgHash, gasLimit)) { _updateMessageStatus(msgHash, Status.DONE); } else { _updateMessageStatus(msgHash, Status.RETRIABLE); }
If a message with a status RETRIABLE
is spotted to be a fantom one, it can't be suspended as no such checks are present within the retryMessage()
function, allowing for fake/fantom messages to be invoked:
function retryMessage( Message calldata _message, bool _isLastAttempt ) external nonReentrant whenNotPaused sameChain(_message.destChainId) { // If the gasLimit is set to 0 or isLastAttempt is true, the caller must // be the message.destOwner. if (_message.gasLimit == 0 || _isLastAttempt) { if (msg.sender != _message.destOwner) revert B_PERMISSION_DENIED(); } bytes32 msgHash = hashMessage(_message); if (messageStatus[msgHash] != Status.RETRIABLE) { revert B_NON_RETRIABLE(); } // Attempt to invoke the messageCall. if (_invokeMessageCall(_message, msgHash, gasleft())) { _updateMessageStatus(msgHash, Status.DONE); } else if (_isLastAttempt) { _updateMessageStatus(msgHash, Status.FAILED); } emit MessageRetried(msgHash); }
Manual Review
Add the same checks present in recallMessage()
and processMessage()
for retryMessage()
as well.
Invalid Validation
#0 - c4-pre-sort
2024-03-28T04:28:03Z
minhquanym marked the issue as duplicate of #273
#1 - c4-pre-sort
2024-03-28T18:59:13Z
minhquanym marked the issue as duplicate of #298
#2 - c4-judge
2024-04-09T18:23:36Z
0xean changed the severity to QA (Quality Assurance)
#3 - c4-judge
2024-04-10T11:45:41Z
0xean marked the issue as grade-c
#4 - c4-judge
2024-04-12T14:03:24Z
This previously downgraded issue has been upgraded by 0xean
#5 - c4-judge
2024-04-12T14:04:03Z
0xean marked the issue as satisfactory