Uninialized or front-runnable .init function in proxy implementation contract
Impact
DoS for all users' smart account proxies leading to locked funds forever.
Proof of Concept
Nowhere in the code the SmartAccount.sol implementation contract is initialized by calling the .init function. This means that the owner of the SmartAccount.sol implementation contract is unknown (as it is set in the .init function) or the .init function may not even be called -
can be forgotten by the team and later called or front-runned by an attacker. This will give an external account control over the implementation control such as executing any transaction from it via call and delegatecall.
If that happens, the attacker can immediately sign and execute a transaction directly on the SmartAccount.sol implementation contract (via call) using .execTransaction and make a delegatecall by setting the tx.operation to 1 and tx.to to a contract containing a selfdestruct operation. This will block all proxies and stuck all users's funds forever due to DoS and non-upgradeability as the updateImplementation mechanism is in the destroyed SmartAccount.sol implementation contract.
Tools Used
Manual review
Recommended Mitigation Steps
The standard way a factory pattern works is that the factory deploys the implementation contract it its constructor and then calls init to become the owner of this implementation contract. As the factory does not implement a function such as f.e. executeArbitraryTransaction there is no way the .execTransaction is called directly on the SmartAccount by the owner. Even more that way there can not be a valid signature to pass the checkSignatures validation.
Create the implementation contract in the SmartAccountFactory.constructor instead of externally passing it. Then call the .init function on the newly deployed implementation contract and set the owner to be the SmartAccountFactory address (address(this))
Lines of code
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L166 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L17
Vulnerability details
Uninialized or front-runnable
.init
function in proxy implementation contractImpact
DoS for all users' smart account proxies leading to locked funds forever.
Proof of Concept
Nowhere in the code the SmartAccount.sol implementation contract is initialized by calling the
.init
function. This means that the owner of the SmartAccount.sol implementation contract is unknown (as it is set in the.init
function) or the.init
function may not even be called - can be forgotten by the team and later called or front-runned by an attacker. This will give an external account control over the implementation control such as executing any transaction from it viacall
anddelegatecall
.If that happens, the attacker can immediately sign and execute a transaction directly on the SmartAccount.sol implementation contract (via
call
) using.execTransaction
and make adelegatecall
by setting the tx.operation to 1 and tx.to to a contract containing aselfdestruct
operation. This will block all proxies and stuck all users's funds forever due to DoS and non-upgradeability as theupdateImplementation
mechanism is in the destroyed SmartAccount.sol implementation contract.Tools Used
Manual review
Recommended Mitigation Steps
The standard way a factory pattern works is that the factory deploys the implementation contract it its constructor and then calls
init
to become theowner
of this implementation contract. As the factory does not implement a function such as f.e.executeArbitraryTransaction
there is no way the.execTransaction
is called directly on theSmartAccount
by the owner. Even more that way there can not be a valid signature to pass thecheckSignatures
validation.Create the implementation contract in the SmartAccountFactory.constructor instead of externally passing it. Then call the
.init
function on the newly deployed implementation contract and set the owner to be theSmartAccountFactory
address (address(this)
)