SKALE contest - kyliek's results

The only Ethereum native multichain scaling network.

General Information

Platform: Code4rena

Start Date: 18/02/2022

Pot Size: $125,000 USDC

Total HM: 13

Participants: 24

Period: 14 days

Judge: GalloDaSballo

Total Solo HM: 6

Id: 88

League: ETH

SKALE

Findings Distribution

Researcher Performance

Rank: 9/24

Findings: 3

Award: $3,522.05

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: kyliek

Also found by: cccz

Labels

bug
2 (Med Risk)
sponsor disputed

Awards

2697.0588 USDC - $2,697.06

External Links

Lines of code

https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/mainnet/DepositBoxes/DepositBoxERC20.sol#L45 https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/schain/tokens/ERC20OnChain.sol#L49-L50 https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/mainnet/DepositBoxes/DepositBoxERC20.sol#L95 https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/schain/tokens/ERC20OnChain.sol#L60-L63

Vulnerability details

Impact

Anyone on Schain that is able to mint more tokens, other than the mint action from postMessage in tokenManagerERC20 by bridging tokens over, can potentially drain the locked tokens in transferredAmount in depositBoxERC20 on mainnet by calling exit with the same amount of tokens in transferredAmount[schainHash][token].

This will trap other users' funds on sChain and lost those funds on mainnet to the malicious attacker.

An example of proof of concept using ERC20OnChian is given below. This case may seem to be special as the deployer of the clone contract is malicious. However, this is a potential risk that generalises to other custom contracts with any mint functionality.

Proof of Concept (with ERC20)

  • Malicious account deploys an ERC20 clone ERC20OnChain on Schain.

  • By deployment, the malicious account is assigned the MINTER_ROLE on ERC20OnChain in the constructor.

constructor( string memory contractName, string memory contractSymbol ) initializer { AccessControlEnumerableUpgradeable.__AccessControlEnumerable_init(); ERC20Upgradeable.__ERC20_init(contractName, contractSymbol); ERC20BurnableUpgradeable.__ERC20Burnable_init(); _setRoleAdmin(MINTER_ROLE, MINTER_ROLE); _setupRole(MINTER_ROLE, _msgSender()); }
  • This ERC20 clone get registered on tokenMangagerERC20.sol by function addERC20TokenByOwner.
function addERC20TokenByOwner( string calldata targetChainName, address erc20OnMainChain, address erc20OnSchain )
  • The malicious account wait for more users to deposit tokens on depositBoxERC20.depositERC20, which will increase the amount in transferredAmount[schainHash][token]
function depositERC20( string calldata schainName, address erc20OnMainnet, uint256 amount )
  • Malicious account mint to his account the amount equal to transferredAmount[schainHash][token] on ERC20OnChain
function mint(address account, uint256 value) external override { require(hasRole(MINTER_ROLE, _msgSender()), "Sender is not a Minter"); _mint(account, value); }
  • Malicious account calls exitToMainERC20 in TokenManagerERC20.
function exitToMainERC20( address contractOnMainnet, uint256 amount ) external override { communityLocker.checkAllowedToSendMessage(msg.sender); _exit(MAINNET_HASH, depositBox, contractOnMainnet, msg.sender, amount); }
  • transferredAmount[schainHash][token] is drained to the malicious account on mainnet and victims' tokens get stranded on schain.

Tools Used

Disable minting function to be called directly in ERC20OnChain. Only allow minting when bridging tokens over.

#0 - cstrangedk

2022-03-04T00:37:37Z

Similar disputed issue of #17 and #39, here applies to ERC20OnChain token clone.

In production with automatic deploy mode enabled, ERC20OnChain is deployed when token is received in DepositBox and subsequent postMessage()._sendERC20() method is executed... MINTER_ROLE and burner address is set to TokenManagerERC20, which is responsible for minting and burning token clones in response to Deposits in DepositBoxes. When automatic deploy is not abled, the SKALE chain owner is responsible for manually adding tokens and assigning the appropriate MINT and burn addresses.

