code4rena-2023-01-biconomy-m05
[M-05] DoS of user operations and loss of user transaction fee due to insufficient gas value submission by malicious bundler
Summary
악의적인 Bundler가 트랜잭션 발생시 Gas limit을 조절하여 유저 트랜잭션을 임의로 실패시킬 수 있다. Bundler는 가스 비용을 환급받고, nonce는 업데이트 되지만 유저 트랜잭션은 실패한다.
Keyword
DoS, griefing attack, gas, gas limit
Vulnerability
- contracts/smart-contract-wallet/aa-4337/core/EntryPoint.sol#L68-L86
- contracts/smart-contract-wallet/aa-4337/core/EntryPoint.sol#L168-L190
악성 Bundler(Relayer)가 충분하지 않은 Gas를 사용하여 실패하게 할 수 있다. 콜이 실패하더라도 사용자나 Paymaster가 여전히 가스비용을 지불해야 한다.
EntryPoint.innerHandleOp 에서는 유저가 지정한 만큼의 가스를 이용하여 call을 호출한다. (bool success,bytes memory result) = address(mUserOp.sender).call{gas : mUserOp.callGasLimit}(callData);
악의적인 Bundler가 EntryPoint.innerHandleOp 를 호출할 때, 애초에 적은 Gas만을 가지고 호출한다면 call이 gas 부족으로 실패하게 된다. 하지만 EIP-150에 의해 call을 할 때는 남은 가스의 63/64 를 이용하기에, 아직 1/64 Gas는 남아있다.
function innerHandleOp(bytes calldata callData, UserOpInfo memory opInfo, bytes calldata context) external returns (uint256 actualGasCost) {
uint256 preGas = gasleft();
require(msg.sender == address(this), "AA92 internal call only");
MemoryUserOp memory mUserOp = opInfo.mUserOp;
IPaymaster.PostOpMode mode = IPaymaster.PostOpMode.opSucceeded;
if (callData.length > 0) {
(bool success,bytes memory result) = address(mUserOp.sender).call{gas : mUserOp.callGasLimit}(callData);
if (!success) {
if (result.length > 0) {
emit UserOperationRevertReason(opInfo.userOpHash, mUserOp.sender, mUserOp.nonce, result);
}
mode = IPaymaster.PostOpMode.opReverted;
}
}
unchecked {
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas;
//note: opIndex is ignored (relevant only if mode==postOpReverted, which is only possible outside of innerHandleOp)
return _handlePostOp(0, mode, opInfo, context, actualGas);
}
}작업이 실패하더라도 사용한 Gas 비용은 _handlePostOp 에서 돌려받게 된다. 1/64 Gas 를 이용하여 환급 처리를 진행할 수 있다.
또한 유저의 트랜잭션은 실패하였지만 nonce 업데이트는 유저 트랜잭션과는 별개로 업데이트 되었기 때문에, nonce는 소비된다.
mUserOp.callGasLimit 보다 gasLeft() 가 더 적은 경우에 대하여 명시적으로 처리하지 않았기때문에, Bundler는 임의로 트랜잭션을 실패시키면서도 가스는 환급받을 수 있다.
공격자가 직접적으로 이득을 보진 않지만 방해를 한다는 점에서 griefing attack 로 분류할 수 있다.
Impact
유저의 트랜잭션은 실패시키면서 유저 또는 paymaster의 가스비만 소비시킨다.
Mitigation
실제 남은 Gas 양과 유저의 트랜잭션을 실행하기 위해 필요한 Gas 를 비교하여 충분한 가스가 없는 경우를 핸들링한다.
require(gasleft()*63/64 >= requiredGasForTheCall); 와 같이 콜을 취소시킨다.
Memo
M-01과 M-05가 헷갈렸는데, M-01의 경우 Nonce가 맞지 않아 검증 단계에서 실패하는 것이고, M-05의 경우 실행 중 가스가 부족하여 실패한 것이다. M-05의 경우 트랜잭션 자체는 실행 시도가 가능했으므로 가스비를 환급받을 수 있다.
tags: bughunting, smart contract, biconomy, account abstraction, erc4337, account abstraction bundler, gas, griefing attack, gas limit, wallet, severity medium