Brahma - rvierdiiev's results

Brahma Console is a custody and DeFi execution environment.

General Information

Platform: Code4rena

Start Date: 13/10/2023

Pot Size: $31,250 USDC

Total HM: 4

Participants: 51

Period: 7 days

Judge: 0xsomeone

Id: 295

League: ETH

Brahma

Findings Distribution

Researcher Performance

Rank: 30/51

Findings: 1

Award: $23.96

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: immeas

Also found by: Alra, Arz, GREY-HAWK-REACH, alexzoid, radev_sw, rvierdiiev, sorrynotsorry

Labels

bug
downgraded by judge
grade-b
primary issue
QA (Quality Assurance)
satisfactory
sponsor disputed
sufficient quality report
Q-07

Awards

23.9577 USDC - $23.96

External Links

Lines of code

https://github.com/code-423n4/2023-10-brahma/blob/main/contracts/src/core/ConsoleFallbackHandler.sol#L44

Vulnerability details

Proof of Concept

Each subaccount should have policy that was provided for them by main console. So every time, when subaccount owners want to execute any transaction, then SafeModerator will check tx. It will validate if tx doesn't violate it's policy. So PolicyValidator.isPolicySignatureValid function will be called to do that check.

https://github.com/code-423n4/2023-10-brahma/blob/main/contracts/src/core/PolicyValidator.sol#L100-L142

    function isPolicySignatureValid(address account, bytes32 transactionStructHash, bytes calldata signatures)
        public
        view
        returns (bool)
    {
        // Get policy hash from registry
        bytes32 policyHash =
            PolicyRegistry(AddressProviderService._getRegistry(_POLICY_REGISTRY_HASH)).commitments(account);
        if (policyHash == bytes32(0)) {
            revert NoPolicyCommit();
        }


        // Get expiry epoch and validator signature from signatures
        (uint32 expiryEpoch, bytes memory validatorSignature) = _decompileSignatures(signatures);


        // Ensure transaction has not expired
        if (expiryEpoch < uint32(block.timestamp)) {
            revert TxnExpired(expiryEpoch);
        }


        // Build validation struct hash
        bytes32 validationStructHash = TypeHashHelper._buildValidationStructHash(
            TypeHashHelper.Validation({
                transactionStructHash: transactionStructHash,
                policyHash: policyHash,
                expiryEpoch: expiryEpoch
            })
        );


        // Build EIP712 digest with validation struct hash
        bytes32 txnValidityDigest = _hashTypedData(validationStructHash);


        address trustedValidator = AddressProviderService._getAuthorizedAddress(_TRUSTED_VALIDATOR_HASH);


        // Empty Signature check for EOA signer
        if (trustedValidator.code.length == 0 && validatorSignature.length == 0) {
            // TrustedValidator is an EOA and no trustedValidator signature is provided
            revert InvalidSignature();
        }


        // Validate signature
        return SignatureCheckerLib.isValidSignatureNow(trustedValidator, txnValidityDigest, validatorSignature);
    }

What this function does is actually the check that offchain trusted validator indeed has allowed(signed) subaccount to execute such transaction. Also it checks that this approve is not old: expiryEpoch < uint32(block.timestamp), which means that validator will sign messages for some period of time. When signature is old, then transaction can't be executed.

For each subaccount ConsoleFallbackHandler is set as fallback at the deployment. It has isValidSignature function in order to implement EIP-1271.

Let's check how it works. The function calls another version of isValidSignature function. https://github.com/code-423n4/2023-10-brahma/blob/main/contracts/src/core/ConsoleFallbackHandler.sol#L39-L55

    function isValidSignature(bytes memory _data, bytes memory _signature) public view override returns (bytes4) {
        // Caller should be a Safe
        GnosisSafe safe = GnosisSafe(payable(msg.sender));
        bytes32 messageHash = getMessageHashForSafe(safe, _data);
        if (_signature.length == 0) {
            require(safe.signedMessages(messageHash) != 0, "Hash not approved");
        } else {
            /// @dev For validating signatures, `PolicyValidator` is invoked to ensure that
            /// the intent of `messageHash` signed by the owners of the safe, complies with its committed
            /// policy
            PolicyValidator policyValidator =
                PolicyValidator(AddressProviderService._getAuthorizedAddress(_POLICY_VALIDATOR_HASH));
            require(policyValidator.isPolicySignatureValid(msg.sender, messageHash, _signature), "Policy not approved");
            safe.checkSignatures(messageHash, _data, _signature);
        }
        return EIP1271_MAGIC_VALUE;
    }

So the purpose of this function is to check if signature is valid. How it can be used? For example subaccount is an owner in another Safe wallet, so on signature validation of tx from that wallet, isValidSignature function of subaccount will be called. In this case, subaccount also should only be able to provide signatures for txs that doesn't violate their's policy. That's why ConsoleFallbackHandler.isValidSignature has policy validation again. So ConsoleFallbackHandler expects, that when signatures will be checked, then it should pass policy validation at the moment of check.

But there is another way to make signature valid. This way is to use SignMessageLib contract and store your message into the signedMessages. If such message is stored then there will be no check in the isValidSignature function.

Such ability allows owners of subaccount to bypass validation sometimes. For example, as i have described before each validator signature has expiring timestamp, which means that signature should be valid only some time. But in case if owner will store this message to signedMessages then on the time of execution will be no policy validation and expiration will not be checked.

Another thing that can happen is that policy will be changed for the subaccount. And after that all messages stored in the signedMessages will be still valid.

I don't know exactly how validator will work, but it's possible for owners of subaccount to store some messages into signedMessages that they would like to be used by other Safe wallets just before their policy will be chnaged. As result all those signatures will work against their policy.

Impact

Subaccounts may provide signatures that violate their policy.

Tools Used

VsCode

If think about expiration time check, then i would remove signedMessages from isValidSignature validation. So it's possible only to use fresh signatures. If think about changed policy for subaccount only, then i would remove all signedMessages from subaccount, when policy is changed. So this move can be executed as multi send from console safe: first remove all signedMessages and then change policy for subaccount.

Assessed type

Error

#0 - c4-pre-sort

2023-10-21T04:06:00Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-10-21T04:06:04Z

raymondfam marked the issue as primary issue

#2 - 0xad1onchain

2023-10-25T14:33:28Z

This is known and already accounted for. To store a signed message, console owners or in case of subaccount, the operators need to execute a transaction from the safe to save a signature in presigned messages, and that transaction like any other transaction would go through policy validator

#3 - c4-sponsor

2023-10-25T14:33:30Z

0xad1onchain (sponsor) disputed

#4 - alex-ppg

2023-10-27T22:05:36Z

As the Sponsor has specified, the policy validation check has been removed from the stored message flow as a matter of optimization as the stored hash would have already been validated by a policy one way or the other.

The Warden does articulate that there is no expiry associated with stored messages that have been signed which is correct, however, this is a QA report (Low) as it would still require the policy signer to allow the behavior in the first place. The altered policy avenue is also interesting and something that the Sponsor may wish to handle.

I consider this exhibit thus sufficient but as a QA rather than a Medium.

#5 - c4-judge

2023-10-27T22:05:42Z

alex-ppg marked the issue as satisfactory

#6 - c4-judge

2023-10-27T22:05:55Z

alex-ppg changed the severity to QA (Quality Assurance)

#7 - c4-judge

2023-10-31T21:31:13Z

alex-ppg marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter