Platform: Code4rena
Start Date: 08/06/2022
Pot Size: $115,000 USDC
Total HM: 26
Participants: 72
Period: 11 days
Judge: leastwood
Total Solo HM: 14
Id: 132
League: ETH
Rank: 11/72
Findings: 4
Award: $2,736.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
Malicious or compromised governance can send a different _init
and _calldata
payload than the one that was proposed. This is dangerous as they will be used for a delegatecall
operation. An attacker can pretend to propose a safe upgrade and later execute a delegatecall
to steal funds or selfdestruct
the system.
_init
and _calldata
._init
pointing to a malicious contract, stealing funds from the system.Consider hashing _init
and _calldata
alongside _diamondCut
as the acceptanceTimes
key, to ensure the execution will use the same payload.
// @audit Apply to rescindDiamondCut() and diamondCut() as well. function proposeDiamondCut( IDiamondCut.FacetCut[] memory _diamondCut, address _init, bytes memory _calldata ) internal { uint256 acceptance = block.timestamp + _delay; diamondStorage().acceptanceTimes[keccak256(abi.encode(_diamondCut, _init, _calldata))] = acceptance; emit DiamondCutProposed(_diamondCut, _init, _calldata, acceptance); }
#0 - jakekidd
2022-06-25T00:05:54Z
Malicious or compromised governance can send a different _init and _calldata payload than the one that was proposed. This is dangerous as they will be used for a delegatecall operation.
Right now, "governance" is just an Owner
role. However, I am still concerned with whether there is, hypothetically, a security issue here when governance is deployed for the protocol in the future.
Additionally worth noting that the implementation here differs from the boilerplate EIP-2535 by adding the propose route, so this isn't a redundant issue with the core proposal.
Because of this, marking as valid. The mitigation step works fine for this.
#1 - jakekidd
2022-06-27T02:22:47Z
Duplicate of #241
🌟 Selected for report: GimelSec
Also found by: Czar102, Lambda, csanuragjain, minhquanym, shenwilly
The current implementation of LibDiamond.diamondCut
allows any diamondCut
update to be executed instantly, defeating the purpose of the 7 day update delay mechanism.
The issue is this check in LibDiamond.diamondCut
:
require( diamondStorage().acceptanceTimes[keccak256(abi.encode(_diamondCut))] < block.timestamp, "LibDiamond: delay not elapsed" );
As acceptanceTimes
has the default value of 0
, any updates that have never been proposed will satisfy the < block.timestamp
condition. A malicious or compromised governance can exploit this to add a malicious upgrade instantly.
Add another check to guard against this:
require( diamondStorage().acceptanceTimes[keccak256(abi.encode(_diamondCut))] != 0, "LibDiamond: not proposed yet" );
#0 - jakekidd
2022-06-27T02:32:33Z
🌟 Selected for report: xiaoming90
Also found by: shenwilly
1169.1572 USDC - $1,169.16
https://github.com/code-423n4/2022-06-connext/blob/4dd6149748b635f95460d4c3924c7e3fb6716967/contracts/contracts/core/connext/facets/BridgeFacet.sol#L827-L850 https://github.com/code-423n4/2022-06-connext/blob/4dd6149748b635f95460d4c3924c7e3fb6716967/contracts/contracts/core/connext/helpers/SponsorVault.sol#L138-L140
When a sponsor wants to stop sponsoring bridge fees, it's possible that they call SponsorVault.setConnext
to stop allowing Connext to reimburse fees, but forgot to inform the Connext team. This will cause bridging executions to be temporarily disrupted.
For fee reimbursement to work, both the Bridge and SponsorVault must point to each other. If SponsorVault's connext
is set to address zero but Connext bridge's sponsorVault
is still set to the vault, any bridge transaction to this chain will revert as the bridge still try to reimburse the fees from the sponsor's vault:
function _handleExecuteTransaction() { ... if (address(s.sponsorVault) != address(0)) { ... // reverts here s.sponsorVault.reimburseRelayerFees(_args.params.originDomain, payable(_args.params.to), _args.params.relayerFee); } ... }
setConnext
to address zero.sponsorVault
to address zero so that transactions start being executed again.Add a function to deactivate sponsorVault atomically when sponsorVault calls setConnext()
to another address.
In BridgeFacet.sol
:
function deactivateSponsorVault() external { if (msg.sender != address(s.sponsorVault)) revert BridgeFacet__notSponsorVault; s.sponsorVault = ISponsorVault(address(0)); emit SponsorVaultUpdated(msg.sender, address(0), msg.sender); }
In SponsorVault.sol
:
function setConnext(address _connext) external onlyOwner { if (IConnextHandler(connext).sponsorVault() == address(this)) { IConnextHandler(connext).deactivateSponsorVault(); } _setConnext(_connext); }
#0 - jakekidd
2022-06-25T01:35:25Z
QA issue, but valid. Worth implementing the suggested mitigation strategy.
#1 - 0xleastwood
2022-08-15T08:06:00Z
This is an interesting issue. The owner of the sponsor vault can DoS the Connext bridge at any point in time. Valid medium
severity issue.
#2 - 0xleastwood
2022-08-15T09:09:42Z
Duplicate of #146.
🌟 Selected for report: BowTiedWardens
Also found by: 0x1f8b, 0x29A, 0x52, 0xNazgul, 0xNineDec, 0xf15ers, 0xkatana, 0xmint, Chom, ElKu, Funen, IllIllI, JMukesh, Jujic, Kaiziron, Lambda, MiloTruck, Ruhum, SmartSek, SooYa, TerrierLover, TomJ, WatchPug, Waze, _Adam, asutorufos, auditor0517, bardamu, c3phas, catchup, cccz, ch13fd357r0y3r, cloudjunky, cmichel, cryptphi, csanuragjain, defsec, fatherOfBlocks, hansfriese, hyh, jayjonah8, joestakey, k, kenta, obtarian, oyc_109, robee, sach1r0, shenwilly, simon135, slywaters, sorrynotsorry, tintin, unforgiven, xiaoming90, zzzitron
142.2658 USDC - $142.27
sponsoredFee
calculationWhen calling SponsorVault.reimburseRelayerFees
with active gasTokenOracle
, sponsoredFee
is calculated after getting the gas rate from oracle. This is unnecessary as sponsoredFee
will be calculated again later in the code.
In addition, there's no zero division check so it is possible this issue could cause a revert in a proper transaction.
Remove the unnecessary line of code.
When initializing stable swap pools, decimals
are provided manually by the deployer. A faulty script could insert the wrong decimal values and make the pool unusable, requiring a redeployment.
Remove decimals
parameter and query token decimals directly on-chain.
uint8 decimals = IERC20Extended(_pooledTokens[i]).decimals() precisionMultipliers[i] = 10**uint256(SwapUtils.POOL_PRECISION_DECIMALS - decimals);
https://github.com/code-423n4/2022-06-connext/blob/4dd6149748b635f95460d4c3924c7e3fb6716967/contracts/contracts/core/connext/helpers/StableSwap.sol#L61-L91 https://github.com/code-423n4/2022-06-connext/blob/4dd6149748b635f95460d4c3924c7e3fb6716967/contracts/contracts/core/connext/facets/StableSwapFacet.sol#L393-L426