DoS Vulnerability In Rebalancer.sol: A Deep Dive
Hey guys! Let's dive into a critical vulnerability found in the Malda protocol's Rebalancer.sol
contract. This is an interesting one, categorized as a Medium severity issue, but it can cause some serious headaches. We're talking about a Denial-of-Service (DoS) vulnerability that can disrupt the core functionality of the protocol – cross-chain liquidity rebalancing. Let’s break it down so you can understand what went wrong, how it can be exploited, and how it can be fixed.
Summary
The sendMsg
function within Rebalancer.sol
has a flaw in how it checks the maximum transfer size. Specifically, it incorrectly accumulates the _amount
to transferInfo.size
even when the transfer time window has expired (transferSizeDeadline < block.timestamp
). This means that even valid rebalancing transactions can be reverted erroneously, causing a Denial-of-Service (DoS). This DoS can seriously disrupt the cross-chain liquidity rebalancing within the Malda protocol. Think of it like this: imagine a crucial bridge between two cities being blocked, preventing essential supplies from getting through. That's the kind of disruption we're talking about here.
Root Cause: The Heart of the Problem
The root cause lies within the sendMsg
function. Instead of resetting the transferInfo.size
when the transferSizeDeadline
has passed, the function continues to add the current _amount
to the old, potentially stale transferInfo.size
. This is where the math goes wrong!
Here's the problematic scenario:
When transferSizeDeadline < block.timestamp
, the function should ideally reset transferInfo.size
to zero and only check if _amount
is less than _maxTransferSize
. Instead, it updates currentTransferSize[_msg.dstChainId][_msg.token].size
by adding _amount
. This leads to an incorrect enforcement of the maxTransferSize
limit, triggering the Rebalancer_TransferSizeExcedeed
error even when the intended transfer is perfectly valid on its own. It's like trying to fit an item into a box that should have enough space, but because you haven't cleared out the old items, it appears too full.
Internal Preconditions: Setting the Stage
To understand how this vulnerability can be exploited, we need to consider the preconditions that must be in place:
- Rebalancer Contract Deployment: The
Rebalancer
contract needs to be deployed with amaxTransferSize
set for a specificdstChainId
andtoken
. This is typically done using thesetMaxTransferSize
function. This is like setting the rules of the game. - Previous Rebalancing Transaction: A prior rebalancing transaction must have occurred, setting
currentTransferSize[_msg.dstChainId][_msg.token].size
to a non-zero value. Think of this as having some initial data stored that the vulnerability can interact with. - Expired Time Window: The
transferTimeWindow
(which defaults to 86400 seconds, or 24 hours) needs to have expired since the last transfer. This is the critical timing element that triggers the incorrect calculation. This is like waiting for the right moment to strike.
External Preconditions: The Attacker's Move
Now, let's look at the external conditions required for an attack:
- Rebalancer Role: A rebalancer (an account with the
REBALANCER_EOA
role) attempts to callsendMsg
with a valid_amount
. This means the amount is less than themaxTransferSize
. - Stale Size: The crucial part is that when this
_amount
is added to the staletransferInfo.size
, the sum exceedsmaxTransferSize
. This is the trap being set.
Attack Path: Step-by-Step Exploitation
Let's walk through a potential attack scenario:
- Configuration: The contract is configured with
maxTransferSize[_dstChainId][_token] = 1000 * 10^6
(imagine this is for USDC) andtransferTimeWindow = 86400
seconds. - Initial Transfer: A rebalancer calls
sendMsg
for_dstChainId
and_token
(USDC), transferring 800 * 10^6 USDC. This setscurrentTransferSize[_dstChainId][_token].size = 800 * 10^6
andtransferInfo.timestamp = block.timestamp
. - Time Passes: After 86400 seconds (the time window expires), the critical vulnerability comes into play.
- Second Transfer Attempt: The rebalancer calls
sendMsg
again with_amount = 300 * 10^6
USDC. This amount is individually less than themaxTransferSize
of 1000 * 10^6 USDC. - Incorrect Calculation: Inside
sendMsg
, the conditiontransferSizeDeadline < block.timestamp
is now true. Instead of resetting thesize
, the contract incorrectly updatescurrentTransferSize[_dstChainId][_token].size += _amount
, resulting in 800 * 10^6 + 300 * 10^6 = 1100 * 10^6. - Failed Check: The check
require(transferInfo.size + _amount < _maxTransferSize, Rebalancer_TransferSizeExcedeed())
evaluates to1100 * 10^6 > 1000 * 10^6
, causing the transaction to revert with theRebalancer_TransferSizeExcedeed
error. - DoS: The rebalancer is unable to complete the valid rebalancing transaction, which means liquidity cannot be transferred to the destination chain. This is the Denial-of-Service in action!
Impact: Real-World Consequences
The impact of this vulnerability is significant:
- Denial-of-Service (DoS): The most immediate impact is the blockage of valid rebalancing transactions. This disrupts the protocol's ability to maintain liquidity across different chains. It’s like a traffic jam on a critical highway.
- Systemic Risk: Persistent DoS can lead to imbalances in liquidity across chains. This can reduce the protocol's overall efficiency and potentially erode user trust. If users can't move their assets smoothly, they might lose confidence in the system.
- No Direct Fund Loss: It's important to note that this issue doesn't lead to direct loss of funds. The
Rebalancer
contract, as per the project description, cannot directly transfer user funds. However, the disruption caused by the DoS can still have severe consequences. - Sherlock Rules Alignment: According to Sherlock's guidelines, this vulnerability is classified as Medium severity. It disrupts a core protocol function (rebalancing) without causing direct fund loss. Specifically, Section III.3 (DoS in time-sensitive operations) supports this classification.
Mitigation: The Fix
The solution is relatively straightforward. We need to modify the sendMsg
function to ensure that currentTransferSize[_dstChainId][_msg.token].size
is reset to zero when transferSizeDeadline < block.timestamp
. After resetting the size, we then only need to check if _amount
is less than _maxTransferSize
. This ensures that the transfer size check is accurate and doesn't include stale data from previous transfers.
Here’s the recommended fix:
- Inside the
sendMsg
function, whentransferSizeDeadline < block.timestamp
, setcurrentTransferSize[_msg.dstChainId][_msg.token].size = 0
. This clears out the old data. - After resetting the size, perform the check
require(_amount < _maxTransferSize, Rebalancer_TransferSizeExcedeed())
. This verifies the current transfer amount against the maximum allowed size without considering past transfers.
In Conclusion
This vulnerability in Rebalancer.sol
highlights the importance of carefully managing state and time-sensitive calculations in smart contracts. An incorrect check on transfer sizes can lead to a Denial-of-Service, disrupting core functionality and potentially harming user trust. By understanding the root cause, attack path, and impact, we can implement effective mitigation strategies to protect decentralized protocols. Remember, guys, security is a continuous process, and vigilance is key!