Connext Amarok contest - 0x1f8b's results

The interoperability protocol of L2 Ethereum.

General Information

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

Connext

Findings Distribution

Researcher Performance

Rank: 5/72

Findings: 4

Award: $4,292.44

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: Ruhum

Also found by: 0x1f8b, WatchPug

Labels

bug
duplicate
3 (High Risk)

Awards

2338.3143 USDC - $2,338.31

External Links

Lines of code

https://github.com/code-423n4/2022-06-connext/blob/20f86d58444d7c8178735ada7e456a3112116e54/contracts/contracts/core/connext/facets/PortalFacet.sol#L108

Vulnerability details

Impact

Integer overflow can affect router balances.

Proof of Concept

The repayAavePortal method of the PortalFacet contract subtracts the balance within an unchecked region, but this balance is not checked beforehand to be greater than the amountIn.

unchecked { s.routerBalances[msg.sender][_local] -= amountIn; }

The unique check against the balance is with the _maxIn that always could be greater than the balance because apparently is use as slippage (is not documented as a @param in the methods).

if (routerBalance < _maxIn) revert PortalFacet__repayAavePortal_insufficientFunds();

Remove the unchecked region.

#0 - ecmendenhall

2022-06-20T04:48:25Z

Duplicate of #68

#1 - LayneHaber

2022-06-21T22:35:27Z

Closing in favor of #68

Findings Information

🌟 Selected for report: WatchPug

Also found by: 0x1f8b

Labels

bug
duplicate
2 (Med Risk)
resolved
sponsor confirmed

Awards

1169.1572 USDC - $1,169.16

External Links

Lines of code

https://github.com/code-423n4/2022-06-connext/blob/07adce8e88d3c93e26d32c9c2056593c62911197/contracts/contracts/core/connext/helpers/ConnextPriceOracle.sol#L103 https://github.com/code-423n4/2022-06-connext/blob/07adce8e88d3c93e26d32c9c2056593c62911197/contracts/contracts/core/connext/helpers/ConnextPriceOracle.sol#L134 https://github.com/code-423n4/2022-06-connext/blob/07adce8e88d3c93e26d32c9c2056593c62911197/contracts/contracts/core/connext/facets/StableSwapFacet.sol#L426

Vulnerability details

Impact

Some contracts assumes decimals <= 18 and does not handle > 18 decimals.

Proof of Concept

Because the pragma used doesn't allow integer underflows, if a token with more than 18 decimals is used, an integer underflow will produce a denial of service.

Some tokens have more than 18 decimals (e.g. YAM-V2 has 24).

This may trigger unexpected reverts due to overflow, posing a liveness risk to the contract.

Reference:

Major severity finding from Consensys Diligence Audit of Defi Saver:

Affected source code:

Ascertain that the code will not fail if the token's decimals are greater than 18.

#0 - ecmendenhall

2022-06-20T15:14:35Z

#1 - ecmendenhall

2022-06-20T15:16:06Z

I gave this a ❤️ along with https://github.com/code-423n4/2022-06-connext-findings/issues/204 because these findings both identified an additional location in the StableSwap contract where the 18 decimal assumption is hardcoded.

#2 - jakekidd

2022-06-25T02:36:44Z

Marking this as duplicate but leaving it open/confirmed since it identifies that additional location where the assumption is hardcoded!

#5 - 0xleastwood

2022-08-13T23:34:49Z

Duplicate of #204.

Low

Front-running init

To initialize contract parameters, most contracts employ an init pattern (rather than a constructor). They are vulnerable to front-running race conditions where an attacker/griefer can front-run (cannot access control because admin roles are not initialized) to initially with their own (malicious) parameters upon detecting (if an event is emitted) which the contract deployer has to redeploy wasting gas and risking other transactions from interacting with the attack unless they are enforced to be atomic with contact deployment via deployment script or factory contracts.

Many init routines (such as DiamondInit.init) lack an explicit event emission, making it difficult to monitor such scenarios.

Affected source code:

Ownable / Pausable

The contract OwnerPausableUpgradeable is Ownable and Pausable, so the owner could resign while the contract is paused, causing a Denial of Service. Owner resignation while paused should be avoided.

Affected source code:

Lack of ACK during owner change

It's possible to lose the ownership under specific circumstances.

Because an human error it's possible to set a new invalid owner. When you want to change the owner's address it's better to propose a new owner, and then accept this ownership with the new wallet.

If the owner calls the following methods, it does not verify that the proposed address is valid.

Affected source code:

Outdated compiler

The pragma version used are:

pragma solidity >=0.6.11; pragma solidity 0.8.14; pragma solidity >=0.7.6;

But recently solidity released a new version with important Bugfixes:

  • The first one is related to ABI-encoding nested arrays directly from calldata. You can find more information here.

  • The second bug is triggered in certain inheritance structures and can cause a memory pointer to be interpreted as a calldata pointer or vice-versa. We also have a dedicated blog post about this bug.

