Platform: Code4rena
Start Date: 11/11/2022
Pot Size: $36,500 USDC
Total HM: 5
Participants: 62
Period: 3 days
Judge: berndartmueller
Id: 181
League: ETH
Rank: 6/62
Findings: 2
Award: $1,016.92
🌟 Selected for report: 0
🚀 Solo Findings: 0
612.4275 USDC - $612.43
https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L206 https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L219
Exchange#bulkExecute() can reenter, and internal execution of delegatecall is allowed to fail, not revert, malicious users can reenter to steal funds
Assumptions: There is a malicious user alice,with NFT for sale, paid through eth If user bob executes a purchase steps:
Execution[0] = {seller:alice,paymentToken=address(0),price:50 eth} Execution[1] = {seller:jack,paymentToken=address(0),price:50 eth}
When Execution[0] is executed, #_executeFundsTransfer()->payable(alice).call{value: 50 eth}("") will be executed;
malicious user alice is a contract, alice#receive() be called, executes a new call, reentering Exchange#bulkExecute{value:1 wei}(Execution[]=[0]) At this point: remainingETH = 1 wei and #bulkExecute ends immediately, and Exchange#_returnDust() is executed, at which point the contract still has 50 eth left, will transfer to alice (which was meant to be used to trade with jack). and remainingETH is changed to 0,remainingETH=0
Execution[0] end of the transaction, back to the Execution[1], the execution of jack's transaction, due to insufficient eth, this transaction will fail, but will not revert
bob's entire Exchange#bulkExecute() ends normally, due to remainingETH=0, will not execute #_returnDust()
so that the user bob was stolen 50 eth in step 3
function _transferTo( address paymentToken, address from, address to, uint256 amount ) internal { (bool success,) = payable(to).call{value: amount}(""); /****@audit step 2 call alice#receive() ***/
function _returnDust() private { uint256 _remainingETH = remainingETH; assembly { if gt(_remainingETH, 0) { let callStatus := call( gas(), caller(), selfbalance(), //*****@audit step 3 , steal 50 eth to alice****/ 0, 0, 0, 0 ) } } }
function bulkExecute(Execution[] calldata executions) external payable whenOpen setupExecution { ... calldatacopy(add(0x04, memPointer), order_pointer, size) // must be put in separate transaction to bypass failed executions // must be put in delegatecall to maintain the authorization from the caller let result := delegatecall(gas(), address(), memPointer, add(size, 0x04), 0, 0) /****@audit step 4 , fail but don't revert****/ } }
#_execute use own reentrancyGuardForOneExecute
contract Exchange is IExchange, ReentrancyGuarded, EIP712, OwnableUpgradeable, UUPSUpgradeable { + bool private reentrancyLockForOneExecute = false; + modifier reentrancyGuardForOneExecute { + require(!reentrancyLockForOneExecute, "Reentrancy detected"); + reentrancyLockForOneExecute = true; + _; + reentrancyLockForOneExecute = false; + } function _execute(Input calldata sell, Input calldata buy) public payable - reentrancyGuard + reentrancyGuardForOneExecute internalCall { .. function execute(Input calldata sell, Input calldata buy) external payable whenOpen + reentrancyGuard setupExecution { ... function bulkExecute(Execution[] calldata executions) external payable whenOpen + reentrancyGuard setupExecution {
#0 - c4-judge
2022-11-16T14:18:28Z
berndartmueller marked the issue as duplicate of #96
#1 - c4-judge
2022-11-16T14:18:35Z
berndartmueller marked the issue as satisfactory
404.489 USDC - $404.49
Pool.sol is Upgradeable but not method "#initialize()", can't init owner, owner still address(0), unable to Upgradeable
contract Pool is IPool, OwnableUpgradeable, UUPSUpgradeable { ... /***@audit not method initialize() **** function _authorizeUpgrade(address) internal override onlyOwner {} /****@audit upgrade need onlyOwner,but still address(0) ***/
add initialize() , deploy need call
contract Pool is IPool, OwnableUpgradeable, UUPSUpgradeable { + function initialize() external initializer { + __Ownable_init(); + }
#0 - trust1995
2022-11-14T22:33:15Z
Dup of #186
#1 - c4-judge
2022-11-16T12:18:26Z
berndartmueller marked the issue as duplicate of #186
#2 - c4-judge
2022-11-16T12:18:29Z
berndartmueller marked the issue as satisfactory