Platform: Code4rena
Start Date: 04/01/2023
Pot Size: $60,500 USDC
Total HM: 15
Participants: 105
Period: 5 days
Judge: gzeon
Total Solo HM: 1
Id: 200
League: ETH
Rank: 20/105
Findings: 6
Award: $621.96
π Selected for report: 0
π Solo Findings: 0
π Selected for report: V_B
Also found by: 0xdeadbeef0x, HE1M, Koolex, Matin, adriro, chaduke, gogo, hihen, jonatascm, kankodu, ro, smit_rajput, spacelord47, taek
125.5131 USDC - $125.51
Functions from the logic contract (SmartAccount) shouldn't be called directly. a bad actor can take the ownership by calling init directly then destroy it making all proxies (Smart Contract Wallets) pointing to a non-existing contract.
One could argue that Biconomy's admin can call init after deploying it. However, there is still a risk of front-runing it.
SmartAccount is left uninitialized. there is no code that's initializing it nor disabling initializing it.
The attacker initializes it to gets the ownership.
Since SmartAccount can delegate calls, the attacker can delegate a call to a malcious contract calling selfdestruct which results in destroying SmartAccount.
Manual analysis
As per openzeppelin's doc:
To prevent the implementation contract from being used, you should invoke the _disableInitializers function in the constructor to automatically lock it when it is deployed
/// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); }
#0 - c4-judge
2023-01-17T14:03:26Z
gzeon-c4 marked the issue as duplicate of #496
#1 - c4-sponsor
2023-01-25T23:37:40Z
livingrockrises marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-10T12:42:47Z
gzeon-c4 marked the issue as satisfactory
22.7235 USDC - $22.72
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/interfaces/ISignatureValidator.sol#L4-L20 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L342
Check:
The EIP-2171 specs https://eips.ethereum.org/EIPS/eip-1271#specification
Line 342 in SmartAccount
require(ISignatureValidator(_signer).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "BSA024");
function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4);
Manual analysis
#0 - c4-judge
2023-01-17T06:56:28Z
gzeon-c4 marked the issue as duplicate of #175
#1 - livingrockrises
2023-01-26T00:15:42Z
elaborate on this please - Change the implementation of ISignatureValidator to adhere to EIP-1271 specs
#2 - c4-sponsor
2023-02-09T11:46:09Z
livingrockrises marked the issue as sponsor confirmed
#3 - c4-judge
2023-02-10T12:28:26Z
gzeon-c4 marked the issue as satisfactory
π Selected for report: Tointer
Also found by: 0xdeadbeef0x, HE1M, Haipls, Koolex, PwnedNoMore, Tricko, V_B, csanuragjain, orion, peakbolt, ro, romand, taek
149.4204 USDC - $149.42
Any transaction that was already executed through execTransaction
function can be executed again (many times not only once).
checkSignatures
function takes into account nonces to prevent a signature from being valid again. The nonces increases by one everytime a transaction gets executed which protects from replaying. However, Biconomy uses a 2D nonces to support batches, and the caller has to pass the batchId when calling execTransaction
. A malicious actor can use a batchId that was never used (within the uint256 range which is big) and replay all transactions in the same order in a batchId that was used by a user.
const txs: MetaTransaction[] = [ buildContractCall( walletFactory, "deployCounterFactualWallet", [owner, entryPoint.address, handler.address, 1], 0 ), buildContractCall( userSCW, "execTransaction", [transaction, 1, refundInfo, signature], 0 ), buildContractCall( userSCW, "execTransaction", [transaction, 77, refundInfo, signature], 0 ), ];
expect(await token.balanceOf(charlie)).to.equal( ethers.utils.parseEther("20") );
npx hardhat test ./test/smart-wallet/onboardings-test.ts
Manual analysis
Include the batchId as a part of the signed hashed data.
#0 - c4-judge
2023-01-17T07:09:46Z
gzeon-c4 marked the issue as duplicate of #485
#1 - c4-sponsor
2023-01-26T00:02:51Z
livingrockrises marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-10T12:34:43Z
gzeon-c4 marked the issue as satisfactory
242.9785 USDC - $242.98
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L506-L513 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol#L63
According to the Biconomy's docs (check contest overview),
Biconomy Smart Account is a smart contract wallet that builds on core concepts of Gnosis / Argent safes and implements an interface to support calls from account abstraction Entry Point contract
As per Biconomy, it doesn't support signature aggregation at the moment, and according to Account Abstraction specs EIP-4337
If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the userOpHash, and SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error should revert
However, the SmartAccount reverts rather than returns SIG_VALIDATION_FAILED. This migh lead to incompatibility since any EntryPoint that would be used in future will expect a returned value in case of invalid signature instead of a revert.
validateUserOp
function (in BaseSmartAccount) calls _validateSignature
to validate the userOp (UserOperation). However, _validateSignature
reverts if the signature is invalid rather than returning SIG_VALIDATION_FAILED.
Check SmartAccount contract for
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash, address) internal override virtual returns (uint256 deadline) { bytes32 hash = userOpHash.toEthSignedMessageHash(); //ignore signature mismatch of from==ZERO_ADDRESS (for eth_callUserOp validation purposes) // solhint-disable-next-line avoid-tx-origin require(owner == hash.recover(userOp.signature) || tx.origin == address(0), "account: wrong signature"); return 0; }
Manual analysis
If the signature is invalid return SIG_VALIDATION_FAILED. for any other error, revert.
Note: adjust the EntryPoint contract according to the suggested change above.
#0 - c4-judge
2023-01-17T14:04:01Z
gzeon-c4 marked the issue as primary issue
#1 - gzeoneth
2023-01-17T14:04:20Z
not high risk
#2 - livingrockrises
2023-01-26T07:16:27Z
you're right. it has to be rebased with the latest code (0.4.0).
#3 - c4-sponsor
2023-01-26T07:16:38Z
livingrockrises marked the issue as sponsor confirmed
#4 - c4-sponsor
2023-01-26T07:16:43Z
livingrockrises marked the issue as disagree with severity
#5 - c4-judge
2023-02-10T11:57:06Z
gzeon-c4 changed the severity to 2 (Med Risk)
#6 - c4-judge
2023-02-10T12:17:03Z
gzeon-c4 marked the issue as selected for report
#7 - vladbochok
2023-02-11T09:53:30Z
Hey @gzeon-c4 @livingrockrises!
The #498 describes that validateUserOp()
SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch.
This is currently not the case, as the case when the account does not support signature aggregation is not supported right now in the code. The validateUserOp() reverts everytime if the recovered signature does not match.
At the same time, the nature of these reports is very similar, so it does not give impact examples, but claim to be incompatible with the standard. All in all, I think this report and its duplicates should be evaluated as duplicates of #498, and not a separate issue.
#8 - livingrockrises
2023-02-11T10:11:36Z
yes it merely talks about contracts should be upgraded as entry point upgrades. when we scoped for C4 ERC4337 was at v0.3.0 now they are at 0.4.0 plus new commits that will become 0.5.0 with these interface updates. we have been already rebasing our wallet source with latest versions. just that it couldn't be sent for c4 audit scope end of year
#9 - vladbochok
2023-02-14T08:05:45Z
@gzeoneth Could you please take a look at my message above?
#10 - gzeon-c4
2023-02-14T08:08:44Z
Hey @gzeon-c4 @livingrockrises!
The #498 describes that
validateUserOp()
SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch.This is currently not the case, as the case when the account does not support signature aggregation is not supported right now in the code. The validateUserOp() reverts everytime if the recovered signature does not match.
At the same time, the nature of these reports is very similar, so it does not give impact examples, but claim to be incompatible with the standard. All in all, I think this report and its duplicates should be evaluated as duplicates of #498, and not a separate issue.
make sense
#11 - c4-judge
2023-02-14T08:09:41Z
gzeon-c4 marked the issue as duplicate of #498
#12 - c4-judge
2023-02-14T08:09:51Z
gzeon-c4 marked the issue as not selected for report
#13 - c4-judge
2023-02-14T08:10:09Z
gzeon-c4 marked the issue as satisfactory
π Selected for report: 0x52
Also found by: 0xSmartContract, Deivitto, Diana, IllIllI, Koolex, Rolezn, SleepingBugs, V_B, adriro, betweenETHlines, cryptostellar5, oyc_109, peanuts
44.8261 USDC - $44.83
If the implementation contract (SmartAccount) was upgraded to a contract which doesn't have upgrading mechanism (e.g. updateImplementation
), then the proxy (i.e. Smart Contract Wallet) is locked forever with the new implementation.
Check updateImplementation
in SmartAccount.
Manual analysis
Use a security mechanism similiar to what's used in UUPS (openzeppelin).
#0 - c4-judge
2023-01-17T07:55:20Z
gzeon-c4 marked the issue as duplicate of #352
#1 - c4-sponsor
2023-02-08T08:19:53Z
livingrockrises marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-10T12:36:41Z
gzeon-c4 marked the issue as satisfactory