Apart from these, there are several minor bug fixes and improvements.

The minimum required version should be 0.8.14

Outdated packages

The packages used are out of date, it is good practice to use the latest version of these packages:

"@openzeppelin/contracts": "4.5.0", "@openzeppelin/contracts-upgradeable": "4.5.2",

Lack of checks

Check for address(0) during constructor, otherwise it could lead to bad initialization, bad implementations, or bad admin changes.

Affected source code without checking address(0):

Wrong comments

Router who took out the credit, and router is not used:

Asserts caller is the router owner (if set) or the router itself, the router is only checked if there are no owner:

Todo's left in code

TODO usually mean something still have to be checked of done. This could lead to vulnerabilities if not verified.

Affected source code:

And this seems more important, because routers can repay any-amount out-of-band using the repayAavePortal method or by interacting with the aave contracts directly:

Non Critical

Use encode instead of encodePacked for hashig

Use of abi.encodePacked in ConnextMessage is safe, but unnecessary and not recommended. abi.encodePacked can result in hash collisions when used with two dynamic arguments (string/bytes).

There is also discussion of removing abi.encodePacked from future versions of Solidity (ethereum/solidity#11593), so using abi.encode now will ensure compatibility in the future.

keccak256(abi.encodePacked(bytes(_name).length, _name, bytes(_symbol).length, _symbol, _decimals));:

Double emits

The methods pause and unpause of ProposedOwnableFacet.sol#L253-L261 contract does not check that the contract is already paused or not, it could produce double emits and dApps could be affected.

Codding style

  • The file OZERC20.sol contains a contract with another name, ERC20. This is a terrible practice that reduces the readability and auditability of the code.

#0 - jakekidd

2022-07-01T22:36:23Z

Outdated compiler lacking examples?

OZERC20 is just an open zeppelin ERC20 implementation.

Delete optimization

Use delete instead of set to default value (false or 0)

Affected source code:

Avoid unused returns

Having a method that always returns the same value is not correct in terms of consumption, if you want to modify a value, and the method will perform a revert in case of failure, it is not necessary to return a true, since it will never be false. It is less expensive not to return anything, and it also eliminates the need to double-check the returned value by the caller.

Affected source code:

Logic improve

It's possible to save gas changine the following logic.

Reduce script size changing:

if (_amount == 0 || local == _asset) {
  return (_amount, local);
}
address adopted = s.canonicalToAdopted[id];
if (adopted == _asset || _amount == 0) {
  return (_amount, adopted);
}

Reduce math operations

Use scientific notation (e.g. 10E18) rather than exponentiation (e.g. 10**18)

10**6:

Store in a constant 10**uint256(POOL_PRECISION_DECIMALS):

Dead code

The following contracts are never used. It's an excellent idea to take them out to save gas and improve the codding style.

owner never can be address(0), renounceOwnership require a valid proposed owner:

The method ERC20._setupDecimals is not used, is initialized like in BridgeToken.sol#L101

Avoid multiple casting

Change the type of the argument _assetId to IERC20 _assetId:

Reduce the size of error messages (Long revert Strings)

Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.

Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.

I suggest shortening the revert strings to fit in 32 bytes, or that using custom errors as described next (require pragma upgrade).

Use Custom Errors instead of Revert Strings to save Gas

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)

Source Custom Errors in Solidity:

Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.

Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).

Affected source code:

++i costs less gas compared to i++ or i += 1

++i costs less gas compared to i++ or i += 1 for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.

i++ increments i and returns the initial value of i. Which means:

uint i = 1;
i++; // == 1 but i == 2

But ++i returns the actual incremented value:

uint i = 1;
++i; // == 2 and i == 2 too, so no need for a temporary variable

In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2 I suggest using ++i instead of i++ to increment the value of an uint variable. Same thing for --i and i--

Affected source code:

Use the right type

Use bool or uint8 instead of uint256, this could help to save one storage slots:

Optimize struct layout

The following structs could be optimized moving the position of certains values in order to save slot storages:

LibConnextStorage.sol#L38-L52

struct CallParams {
  address to;
  bytes callData;
  uint32 originDomain;
  uint32 destinationDomain;
  address agent;
  address recovery;
  address callback;
  uint256 callbackFee;
  uint256 relayerFee;
  bool forceSlow;       // Move together to destinationDomain
  bool receiveLocal;    // Move together to destinationDomain
  uint256 slippageTol;
}

Move the bool types close to address in LibConnextStorage.sol#L247

#0 - 0xleastwood

2022-08-16T19:46:29Z

Some good optimisations but also important to point out some invalid ones.

Dead Code - renounceOwnership requires that _proposed is the zero address, so this is incorrect.

Delete Optimization and Avoid unused returns - This would typically be optimised out by the Solidity compiler.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter