Block: 90

Timestamp: 12:59:42

AuditProfile

Security blog

Same problem with nonce, but different implementation

A quite intersting bug was found by the awesome auditor - MiloTruck, in the Biconomy: Nexus contest.

There was a function that didn't include a nonce, thereby allowing enable mode signatures to be replayed. However, it was an unusual function impelementation, so the only adutor was able to find the bug.

Let's dive deep into the report and start with the code snippet:

    function validateUserOp(
        PackedUserOperation calldata op,
        bytes32 userOpHash,
        uint256 missingAccountFunds
    ) external virtual payPrefund(missingAccountFunds) onlyEntryPoint returns (uint256 validationData) {
        address validator = op.nonce.getValidator();
        if (!op.nonce.isModuleEnableMode()) {
            // Check if validator is not enabled. If not, return VALIDATION_FAILED.
            if (!_isValidatorInstalled(validator)) return VALIDATION_FAILED;
            validationData = IValidator(validator).validateUserOp(op, userOpHash);
        } else {
            PackedUserOperation memory userOp = op;
            userOp.signature = _enableMode(validator, op.signature);
            validationData = IValidator(validator).validateUserOp(userOp, userOpHash);
        }    
    }

When Nexus account owners send a transaction with enable mode in PackedUserOperation.nonce, calls _enableMode() to install the validator as a new module.

To ensure that the account owner has allowed the validator to be installed, the validator (ie. module shown below) is hashed alongside its data (ie. moduleInitData) in _getEnableModeDataHash(), and subsequently checked to be signed by the owner in enableModeSignature in _checkEnableModeSignature():

    function _enableMode(address module, bytes calldata packedData) internal returns (bytes calldata userOpSignature) {   
        uint256 moduleType;
        bytes calldata moduleInitData;
        bytes calldata enableModeSignature;
        
        (moduleType, moduleInitData, enableModeSignature, userOpSignature) = packedData.parseEnableModeData();  

        _checkEnableModeSignature(
            _getEnableModeDataHash(module, moduleInitData),
            enableModeSignature
        );
        _installModule(moduleType, module, moduleInitData);
    }

However, the hash returned by _getEnableModeDataHash() does not include a nonce:

function _getEnableModeDataHash(address module, bytes calldata initData) internal view returns (bytes32 digest) {
digest = _hashTypedData(
keccak256(
abi.encode(
MODULE_ENABLE_MODE_TYPE_HASH,
module,
keccak256(initData)
)
)
);
}

This allows the owner's signature to be used repeatedly.

As a result, if a validator that was previously installed through _enableMode() is uninstalled by the owner, a malicious relayer/bundler can re-use the previous signature to re-install it through validatorUserOp() again, despite not having the owner's permission.

The recommendation was simple - use nonce!

And you - read the full report here:

Link: https://codehawks.cyfrin.io/c/2024-07-biconomy/s/202

#nonce

#signature

#validate

Connent with me:

Регистрация прошла успешно! Спасибо за внимание!

loader