Platform: Code4rena
Start Date: 03/05/2022
Pot Size: $75,000 USDC
Total HM: 6
Participants: 55
Period: 7 days
Judge: Albert Chon
Total Solo HM: 2
Id: 116
League: COSMOS
Rank: 4/55
Findings: 2
Award: $6,338.83
🌟 Selected for report: 2
🚀 Solo Findings: 0
🌟 Selected for report: p_crypt0
Also found by: 0x1337, GermanKuber, IllIllI, WatchPug, csanuragjain, danb, dirk_y, kirk-baird
502.4722 USDC - $502.47
https://github.com/code-423n4/2022-05-cudos/blob/de39cf3cd1f1e1cf211819b06d4acf6a043acda0/solidity/contracts/Gravity.sol#L632-L638 https://github.com/code-423n4/2022-05-cudos/blob/de39cf3cd1f1e1cf211819b06d4acf6a043acda0/solidity/contracts/Gravity.sol#L595-L609
Ability for admin to drain all ERC20 funds stored in contract at will, meaning all ERC20 based Cudos tokens (and any other ERC20 tokens stored in the contract) could be extracted by anyone with admin role and later sold, leaving users funds bridged on Cudos Cosmos chain with no ERC20 representation stored across the bridge - similar in impact as the wormhole hack.
This issue ought to fall within the limits the team allocated on assessing the governance role setups, since it describes a full-fledged security risk regarding users' funds. Crucially, this function is not in the original Gravity Bridge contract for Gravity.sol: https://github.com/Gravity-Bridge/Gravity-Bridge/blob/f65d9da692c1af76f8188bd17b55dea58c1d8723/solidity/contracts/Gravity.sol
Furthermore, the function has not been commented and does not appear in the documentation, suggesting that it has perhaps not yet been reasoned through by the development team and it's critical this is flagged in the security audit.
Firstly, User with admin role granted waits until CUDOS bridge has decent TVL from users bridging their CUDOS tokens from Ethereum to the CUDOS Cosmos chain,
Secondly, User manually calls withdrawERC20(address _tokenAddress) with the ERC token address of the CUDOS token
function withdrawERC20( address _tokenAddress) external { require(cudosAccessControls.hasAdminRole(msg.sender), "Recipient is not an admin"); uint256 totalBalance = IERC20(_tokenAddress).balanceOf(address(this)); IERC20(_tokenAddress).safeTransfer(msg.sender , totalBalance); }
Thirdly, withdrawERC20() function checks if user has admin role and if so withdraws all the tokens of a given token address straight to the admin's personal wallet
require(cudosAccessControls.hasAdminRole(msg.sender), "Recipient is not an admin"); uint256 totalBalance = IERC20(_tokenAddress).balanceOf(address(this)); IERC20(_tokenAddress).safeTransfer(msg.sender , totalBalance);
Fourth, user exchanges CUDOS on DEX and then sends funds to tornado cash, leaving all user funds at risk.
My own logical reasoning and discussion with team on discord for confirmation of admin roles and function's logic.
Delete the function or alternatively, send all funds to the '0' address to burn rather than give them to the admin.
Change withdrawERC20 to:
function burnERC20( address _tokenAddress) external { require(cudosAccessControls.hasAdminRole(msg.sender), "Recipient is not an admin"); uint256 totalBalance = IERC20(_tokenAddress).balanceOf(address(0)); - IERC20(_tokenAddress).safeTransfer(msg.sender , totalBalance); + IERC20(_tokenAddress).safeTransfer(address(0) , totalBalance); }
#0 - maptuhec
2022-05-11T06:45:09Z
The reason why we have created this functions is that, if the bridge stop working, the funds for the users would be locked, and there is no chance to withdraw them. CUDOS have no intention and incentive to maliciously withdraw the ERC20 tokes, because that would lead to loosing the trust in its clients and thus killing their own network. The best way for handling this is to communicate this with the community so they can be aware.
🌟 Selected for report: p_crypt0
Also found by: CertoraInc
5836.3584 USDC - $5,836.36
No checks for non-Cudos tokens mean that non-Cudos ERC20 tokens will be lost to the contract, with the user not having any chance of retrieving them.
However, the admin can retrieve them through withdrawERC20.
Impact is that users lose their funds, but admins gain them.
The mistakes could be mitigated on the contract, by checking against a list of supported tokens, so that users don't get the bad experience of losing funds and CUDOS doesn't have to manually refund users
User sends 100 ETH through sendToCosmos, hoping to retrieve 100 synthetic ETH on Cudos chain but finds that funds never appear.
function sendToCosmos( address _tokenContract, bytes32 _destination, uint256 _amount ) public nonReentrant { IERC20(_tokenContract).safeTransferFrom(msg.sender, address(this), _amount); state_lastEventNonce = state_lastEventNonce.add(1); emit SendToCosmosEvent( _tokenContract, msg.sender, _destination, _amount, state_lastEventNonce ); }
Admin can retrieve these funds should they wish, but user never gets them back because the contract does not check whether the token is supported.
function withdrawERC20( address _tokenAddress) external { require(cudosAccessControls.hasAdminRole(msg.sender), "Recipient is not an admin"); uint256 totalBalance = IERC20(_tokenAddress).balanceOf(address(this)); IERC20(_tokenAddress).safeTransfer(msg.sender , totalBalance); }
Logic and discussion with @germanimp
Add checks in sendToCosmos to check the incoming tokenAddress against a supported token list, so that user funds don't get lost and admin don't need to bother refunding.
#0 - V-Staykov
2022-05-17T07:40:54Z