xanoutas/solidity-security-findings huggingface dataset:
Console account owners may not be able to complete transactions when minimum signatures required are not met.
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L56-L71
https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L82-L104


# Vulnerability details

## Impact
Console account owners may not be able to complete transactions when minimum signatures required are not met.

## Proof of Concept
In SafeDeployer contract, the deployConsoleAccount and deploySubAccount functions provides for the threshold parameter. 
`
function deployConsoleAccount(address[] calldata _owners, uint256 _threshold, bytes32 _policyCommit, bytes32 _salt)
        external
        nonReentrant
        returns (address _safe)
    {
        bool _policyHashValid = _policyCommit != bytes32(0);

        _safe = _createSafe(_owners, _setupConsoleAccount(_owners, _threshold, _policyHashValid), _salt);

        if (_policyHashValid) {
            PolicyRegistry(AddressProviderService._getRegistry(_POLICY_REGISTRY_HASH)).updatePolicy(
                _safe, _policyCommit
            );
        }
        emit ConsoleAccountDeployed(_safe);
    }

`
https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L56-L72

`
 function deploySubAccount(address[] calldata _owners, uint256 _threshold, bytes32 _policyCommit, bytes32 _salt)
        external
        nonReentrant
        returns (address _subAcc)
    {
        // Policy commit is required for sub account
        if (_policyCommit == bytes32(0)) revert InvalidCommitment();

        // Check if msg.sender is a registered wallet
        WalletRegistry _walletRegistry = WalletRegistry(AddressProviderService._getRegistry(_WALLET_REGISTRY_HASH));
        if (!_walletRegistry.isWallet(msg.sender)) revert NotWallet();

        // Deploy sub account
        _subAcc = _createSafe(_owners, _setupSubAccount(_owners, _threshold, msg.sender), _salt);

        // Register sub account to wallet
        _walletRegistry.registerSubAccount(msg.sender, _subAcc);

        // Update policy commit for sub account
        PolicyRegistry(AddressProviderService._getRegistry(_POLICY_REGISTRY_HASH)).updatePolicy(_subAcc, _policyCommit);
        emit SubAccountDeployed(_subAcc, msg.sender);
    }

`
https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L82-L104

The threshold is a uint defining how many minimum signatures are required from owners to complete a transaction.

If there are five owners, threshold can be set from 1 to 5. However, if there are three owners, the minimum required signature to complete a transaction is 3.

Now, in both deployConsoleAccount and deploySubAccount, there is no check to ensure threshold cannot be set below 3 when owners are three.

Also, threshold set to complete a transaction can be more than owners. There is no check to ensur
A safe is not registered in the WalletRegistry contract upon calling the deployConsoleAccount function in the SafeDeploy contract
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L56-L71
https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L82-L103


# Vulnerability details

## Impact
It's possible to deploy a safe (Console account) without registering it in the WalletRegistry. 

## Proof of Concept
`
    function deployConsoleAccount(address[] calldata _owners, uint256 _threshold, bytes32 _policyCommit, bytes32 _salt)
        external
        nonReentrant
        returns (address _safe)
    {
        bool _policyHashValid = _policyCommit != bytes32(0);

        _safe = _createSafe(_owners, _setupConsoleAccount(_owners, _threshold, _policyHashValid), _salt);

        if (_policyHashValid) {
            PolicyRegistry(AddressProviderService._getRegistry(_POLICY_REGISTRY_HASH)).updatePolicy(
                _safe, _policyCommit
            );
        }
        emit ConsoleAccountDeployed(_safe);
    }

`

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L56-L71

In the code above, there is nowhere the deployed safe is registered in the WalletRegistry. Whereas, the comment above the function says:

 * @notice Deploys a new console account with or without policy commit and registers it

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L48

Looking at the deploySubAccount, it deploys a sub-account and registers it in the WalletRegistry. Here:
`  
// Register sub account to wallet
        _walletRegistry.registerSubAccount(msg.sender, _subAcc);
`

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L97-L98

## Tools Used
Manual review

## Recommended Mitigation Steps
Register wallet in the WalletRegistry as seen in the deploySubAccount function.


## Assessed type

Other
It is possible to deploy address(0) as owners of a Console account and subAccount
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L90-L92
https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L82-L103


# Vulnerability details

## Impact
address(0) is allowed to be an owner of a console account and subAccount

## Proof of Concept
In both deployConsoleAccount function and deploySubAccount function in the SafeDeploy contract, there is no check against passing address(0) as owners or part of the owners.

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L82-L103

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L90-L92

## Tools Used
Manual review

## Recommended Mitigation Steps
I suggest a check against having address(0) as an owner.


