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