code4rena-2023-01-biconomy-m08

[M-08] Transaction can fail due to batchId collision

보고서

Summary

예약된 batchId 값을 임의로 이용할 수 있어 트랜잭션 실패를 야기할 수 있다.

Keyword

nonce

Vulnerability

SmartAccount에 콜을 하는 방법은 다음과 같다.

  1. EntryPoint를 통해: SmartAccount의 execFromEntryPoint, execute, executeBatch 함수를 통해 콜을 전달한다.
  2. 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