Closed code423n4 closed 1 year ago
xmxanuel marked the issue as disagree with severity
[dispute vaildity]
This is expected behavior. The sender of the top-level batch provides some Ether for the batch and is responsible for deciding who to call and what will be the consequences. The author sender can't lose more Ether than they've provided in the beginning. On top of that if there's not enough Ether to perform all the steps of the batch, the whole transaction reverts. The reentrancy pattern you've shown may also be used for cleaning up leftover tokens if for whatever reason they need to be sent to Caller
and the batch can't be designed to always consume all of them, no only Ether but also ERC-20 tokens.
xmxanuel marked the issue as sponsor disputed
I fail to see any state changes being performed after the call, meaning that re-entering wouldn't cause any inconsistent state
GalloDaSballo marked the issue as unsatisfactory: Insufficient proof
Lines of code
https://github.com/code-423n4/2023-01-drips/blob/9fd776b50f4be23ca038b1d0426e63a69c7a511d/src/Caller.sol#L185-L192 https://github.com/code-423n4/2023-01-drips/blob/9fd776b50f4be23ca038b1d0426e63a69c7a511d/src/Caller.sol#L193-L200 https://github.com/code-423n4/2023-01-drips/blob/9fd776b50f4be23ca038b1d0426e63a69c7a511d/src/Caller.sol#L202-L208
Vulnerability details
Proof of Concept
The Caller contract implements
callBatched
function in order to execute a batch of calls within one call. The function has payable declaration to be able to send ETH inside the call.The NATSPEC is also provided in parallel;
Link
Accordingly,
calls
parameters are provided asCall[]
and the function internally calls the_call
function inside the iteration;Link
_call
function is being called internally;Link
As you can see, the
_call
function also callsAddress
contract'sfunctionCallWithValue
inherited from Open Zeppelin.functionCallWithValue
is an overloaded function and it ends with;As can be seen, it checks whether the contract has sufficient balance in order to execute the low-level call to the target with
target.call{value: value}(data)
However, there is one glitch at this point. Since the calls can be made to arbitrary addresses, an organized actor can create re-entrancy by simply sending funds through this function and triggering the fallback function of his/her contract.
So once the ETH is received in the attacker contract(A), the fallback function of (A) is triggered and it calls the
Caller.callBatched
again, and the whole function executes again with supplied params. This results in draining the forwarder contract/wallet.Impact
Draining funds from the forwarder wallet/contract
Tools Used
Manual Review
Recommended Mitigation Steps
The function could have the
nonReentrant
modifier that locks the function state during the call.