code4rena-2023-01-biconomy-m08
[M-08] Transaction can fail due to batchId collision
Summary
예약된 batchId 값을 임의로 이용할 수 있어 트랜잭션 실패를 야기할 수 있다.
Keyword
nonce
Vulnerability
- contracts/smart-contract-wallet/SmartAccount.sol#L501
- contracts/smart-contract-wallet/SmartAccount.sol#L216
SmartAccount에 콜을 하는 방법은 다음과 같다.
- EntryPoint를 통해: SmartAccount의
execFromEntryPoint,execute,executeBatch함수를 통해 콜을 전달한다. execTransaction직접 호출을 통해
EntryPoint를 통해 호출되는 경우에는 batchId가 #0으로 고정된다. EntryPoint에서트랜잭션을 실행하기 전 smartAccount.validateUserOp 를 호출하고, 여기에서 batchId 0번에 대한 nonce를 업데이트한다. batchId 가 하드코딩 되어있음에 주목한다.
function _validateAndUpdateNonce(UserOperation calldata userOp) internal override {
require(nonces[0]++ == userOp.nonce, "account: invalid nonce");
}이 때, execTransaction 로 직접 호출할 때 batchId #0을 사용하지 못한다는 제한이 없다. 따라서 호출자가 임의로 batchId #0으로 설정할 수 있다.
function execTransaction(
Transaction memory _tx,
uint256 batchId,
FeeRefund memory refundInfo,
bytes memory signatures
) public payable virtual override returns (bool success) {
...
nonces[batchId]++;
....
}bundler에 아직 처리되지 않은 트랜잭션이 있는 상황에 execTransaction를 호출하여 batchId #0을 업데이트하면, 처리 대기중인 트랜잭션의 nonce가 맞지 않게 된다. 따라서 대기중이었던 트랜잭션은 실패할 것이다.
Impact
예상치 못한 트랜잭션 실패를 야기할 수 있다.
Mitigation
execTransaction 호출시 batchId 를 #0으로 설정할 수 없도록 제한하여 EntryPoint용 batchId를 분리한다.
require(batchId != 0, "batchId 0 is used only by EntryPoint")를 추가한다.
Memo
execTransaction 도 결국 유저의 서명이 필요한 것이므로 취약점으로 보기는 어렵지만, 이론적으로 문제를 발생할 소지가 있긴 하다.
tags: bughunting, smart contract, biconomy, account abstraction, erc4337, upgradeable, wallet, severity medium