Here TokenManagerERC20 mints tokens in response to a deposit https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/schain/TokenManagers/TokenManagerERC20.sol#L231

and here burns the token in response to an exit: https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/schain/TokenManagers/TokenManagerERC20.sol#L306

#1 - GalloDaSballo

2022-06-01T16:33:41Z

I believe the sponsor reply sheds further light into how the system will be used in 99% of cases.

That said we have to judge the codebase for how it could be exploited and used by malicious actors, and I do agree with he warden that if the MINTER_ROLE is granted to someone (let's say the admin) then they could use it to drain funds from the mainnet side of the bridge.

Because this is contingent on setup and Admin Privilege, I believe Medium Severity to be more appropriate.

Switching from a role based system to an immutable Minter Address (the bridge contract) can be used as mitigation, alternatively sChain end users will have to monitor for these types of changes and act accordingly

Findings Information

🌟 Selected for report: defsec

Also found by: 0x1f8b, 0xwags, cmichel, csanuragjain, gzeon, jayjonah8, kenta, kirk-baird, kyliek, leastwood, rfa, robee, ye0lde

Labels

bug
QA (Quality Assurance)

Awards

662.802 USDC - $662.80

External Links

Low Severity

[L01] Outdated version of inherited contracts and upgrade library
@openzeppelin/contracts-upgradeable 4.3.2 -> 4.5.1 @openzeppelin/hardhat-upgrades 1.10.0 -> 1.14.0
[L02] Not calling inherited function directly

For instance https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/mainnet/SkaleManagerClient.sol#L62

AccessControlEnumerableUpgradeable.__AccessControlEnumerable_init();

is not necessary. Just use __AccessControlEnumerable_init();. It compiles and all tests passed.

[L03] Require messages not indicative of the contracts

Almost all require message has no indication of which contract it is coming from. It is a good practice for debugging and monitoring purposes to mark the contract that reverted.

For example, https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/MessageProxy.sol#L188 the revert message could be "MessageProxy: Extra contract is not registered". Many other instances are also present in almost all contracts by globally search require.

[L04] Use initializer instead of onlyInitializing

In OpenZeppline-contracts-upgradeable v4.5 initializer has a new modifier https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/20380ee699406211ae88c4edd11b0af87c8abdd6/contracts/proxy/utils/Initializable.sol#L72

modifier onlyInitializing() { require(_initializing, "Initializable: contract is not initializing"); _; }

to mark and protect the initializing functions that should not be called directly called.

Instances such as

[L05] Inconsistent use of schainHash or schainName when referencing a schain

There is inconsistency in referring to a schain, sometimes by bytes32 hash, sometimes by string name in a single contract.

For example, in SkaleManagerClient,

https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/mainnet/SkaleManagerClient.sol#L70-L73 uses schainNash and https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/mainnet/SkaleManagerClient.sol#L42-L48 uses schainName.

Bytes32 should be preferred over string for gas-saving reasons.

[L06] Schain owner cannot add ERC20 token manually as specified in the doc

The specification in the doc is inconsistent with the code. For example in https://docs.skale.network/ima/1.2.x/managing-erc20 and the diagram https://docs.skale.network/ima/1.2.x/flows#_manual_setup_steps is not consistent with the code.

Minter role in ERC20 is granted automatically either to TokenManagerERC20 or to the deployer in the constructor.

[L07] Outdated Solidity Version

Solidity version 0.8.6 is used throughout. Many updates have been applied to the compiler. A more recent version such as 0.8.10 would work better. The latest version is 0.8.11.

[L08] Use OnlyRole() modifier from inherited contract AccessControlUpgradeable

Many instances can be simplified using the OnlyRole modifier.

https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/20380ee699406211ae88c4edd11b0af87c8abdd6/contracts/access/AccessControlUpgradeable.sol#L75-L78

For instance, https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/schain/MessageProxyForSchain.sol#L237 and https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/schain/tokens/ERC20OnChain.sol#L60-L61 and many other role-based require statements.

Informational

[N01] NatSpec on Events and Functions

Many functions and events do not have @param and @return labelled in Natspec, for instance

and many others.

[N02] initialize functions positioned too much down the contract

initialize function is a special feature in upgradeability patterns and often important and prone to grave consequences too. It is a good practice to put them at the beginning of the contract, like those in the OpenZeppelin-contracts-upgradeable library.

[N03] Repeated code

Repeated unimplemented function postIncomingMessages() in both

and "@skalenetwork/ima-interfaces/IMessageProxy.sol" .

One can remove the one in MessageProxy.sol without affecting any test results.

Gas Saving

[G01] public functions can be external

Here are two example instances

#0 - DimaStebaev

2022-03-14T14:00:52Z

L06: If SKALE chain owner adds custom ERC20 it must allow TokenManager to minting it. Custom token can have custom logic of minting N02: In our code style we always put public functions after external G01: They are called inside smart contract in child contracts

#1 - GalloDaSballo

2022-05-05T15:00:45Z

[L01] Outdated version of inherited contracts and upgrade library

Would like to see explanation of vulns like other wardens, downgrading to non-critical

[L02] Not calling inherited function directly

Also non-critical as it's just explicit inheritance

[L03] Require messages not indicative of the contracts

Non-critical as if all messages are unique you don't need the contract, also the revert trace will show you the contract as well

[L04] Use initializer instead of onlyInitializing

I think this is an interesting and valid finding

[L05] Inconsistent use of schainHash or schainName when referencing a schain

Agree but Non-critical

[L06] Schain owner cannot add ERC20 token manually as specified in the doc

Disagree with finding per-sponsor reply

[L07] Outdated Solidity Version

Will leave as valid although the warden has missed some bugs from 0.8.6

[L08] Use OnlyRole() modifier from inherited contract AccessControlUpgradeable

Agree, but non-critical as it's just a refactoring

[N01] NatSpec on Events and Functions

Agree

[N02] initialize functions positioned too much down the contract

Per the sponsor's styleguide, consistency > dogma

[N03] Repeated code

I believe this is a test function but finding is valid nonetheless

I really like this report as it's short and sweet and has some interesting finds, good job!

Findings Information

🌟 Selected for report: IllIllI

Also found by: 0x1f8b, CertoraInc, TerrierLover, Tomio, WatchPug, d4rk, gzeon, kenta, kyliek, m_smirnova2020, rfa, robee, saian, ye0lde

Labels

bug
G (Gas Optimization)
sponsor disputed

Awards

162.1871 USDC - $162.19

External Links

Lines of code

https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/mainnet/MessageProxyForMainnet.sol#L232-L233

Vulnerability details

Impact

Division by zero will cause EVM to revert in calculating additionalGasPerMessage in MessageProxyForMainnet.postIncomingMessages.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

See https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/mainnet/MessageProxyForMainnet.sol#L232-L233

Unit test shows

Error: VM Exception while processing transaction: reverted with panic code 0x12 (Division or modulo division by zero)

Tools Used

validate above https://github.com/skalenetwork/ima-c4-audit/blob/11d6a6ae5bf16af552edd75183791375e501915f/contracts/mainnet/MessageProxyForMainnet.sol#L232-L233 by

require(messages.length > 0, "MsgProxyForMainnet:zero message length");

#0 - cstrangedk

2022-03-04T20:34:42Z

IMA Agent would never send a transaction with 0 messages, as this is defined in the agent protocol. Further, theoretically if an agent could send a transaction with 0 message, transaction would be simply be reverted and agent would incur gas spend in the attempt.

#1 - GalloDaSballo

2022-04-27T15:39:49Z

I have to agree with the sponsor that the finding is gas level in nature, the worst case scenario is that the tx will revert after some computation has happened, which doesn't warrant the Med Severity

#2 - GalloDaSballo

2022-04-27T15:40:08Z

For that reason am downgrading to gas

#3 - GalloDaSballo

2022-06-01T23:47:47Z

Have given a 0 as this doesn't save gas

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