Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Invalid

Unchecked Initializer Usage

Summary

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/39e33b2f6b3890573bb1affc41a7e520277ceb2c/src/zlp/ZlpVault.sol#L59

The ZlpVault contract contains an unchecked initializer function, making it susceptible to misuse. If an attacker interacts with the implementation contract directly, they could call the initialize function to gain ownership and control over the contract. This vulnerability arises from improper restriction of initialization.

Vulnerability Details

The vulnerable code resides in the initialize function:

function initialize(
address marketMakingEngine,
uint8 decimalsOffset,
address owner,
IERC20 asset_,
uint128 vaultId
) external initializer {
__Ownable_init(owner);
__ERC4626_init(asset_);
ZlpVaultStorage storage zlpVaultStorage = _getZlpVaultStorage();
zlpVaultStorage.marketMakingEngine = marketMakingEngine;
zlpVaultStorage.decimalsOffset = decimalsOffset;
zlpVaultStorage.vaultId = vaultId;
IERC20(asset_).approve(marketMakingEngine, type(uint256).max);
}

The initializer modifier prevents re-initialization but does not prevent the function from being called directly on the implementation contract.

An attacker could deploy the implementation contract, invoke initialize, and gain ownership (__Ownable_init(owner)).

Impact

Ownership Takeover: The attacker can set themselves as the owner, allowing them to:

Upgrade the contract.

Call privileged functions guarded by onlyOwner.

Fund Loss: Improper initialization could lead to mismanagement of assets or complete loss.

Contract Disruption: Proxy deployment relying on the implementation may fail if the implementation is initialized incorrectly.

Tools Used

Manual Review

Recommendations

Restrict Initialization to Proxy Contracts

Add a validation check to ensure the initialize function is only executed via a proxy:

function initialize(
address marketMakingEngine,
uint8 decimalsOffset,
address owner,
IERC20 asset_,
uint128 vaultId
) external initializer {
require(address(this) != implementationAddress, "Cannot initialize implementation contract");
__Ownable_init(owner);
__ERC4626_init(asset_);
ZlpVaultStorage storage zlpVaultStorage = _getZlpVaultStorage();
zlpVaultStorage.marketMakingEngine = marketMakingEngine;
zlpVaultStorage.decimalsOffset = decimalsOffset;
zlpVaultStorage.vaultId = vaultId;
IERC20(asset_).approve(marketMakingEngine, type(uint256).max);
}

Replace implementationAddress with the actual implementation contract address.

Use constructor to Disable Initialization Permanently

This feature is already present in the contract, but confirm it is enforced:

constructor() {
_disableInitializers();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
5 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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