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: 8/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#L40-L46 https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L154-L158 https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L168-L172 https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L212-L227
The funds (ETH) that users sent along with the bulkExecute
may be stolen.
When a buyer send a bulkExecute()
tx with msg.value > 0
(with order of buying token with eth), the sent ETH may be stolen if the tx contains a malicious selling order which can trigger a reentry attack.
Here are the details of the reentry attack:
bulkExecute()
with a list of executions(order pairs) and some ETH. Assume v1 = remainingETH = msg.value
, we have balanceOf(Exchange) >= v1
._execute()
one of the executing which is malicious, triggle a call to a deployed malicious contract X. We can know that v2 = remainingETH >= v1
.bulkExecute()
of the Exchange
with at least 1 wei and an empty list of executions. And this call with send all (balanceOf(Exchange) >= v2 + 1wei
) ETH to contract X by _returnDust()
.remainingETH == 0
and balanceOf(Exchange) ==0
now.The invocation of a malicious contract in step 2 above may occur under the following circumstances and may be considered constructed maliciously
_executeTokenTransfer()
.In addition, when the Buyer calls execute()
with a msg.value
exceeds the actual price, the buyer may also lose the remaining funds by a reentry attack.
VS Code
This problem is mainly due to the fact that _execute()
is reentrancy protected and setupExecution()
is not.
Recommended: remove reentrancyGuard
from _execute()
and add it to external functions (execute()
and bulkExecute()
) to make setupExecution
reentrancy protected.
Here is the diff:
diff --git a/contracts/Exchange.sol b/contracts/Exchange.sol index ec27b1d..f3fa09b 100644 --- a/contracts/Exchange.sol +++ b/contracts/Exchange.sol @@ -155,6 +155,7 @@ contract Exchange is IExchange, ReentrancyGuarded, EIP712, OwnableUpgradeable, U external payable whenOpen + reentrancyGuard setupExecution { _execute(sell, buy); @@ -169,6 +170,7 @@ contract Exchange is IExchange, ReentrancyGuarded, EIP712, OwnableUpgradeable, U external payable whenOpen + reentrancyGuard setupExecution { /* @@ -234,7 +236,6 @@ contract Exchange is IExchange, ReentrancyGuarded, EIP712, OwnableUpgradeable, U function _execute(Input calldata sell, Input calldata buy) public payable - reentrancyGuard internalCall { require(sell.order.side == Side.Sell);
#0 - c4-judge
2022-11-16T14:17:59Z
berndartmueller marked the issue as duplicate of #96
#1 - c4-judge
2022-11-16T14:18:09Z
berndartmueller marked the issue as satisfactory
404.489 USDC - $404.49
The Pool (proxy) contract will never be able to upgrade successfully
The Pool contract is defined as OwnableUpgradeable
and UUPSUpgradeable
, and it should be upgradeable just like the Exchange contract.
UUPSUpgradeable
will invoke _authorizeUpgrade()
in upgradeTo()
and upgradeToAndCall()
, and the Pool contract overrides _authorizeUpgrade:
function _authorizeUpgrade(address) internal override onlyOwner {}
This means that only the owner can perform the upgrade.
But, according to the existing implementation, the owner of the Pool proxy will always be the ZERO address, because it is not initialized.
Therefore, only the ZERO address can call upgradeTo()
or upgradeToAndCall()
successfully, and this is impossible.
VS Code
Add an initialize
function to call the __Ownable_init()
.
The diff:
diff --git a/contracts/Pool.sol b/contracts/Pool.sol index 3722082..ff664b5 100644 --- a/contracts/Pool.sol +++ b/contracts/Pool.sol @@ -22,6 +22,14 @@ contract Pool is IPool, OwnableUpgradeable, UUPSUpgradeable { event Transfer(address indexed from, address indexed to, uint256 amount); + constructor() { + _disableInitializers(); + } + + function initialize() external initializer { + __Ownable_init(); + } +
#0 - trust1995
2022-11-14T22:22:29Z
Dup of #186
#1 - c4-judge
2022-11-16T12:17:50Z
berndartmueller marked the issue as duplicate of #186
#2 - c4-judge
2022-11-16T12:17:56Z
berndartmueller marked the issue as satisfactory