Skip to content
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

ProtocolDAO contract cannot be updated #729

Closed
code423n4 opened this issue Jan 3, 2023 · 6 comments
Closed

ProtocolDAO contract cannot be updated #729

code423n4 opened this issue Jan 3, 2023 · 6 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working duplicate-742 edited-by-warden satisfactory satisfies C4 submission criteria; eligible for awards sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")

Comments

@code423n4
Copy link
Contributor

code423n4 commented Jan 3, 2023

Lines of code

https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/ProtocolDAO.sol#L209
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/Storage.sol#L29

Vulnerability details

Impact

The protocol has chosen a data separation upgrade mechanism which means that all the data is located in a smart contract not upgradable and the logic in upgradeable contracts. TheProtocolDAO contract which is part of the upgradable ones via the storage registration technique (as per documentation) is actually not upgradeable.

The impact is that the ProtocolDAO contract cannot be updated which means that, if any logic is added to the smart contract, the protocol will be stuck with it.

Proof of Concept

The modifier onlyRegisteredNetworkContract is limiting the execution of setting and deleting keys in Storage.sol.

Practically when updating the ProtocolDAO, the ProtocolDAO contract is unregistered which means that the following key keccak256(abi.encodePacked("contract.exists", addr))) is deleted and will revert when deleting the address due to the condition check on the key in the modifier.

// Storage.sol
modifier onlyRegisteredNetworkContract() {
	if (booleanStorage[keccak256(abi.encodePacked("contract.exists", msg.sender))] == false && msg.sender != guardian) {
		revert InvalidOrOutdatedContract();
	}
	_;
}

// ProtocolDAO.sol
function registerContract(address addr, string memory name) public onlyGuardian {
	setBool(keccak256(abi.encodePacked("contract.exists", addr)), true);
	setAddress(keccak256(abi.encodePacked("contract.address", name)), addr);
	setString(keccak256(abi.encodePacked("contract.name", addr)), name);
}
function unregisterContract(address addr) public onlyGuardian {
	string memory name = getContractName(addr);
	deleteBool(keccak256(abi.encodePacked("contract.exists", addr)));
	deleteAddress(keccak256(abi.encodePacked("contract.address", name)));
	deleteString(keccak256(abi.encodePacked("contract.name", addr)));
}
function upgradeExistingContract(
	address newAddr,
	string memory newName,
	address existingAddr
) external onlyGuardian {
	registerContract(newAddr, newName);
	unregisterContract(existingAddr);
}

To reproduce it, add this method in the ProtocolDAO.t.sol and run the test by using:

forge test --match-test "testUpgradeProtocolDao" -vvvv

Tools Used

Visual Studio Code, Unit tests (Foundry)

Recommended Mitigation Steps

By removing the boolean existence at the end, we are allowing the deletion of the address and the name of the contract without triggering the exception from onlyRegisteredNetworkContract.

function upgradeExistingContract(
  address newAddr,
  string memory newName,
  address existingAddr
) external onlyGuardian {
  string memory existingName = getContractName(existingAddr);

  deleteAddress(keccak256(abi.encodePacked("contract.address", existingName)));
  deleteString(keccak256(abi.encodePacked("contract.name", existingAddr)));
  setBool(keccak256(abi.encodePacked("contract.exists", newAddr)), true);
  setAddress(keccak256(abi.encodePacked("contract.address", newName)), newAddr);
  setString(keccak256(abi.encodePacked("contract.name", newAddr)), newName);
  deleteBool(keccak256(abi.encodePacked("contract.exists", existingAddr)));
}
@code423n4 code423n4 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Jan 3, 2023
code423n4 added a commit that referenced this issue Jan 3, 2023
C4-Staff added a commit that referenced this issue Jan 6, 2023
@GalloDaSballo
Copy link

Looks off because I got a ton of report of the function being wrong, but this is the only one mentioning a revert

@emersoncloud
Copy link

This is a special case where we delete the address of the ProtocolDAO and then try to make another change as the ProtocolDAO which fails.

@emersoncloud emersoncloud added the sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") label Jan 18, 2023
@GalloDaSballo
Copy link

I believe it makes sense to group under #742

@GalloDaSballo
Copy link

I will award full merit despite this being an instance, because it's a specific instance that does make sense

@c4-judge
Copy link
Contributor

GalloDaSballo marked the issue as duplicate of #742

@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Feb 8, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Feb 8, 2023

GalloDaSballo marked the issue as satisfactory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working duplicate-742 edited-by-warden satisfactory satisfies C4 submission criteria; eligible for awards sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
Projects
None yet
Development

No branches or pull requests

4 participants