Improper initialization within the UUPS pattern
Description
In the UUPS pattern, the upgrade functionality of the proxy is typically found within the implementation contract.
// PythLazer.sol::initialize()
function initialize(address _topAuthority) public initializer {
__Ownable_init(_topAuthority);
__UUPSUpgradeable_init();
verification_fee = 1 wei;
}Prior to OpenZeppelin version 4.7.3 and the Shanghai fork, a common attack technique involved frontrunning the initialize function of an implementation contract and using delegatecall within the upgradeToAndCall function to self-destruct the implementation. Since self-destruct has been deprecated on ETH mainnet, this specific attack vector is no longer a concern.
// UUPSUpgradeable.sol::upgradeToAndCall()
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}However, the invocation of initialize in the implementation contract can still be frontrun. If this function contains critical logic, it may introduce another attack vector with potentially widespread implications for the protocol.
To clarify, this is not a security vulnerability but a design consideration for upgradeable contracts.
Impact
While any user could potentially take over the uninitialized implementation contract, this vulnerability is limited to the implementation contract itself.
Recommendations
In order to prevent the invocation of the initialize function in the implementation contract below, it is advisable↗ to include the _disableInitializers function within the constructor.
constructor() {
_disableInitializers();
}Remediation
This issue has been acknowledged by Pyth Data Association, and a fix was implemented in commit 49de9a23↗.