## Assessed type

Other
A user can register a wallet, then deploy subAccount without actually deploying a _safe
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/registries/WalletRegistry.sol#L35-L40
https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L82-L103


# Vulnerability details

## Impact
subAccount can be deployed without deploying a _safe

## Proof of Concept
- User calls the registerWallet function in WalletRegistry.sol and registers a wallet.
- Then, calls the deploySubAccount function to deploy a subAccount.
- A subAccount will be created and deployed.

In the deploySubAccount function in SafeDeploy contract, it checks if the wallet intending to deploy a subAccount exists:
`
    // Check if msg.sender is a registered wallet
        WalletRegistry _walletRegistry = WalletRegistry(AddressProviderService._getRegistry(_WALLET_REGISTRY_HASH));
        if (!_walletRegistry.isWallet(msg.sender)) revert NotWallet();
`

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/SafeDeployer.sol#L90-L92

And because the user already called the registerWallet function to register a wallet, the check in the deploySubAccount function will pass.

## Tools Used
Manual review

## Recommended Mitigation Steps
registerWallet function should be restricted and called by a deployed safe. 


## Assessed type

Access Control
When updating the governance, use timelocks and keep current governance in power for some time
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Auditor

NeoCrao

## Description

The [AddressProvider](https://github.com/code-423n4/2023-10-brahma/blob/main/contracts/src/core/AddressProvider.sol) contract provides method to `setGovernance()`, which must then be accepted by the new governance by calling `acceptGovernance()`. This helps in avoiding transferring of governance to invalid addresses by mistake.

However, this does not eliminate all the mistakes. The project assumptions state that the governanance is trusted. And hence, the action of calling `setGovernance()` by the governance can be assumed that it was with good intentions. But, if the new governance turns out to be malicious, or if it was called with the wrong address, then the subsequent calls `setGovernance()` to clear the `pendingGovernance` can be frontrun by the malicioud `pendingGovernance`  or the address that was mistakenly set as `pendingGovernance`.

Here is the flow:
* The `governance` determines that `0x123` will be better suited for governance.
* The `governance` calls `setGovernance(0x123)`. This sets `pendingGovernance` as `0x123`.
* The `governance` realizes that `0x123` actually has malicious intents, and they want to reset `0x123` before `0x123` accepts the governance.
    * Alternatively, it could have been a mistake too. The governance might want to have set `0x234` as the `pendingGovernance`, but they just pick up the wrong address, or make a typo somehow.
* `0x123` realizes that the `governance` might reset `pendingGovernance` by calling `setGovernance(0x0)`.
* `0x123` immediately front runs the `setGovernance()` call with `acceptGovernance()`
* Now `0x123` is in control.

## Impact
Because there is only a single governance possible, `0x123` was able to **take control of the project** just because of a minor mistake made by the previous governance.

## Severity Justification
This is High severity, based on the Code4rena Severity Categorization: https://docs.code4rena.com/awarding/judging-criteria/severity-categorization

High: **Assets can be stolen/lost/compromised** directly (or **indirectly if there is a valid attack path** that does not have hand-wavy hypotheticals).

## Tools Used
Manual analysis

## Recommended Mitigation Steps

The following mitigations will help in reducing the changes of this problem:
* Use timelock when the new governance is proposed. The new governance can accept only after the timelock expires.
* Use timelock after the new governance accepts.
    * The new governance comes into power only after the timelock expires.
    * The previous governance is taken out of power after the timelock expires
        * Either after the timelock expires just the acceptance of the new governance
        * Or, a new timelock is initiated after the new governance comes into power


## Assessed type

Governance
Arbitrary transactions possible due to insufficient signature validation
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/ConsoleFallbackHandler.sol?plain=1#L39-L54


# Vulnerability details

## Impact
An attacker can create arbitrary transaction by evading signature validation.

## Proof of Concept
The protocol supports contract signed transactions `(EIP-1271)`. The support is implemented as we can see in the `isValidSignature`  call
The `isValidSignature` function does a thorough check in order to be certain a signature is valid but in this case there is a bug in the `checkSignatures` which is called by the `isValidSignature` function.

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/ConsoleFallbackHandler.sol#L52C1-L53C10
```diff
            safe.checkSignatures(messageHash, _data, _signature);
```
  The `checkSignatures` function inside the Safe contracts, does not check whether the signer is actually the contract owner in a acase where `v=0`. One might argue that the `checkSignatures` function is presently out of scope but this is still a valid bug as the `isValidSignature` in the  `ConsoleFallbackHandler` calls the `checkSignatures` function in order to verify if a signature is truly valid.

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/lib/safe-contracts/contracts/GnosisSafe.sol#L240C5-L240C31
```
  function checkSignatures(
        bytes32 dataHash,
        bytes memory data,
        bytes memory signatures
    ) public view {
        // Load threshold to avoid multiple storage loads
        uint256 _threshold = threshold;
        // Check that a threshold is set
        require(_threshold > 0, "GS001");
        checkNSignatures(dataHash, data, signatures, _threshold);
    }

    function checkNSignatures(
....
 if (v == 0) {
                // If v is 0 then it is a contract signature
                // When handling contract signatures the address of the contract is encoded into r
                currentOwner = address(uint160(uint256(r)));

```
 A hacker can craft a signature that bypasses the signature requirements and sets a hacker controlled signer that will always return `EIP1271_MAGIC_VALUE` from the `isValidSignature.` If exploited, the `isValidSignature` will end up allowing a forged signature from a fake signer. Any Contract that make calls to the `isValidSignature` function will be affected as well because `checkSignatures` does not validate that the signer is the caller or owner of the contract. 
This impacts the `execTransaction` function in the `GnosisSafe` contracts most especially.

## Tools Used
Manual Review
## Recommended Mitigation Steps
Properly validate that the `signer` is the `owner` in the `checkSignatures` function in order to keep the `isValidSignature` function in the `ConsoleFallbackHandler` safe.


## Assessed type

Other
In registerSubAccount function, doesn't check that sub account is not a wallet.
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/core/registries/WalletRegistry.sol#L49-L55


# Vulnerability details

## Impact
Breaking the rules, wallet address can be _subAccount. 

## Vulnerability details
In registerSubAccount function, there is no check for if the sub account address is not a wallet address.
## Tools Used
Manual Review
## Recommended Mitigation Steps
Check that _subAccount address is not wallet address.


## Assessed type

Other
Hash Collision in Transaction Data
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-brahma/blob/dd0b41031b199a0aa214e50758943712f9f574a0/contracts/src/libraries/TypeHashHelper.sol#L64-L78


# Vulnerability details

## Impact
in  The _buildTransactionStructHash function it uses the keccak256 hash function to create a hash of the data field of the Transaction struct  If the data field can be controlled by an attacker, it's can  potentially craft a transaction that, when hashed, produces the same hash as a legitimate transaction. This can allow the attacker to bypass the validation.

## Proof of Concept
Here's an example : 
```solidity
pragma solidity 0.8.19;

contract Test {
    function hash(uint256 data) public pure returns (uint256) {
        return data % 1000000;
    }
}

```
- the hash function takes a uint256 and returns the remainder when divided by 1,000,000. This is a very simple hash function and is not suitable for cryptographic purposes, but it serves in our poc test.

```soliidty
pragma solidity 0.8.19;

contract Test {
    function hash(uint256 data) public pure returns (uint256) {
        return data % 1000000;
    }

    function test() public pure returns (uint256, uint256, uint256) {
        uint256 a = 123456789;
        uint256 b = 123456789 + 1000000;
        return (a, hash(a), b, hash(b));
    }
}

```
- in the test function there is two different inputs a and b that produce the same hash when passed through the hash function.

## Tools Used
manual review
## Recommended Mitigation Steps
here is an example of fixing : 
```solidity
function _buildTransactionStructHash(Transaction memory txn) internal pure returns (bytes32) {
    require(txn.data.length <= MAX_DATA_LENGTH, "Data length exceeds maximum");
    return keccak256(
        abi.encode(
            TRANSACTION_PARAMS_TYPEHASH,
            txn.to,
            txn.value,
            keccak256(txn.data),
            txn.operation,
            txn.account,
            txn.executor,
            txn.nonce
        )
    );
}
```


## Assessed type

Other
Unintended bypass of policy signature validation in `TransactionValidator` due to specific conditions in the `_isConsoleBeingOverriden` mechanism
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Summary

The `_isConsoleBeingOverriden` function in `TransactionValidator` CA checks if the console is being overridden by certain conditions. If these conditions are met, the function will return early without validating the policy signature. This provides an avenue for malicious actors to craft a tx that satisfies these conditions, bypassing the `_validatePolicySignature` check.

## Vulnerability Detail

The `_isConsoleBeingOverriden` function checks if `_from` is the same as `_to`, `_value` is 0, and `_operation` is a Call. 

```solidity
function _isConsoleBeingOverriden(
 address _from,
 address _to,
 uint256 _value,
 bytes memory _data,
 Enum.Operation _operation
 ) internal pure returns (bool) {

 if (_from == _to && _value == 0 && _operation == Enum.Operation.Call) {
 if (SafeHelper._GUARD_REMOVAL_CALLDATA_HASH == keccak256(_data)) {
 return true;
 } else if (SafeHelper._FALLBACK_REMOVAL_CALLDATA_HASH == keccak256(_data)) {
 return true;
 }
 }

 return false;
 }
```

If these conditions are met and the `keccak256(_data)` matches either `_GUARD_REMOVAL_CALLDATA_HASH` or `_FALLBACK_REMOVAL_CALLDATA_HASH`, the function returns true. While these conditions might be specific to some internal operations, they could be unintentionally met, allowing unauthorized txns to proceed without signature validation.

## Impact

Attackers can craft txns that satisfy the conditions in `_isConsoleBeingOverriden` to bypass policy signature validation. This can lead to unauthorized txns being processed without proper validation, which in turn can result in financial losses and other adverse impacts on the Brahma system.

## Proof of Concept

1. Create a tx where `_from` is the same as `_to` and `_value` is 0.
2. Ensure the data causes the `keccak256(_data)` to match either `_GUARD_REMOVAL_CALLDATA_HASH` or `_FALLBACK_REMOVAL_CALLDATA_HASH`.
3. The tx would bypass the `_validatePolicySignature` check.

where `_data` in their malicious tx would reflect:

For Guard Removal:

- Calldata: `0xe19a9dd90000000000000000000000000000000000000000000000000000000000000000`

For Fallback Handler Removal:

- Calldata: `0xf08a0323000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000`

For instance, consider the following script:
```javascript
const { ethers } = require("hardhat");

async function main() {
 // Fetch account to deploy contracts
 const [attacker] = await ethers.getSigners();

 // Deploy AddressProvider CA
 const AddressProvider = await ethers.getContractFactory("AddressProvider");
 const addressProvider = await AddressProvider.deploy(attacker.address);
 await addressProvider.deployed();
 console.log("AddressProvider deployed to:", addressProvider.address);

 // Ensure that the AddressProvider address is valid
 if (!ethers.utils.isAddress(address
Executors can completely take over a SubAccount
Audit: 2023-10-brahma-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Executors can install a backdoor in a Subaccount. This has several consequences because, as a result, it opens the way to a huge array of attack vectors, and it will break several protocol invariants:

1. Subaccounts can remove/change guards without intervention from the Console account
2. Subaccounts can remove/change the fallback handler without intervention from the Console account
3. Subaccounts can ignore the policy validation after installing the backdoor

As a consequence, Executors will have complete control over a Subaccount.

## Proof of Concept
Executors can interact with the Subaccount's safe through the `ExecutorPlugin` by calling `executeTransaction`:

```solidity
function executeTransaction(ExecutionRequest calldata execRequest) external nonReentrant returns (bytes memory) {
    _validateExecutionRequest(execRequest);

    bytes memory txnResult = _executeTxnAsModule(execRequest.account, execRequest.exec);

    TransactionValidator(AddressProviderService._getAuthorizedAddress(_TRANSACTION_VALIDATOR_HASH))
        .validatePostExecutorTransaction(msg.sender, execRequest.account);

    return txnResult;
}
```

This has several checks, and it's normally not possible to remove either the guard or the fallback handler.

However, it is possible for the Executor to use this function **to enable a malicious module that does not have these restrictions.**

It's possible to do so by following these steps:

1. Executor calls `executeTransaction` in `ExecutorPlugin`, and they enable a new malicious module `M` by calling `IGnosisSafe.enableModule(address(M))`
2. Unlike `ExecutorPlugin`, `M` has a function `F` that will internally call the connected Safe through `execTransactionFromModuleReturnData` or `execTransactionFromModule`, without any validation
3. The last two functions **will not trigger** `checkTransaction/checkAfterExecution` from the guard (`SafeModerator`), so these checks will not be used
4. Executor can successfully call `F` from `M` with basically any function, including `setGuard(0)` and `setFallbackHandler(0)`

### Coded POC
Add the following module to `src/core/MaliciousModule.sol`

```solidity
/// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.19;

import {EIP712} from "solady/utils/EIP712.sol";
import {IGnosisSafe} from "interfaces/external/IGnosisSafe.sol";
import {SafeHelper} from "src/libraries/SafeHelper.sol";
import {Types} from "interfaces/Types.sol";
import {ExecutorPlugin} from "src/core/ExecutorPlugin.sol";


contract MaliciousModule is EIP712 {
    error ModuleExecutionFailed();

    string private constant _NAME = "MaliciousModule";
    string private constant _VERSION = "1.0";

    function executeTransaction(ExecutorPlugin.ExecutionRequest calldata execRequest) external returns (bytes memory) {
        bytes memory txnResult = _executeTxnAsModule(exec