Sparkn

CodeFox Inc.
DeFiFoundryProxy
15,000 USDC
View results
Submission Details
Severity: low
Valid

Unsafe ownership removal of the ProxyFactory contract

Summary

The ProxyFactory contract inherently implements the renounceOwnership(), which is risky. Due to possible human error, the owner-privileged functions using the onlyOwner modifier can be locked permanently.

Vulnerability Details

The ProxyFactory contract inherits from OpenZeppelin's Ownable contract. The Ownable implements the renounceOwnership() that can permanently remove the ProxyFactory contract’s ownership.

If an owner (protocol admin) mistakenly invokes the renounceOwnership(), they will immediately lose ownership of the ProxyFactory contract, and this action cannot be undone.

// FILE: lib/openzeppelin-contracts/contracts/access/Ownable.sol
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}

Impact

The following owner-privileged functions using the onlyOwner modifier will be locked permanently.

To clarify the vulnerability, although only an owner can execute the renounceOwnership() and the owner is trusted, the incident can occur by mistake (i.e., this vulnerability is not about any centralization or trust risks; it is about the risks of function invocation mistakes only).

The likelihood is considered LOW (since the owner is expected to do due diligence). The impact is considered HIGH. Therefore, the severity is considered MEDIUM.

Tools Used

Manual Review

Recommendations

If the renounceOwnership() is not meant to be used, turn off its functionality by overriding the function and executing the revert() when the function is called.

If the renounceOwnership() is expected to be used, implement a two-step ownership renouncement mechanism. One example of the two-step renouncement mechanism with a confirmation interval works as follows.

  1. An owner calls the initiateOwnershipRenouncement(). This function will set the initialTimestamp (using block.timestamp).

  2. An owner calls the confirmOwnershipRenouncement(). This function will compare the diffTimestamp between the initialTimestamp (in step 1) and the currentTimestamp (block.timestamp). If diffTimestamp <= expectedConfirmationInterval, the function will renounce the contract’s ownership. Otherwise, the function will revert the transaction.

The two-step ownership renouncement mechanism guarantees that an owner will not remove the contract's ownership by mistake.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.