Incorrect Validation Logic
In this page, we explore vulnerabilities caused by incorrect validation logic.
Improper Handling of Signature Error
In some implementations, a failed signature verification for the paymaster or user account causes a revert. However, according to ERC-4337 specification, signature verification failures should not trigger a revert.
If the account does not support signature aggregation, it MUST validate that the signature is a valid signature of the
userOpHash
, and SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert.
Mitigation: Instead of reverting on signature verification failure, return
SIG_VALIDATION_FAILED
.
Missing Validation for postOpGasLimit Range
If validation for the postOpGasLimit range is missing in the _validatePaymasterUserOp
, a malicious user could set the gas fee for _postOp
to zero or an extremely low value. This would result in improper gas settlement during _postOp
, allowing the operation to complete without accurate gas usage accounting.
Mitigation: Add logic to verify the minimum value of
postOpGasLimit
in_validatePaymasterUserOp
.
function _validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 requiredPreFund
)
internal
view
override
returns (bytes memory context, uint256 validationData)
{
...
require(refundPostopCost < userOp.unpackPostOpGasLimit(), "PostOpGasLimit too low");
...
}
Missing Verification of Changes in refundPostopCost Value
If there’s no logic to confirm that the refundPostopCost
value in the _validatePaymasterUserOp
function matches the one in the _postOp
function, a malicious paymaster owner could exploit this by increasing the refundPostopCost
in a transaction, profiting from the user's funds.
Example Case:
After the refundPostopCost
value is validated in the _validatePaymasterUserOp
function, the Paymaster's owner can call the setTokenPaymasterConfig
function to increase the refundPostopCost
value beyond the user's intended amount, allowing them to deduct a larger amount of funds from the user during the _postOp
function.

Mitigation: Ensure that
refundPostopCost
is included in the context during the_validatePaymasterUserOp
function and verify in the_postOp
function that the value remains unchanged.
function _validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32, uint256 requiredPreFund)
internal
override
returns (bytes memory context, uint256 validationResult)
{
...
context = abi.encode(tokenAmount, userOp.sender, refundPostopCost);
...
}
function _postOp(PostOpMode, bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas) internal override
{
...
(
uint256 preCharge,
address userOpSender,
uint48 validRefundPostopCost
) = abi.decode(context, (uint256, address, uint48));
require(validRefundPostopCost == refundPostopCost, "refundPostopCost value has changed");
...
}
Issue with set Function Not Allowing Lower Values
Functions such as setPriceMarkup
, which adjust priceMarkup
, have an issue where new values cannot be set lower than the current ones. In some cases, this behavior may be intentional based on the business logic.
Example code:
function setUnaccountedGas(uint256 value) external payable override onlyOwner {
if (value < unaccountedGas) {
revert;
}
...
}
Mitigation: Rather than limiting the set function based on the previous value, apply limits using a constant variable.
function setUnaccountedGas(uint256 value) external payable override onlyOwner {
if (value < UNACCOUNTED_GAS_MIN) {
revert;
}
...
}
Last updated