-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Coding logic of the contract upgrading renders upgrading contracts impractical #742
Comments
Exceptional value, making primary |
GalloDaSballo marked the issue as primary issue |
upgradeExistingContract
cannot upgrade contracts with a new implementation having the same name
#724
emersoncloud marked the issue as sponsor confirmed |
Not sure if this is considered a high since there isnt a direct loss of funds? Medium: Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements. |
In spite of the lack of risk for Principal, a core functionality of the protocol is impaired. This has to be countered versus the requirement of the names being the same, which intuitively seems to be the intended use case, as changing the name would also break balances / other integrations such as the modifier |
The other side of the argument is that the mistake is still fixable by the same actor within a reasonable time frame |
I believe the finding is meaningful and well written, but ultimately the damage can be undone with a follow-up call to Because of this am downgrading to Medium Severity. |
GalloDaSballo changed the severity to 2 (Med Risk) |
GalloDaSballo marked the issue as selected for report |
Lines of code
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/ProtocolDAO.sol#L209-L216
Vulnerability details
Impact
Link to original code
Function
ProtocolDAO.upgradeExistingContract
handles contract upgrading. However, there are multiple implicaitons of the coding logic in the function, which render the contract upgrading impractical.Implication 1:
The above function
upgradeExistingContract
registers the upgraded contract first, then unregisters the existing contract. This leads to the requirement that the upgraded contract name must be different from the existing contract name. Otherwise the updated contract address returned byStorage.getAddress(keccak256(abi.encodePacked("contract.address", contractName)))
will beaddress(0)
(please refer to the below POC Testcase 1). This is because if the upgraded contract uses the original name (i.e. the contract name is not changed), function callunregisterContract(existingAddr)
in theupgradeExistingContract
will override the registered contract address inStorage
to address(0) due to the use of the same contract name.Since using the same name after upgrading will run into trouble with current coding logic, a safeguard should be in place to make sure two names are really different. For example, put this statement in the
upgradeExistingContract
function:require(newName != existingName, "Name not changed");
, whereexistingName
can be obtained using:string memory existingName = store.getString(keccak256(abi.encodePacked("contract.name", existingAddr)));
.Implication 2:
If we really want a different name for an upgraded contract, we then get into more serious troubles: We have to upgrade other contracts that reference the upgraded contract since contract names are referenced mostly hardcoded (for security considerations). This may lead to a very complicated issue because contracts are cross-referenced.
For example, contract
ClaimNodeOp
references contractsRewardsPool
,ProtocolDAO
andStaking
. At the same time, contractClaimNodeOp
is referenced by contractsRewardsPool
andStaking
. This means that:ClaimNodeOp
was upgraded, which means the contract nameClaimNodeOp
was changed;RewardsPool
andStaking
to be upgraded (with new names) in order to correctly reference to newly namedClaimNodeOp
contract;RewardsPool
orStaking
to be upgraded in order to correctly reference them;I rate this issue as high severity due to the fact that:
Contract upgradability is one of the main features of the whole system design (all other contracts are designed upgradable except for
TokenGGP
,Storage
andVault
). However, the currentupgradeExistingContract
function's coding logic requires the upgraded contract must change its name (refer to the below Testcase 1). This inturn requires to upgrade all relevant cross-referenced contracts (refer to the below Testcase 2). Thus leading to a quite serous code management issue while upgrading contracts, and renders upgrading contracts impractical.Proof of Concept
Testcase 1: This testcase demonstrates that current coding logic of upgrading contracts requires: the upgraded contract must change its name. Otherwise contract upgrading will run into issue.
Put the below test case in file
ProtocolDAO.t.sol
. The test case demonstrates thatProtocolDAO.upgradeExistingContract
does not function properly if the upgraded contract does not change the name. That is: the upgraded contract address returned byStorage.getAddress(keccak256(abi.encodePacked("contract.address", contractName)))
will beaddress(0)
if its name unchanged.Testcase 2: This testcase demonstrates that current coding logic of upgrading contracts requires: in order to upgrade a single contract, all cross-referenced contracts have to be upgraded and change their names. Otherwise, other contracts will run into issues.
If the upgraded contract does change its name, contract upgrading will succeed. However, other contracts' functions that reference the upgraded contract will fail due to referencing hardcoded contract name.
The below testcase upgrades contract
ClaimNodeOp
toClaimNodeOpV2
. Then, contractStaking
callsincreaseGGPRewards
which references hardcoded contract nameClaimNodeOp
in its modifier. The call is failed.Test steps:
ClaimNodeOp.sol
toClaimNodeOpV2.sol
, and rename the contract name fromClaimNodeOp
toClaimNodeOpV2
in fileClaimNodeOpV2.sol
;UpgradeContractIssue.t.sol
under foldertest/unit/
;Note: In order to test actual function call after upgrading contract, this testcase upgrades a real contract
ClaimNodeOp
. This is different from the above Testcase 1 which uses a random address to simulate a contract.Tools Used
Manual code review
Recommended Mitigation Steps
upgradeExistingContract
definition, swap fucnction call sequence betweenregisterContract()
andunregisterContract()
so that contract names can keep unchanged after upgrading. That is, the modified function would be:POC of Mitigation:
After the above recommended mitigation, the below Testcase vefifies that after upgrading contracts, other contract's functions, which reference the hardcoded contract name, can still opetate correctly.
ProtocolDAO.upgradeExistingContract
;UpgradeContractImproved.t.sol
under foldertest/unit/
;Note: Since we don't change the upgraded contract name, for testing purpose, we just need to create a new contract instance (so that the contract instance address is changed) to simulate the contract upgrading.
The text was updated successfully, but these errors were encountered: