DoS Vulnerability In Rebalancer.sol: A Deep Dive

by Lucas 49 views
Iklan Headers

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:

  1. Rebalancer Contract Deployment: The Rebalancer contract needs to be deployed with a maxTransferSize set for a specific dstChainId and token. This is typically done using the setMaxTransferSize function. This is like setting the rules of the game.
  2. 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.
  3. 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:

  1. Rebalancer Role: A rebalancer (an account with the REBALANCER_EOA role) attempts to call sendMsg with a valid _amount. This means the amount is less than the maxTransferSize.
  2. Stale Size: The crucial part is that when this _amount is added to the stale transferInfo.size, the sum exceeds maxTransferSize. This is the trap being set.

Attack Path: Step-by-Step Exploitation

Let's walk through a potential attack scenario:

  1. Configuration: The contract is configured with maxTransferSize[_dstChainId][_token] = 1000 * 10^6 (imagine this is for USDC) and transferTimeWindow = 86400 seconds.
  2. Initial Transfer: A rebalancer calls sendMsg for _dstChainId and _token (USDC), transferring 800 * 10^6 USDC. This sets currentTransferSize[_dstChainId][_token].size = 800 * 10^6 and transferInfo.timestamp = block.timestamp.
  3. Time Passes: After 86400 seconds (the time window expires), the critical vulnerability comes into play.
  4. Second Transfer Attempt: The rebalancer calls sendMsg again with _amount = 300 * 10^6 USDC. This amount is individually less than the maxTransferSize of 1000 * 10^6 USDC.
  5. Incorrect Calculation: Inside sendMsg, the condition transferSizeDeadline < block.timestamp is now true. Instead of resetting the size, the contract incorrectly updates currentTransferSize[_dstChainId][_token].size += _amount, resulting in 800 * 10^6 + 300 * 10^6 = 1100 * 10^6.
  6. Failed Check: The check require(transferInfo.size + _amount < _maxTransferSize, Rebalancer_TransferSizeExcedeed()) evaluates to 1100 * 10^6 > 1000 * 10^6, causing the transaction to revert with the Rebalancer_TransferSizeExcedeed error.
  7. 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, when transferSizeDeadline < block.timestamp, set currentTransferSize[_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!