GithubHelp home page GithubHelp logo

2023-03-aragon-findings's Introduction

Aragon Protocol Contest

Unless otherwise discussed, this repo will be made public after contest completion, sponsor review, judging, and two-week issue mitigation window.

Contributors to this repo: prior to report publication, please review the Agreements & Disclosures issue.


Contest findings are submitted to this repo

Sponsors have three critical tasks in the contest process:

  1. Weigh in on severity.
  2. Respond to issues.
  3. Share your mitigation of findings.

Let's walk through each of these.

High and Medium Risk Issues

Please note: because wardens submit issues without seeing each other's submissions, there will always be findings that are duplicates. For all issues labeled 3 (High Risk) or 2 (Medium Risk), these have been pre-sorted for you so that there is only one primary issue open per unique finding. All duplicates have been labeled duplicate, linked to a primary issue, and closed.

Weigh in on severity

Judges have the ultimate discretion in determining severity of issues, as well as whether/how issues are considered duplicates. However, sponsor input is a significant criteria.

For a detailed breakdown of severity criteria and how to estimate risk, please refer to the judging criteria in our documentation.

If you disagree with a finding's severity, leave the severity label intact and add the label disagree with severity, along with a comment indicating your opinion for the judges to review. You may also add questions for the judge in the comments.

Respond to issues

Label each open/primary High or Medium risk finding as one of these:

  • sponsor confirmed, meaning: "Yes, this is a problem and we intend to fix it."
  • sponsor disputed, meaning either: "We cannot duplicate this issue" or "We disagree that this is an issue at all."
  • sponsor acknowledged, meaning: "Yes, technically the issue is correct, but we are not going to resolve it for xyz reasons."

(Note: please don't use sponsor disputed for a finding if you think it should be considered of lower or higher severity. Instead, use the label disagree with severity and add comments to recommend a different severity level -- and include your reasoning.)

Add any necessary comments explaining your rationale for your evaluation of the issue.

Note that when the repo is public, after all issues are mitigated, wardens will read these comments; they may also be included in your C4 audit report.

QA and Gas Reports

For low and non-critical findings (AKA QA), as well as gas optimizations: wardens are required to submit these as bulk listings of issues and recommendations. They may only submit a single, compiled report in each category:

  • QA reports should include all low severity and non-critical findings, along with a summary statement.
  • Gas reports should include all gas optimization recommendations, along with a summary statement.

For QA and Gas reports, sponsors are not required to weigh in on severity or risk level. We ask that sponsors:

  • Leave a comment for the judge on any reports you consider to be particularly high quality. (These reports will be awarded on a curve.)
  • Add the sponsor disputed label to any reports that you think should be completely disregarded by the judge, i.e. the report contains no valid findings at all.

Once labelling is complete

When you have finished labelling findings, drop the C4 team a note in your private Discord backroom channel and let us know you've completed the sponsor review process. At this point, we will pass the repo over to the judge to review your feedback while you work on mitigations.

Share your mitigation of findings

Note: this section does not need to be completed in order to finalize judging. You can continue work on mitigations while the judge finalizes their decisions and even beyond that. Ultimately we won't publish the final audit report until you give us the OK.

For each finding you have confirmed, you will want to mitigate the issue before the contest report is made public.

As you undertake that process, we request that you take the following steps:

  1. Within a repo in your own GitHub organization, create a pull request for each finding.
  2. Link the PR to the issue that it resolves within your contest findings repo.

This will allow for complete transparency in showing the work of mitigating the issues found in the contest. If the issue in question has duplicates, please link to your PR from the open/primary issue.

2023-03-aragon-findings's People

Contributors

code423n4 avatar kartoonjoy avatar

Watchers

Ashok avatar

2023-03-aragon-findings's Issues

QA Report

See the markdown file with the details of this report here.

QA Report

See the markdown file with the details of this report here.

No Contract Compiled Yet, missing "contract RegistryUtils{...}" in in file packages/contracts/src/framework/utils/RegistryUtils.sol

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/utils/RegistryUtils.sol#L1-L38
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/dao/DAORegistry.sol#L8
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/plugin/repo/PluginRepoRegistry.sol#L8

Vulnerability details

Impact

packages/contracts/src/framework/utils/RegistryUtils.sol file is missing "contract{}", here there is one function function isSubdomainValid() and it will not compile.
isSubdomainValid() is called in several places,
1.packages/contracts/src/framework/dao/DAORegistry.sol
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/dao/DAORegistry.sol#L8
2.packages/contracts/src/framework/plugin/repo/PluginRepoRegistry.sol
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/plugin/repo/PluginRepoRegistry.sol#L8
This will cause a crash which is dangerous.

Proof of Concept

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/utils/RegistryUtils.sol#L1-L38
https://docs.soliditylang.org/en/v0.8.19/introduction-to-smart-contracts.html

Tools Used

Manual view

Recommended Mitigation Steps

Add "contract RegistryUtils{...}", to compile the file.

`// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity 0.8.17;

contract RegistryUtils{
/// @notice Validates that a subdomain name is composed only from characters in the allowed character set:
/// - the lowercase letters a-z
/// - the digits 0-9
/// - the hyphen -
/// @dev This function allows empty (zero-length) subdomains. If this should not be allowed, make sure to add a respective check when using this function in your code.
/// @param subDomain The name of the DAO.
/// @return true if the name is valid or false if at least one char is invalid.
/// @dev Aborts on the first invalid char found.
function isSubdomainValid(string calldata subDomain) pure returns (bool) {
bytes calldata nameBytes = bytes(subDomain);
uint256 nameLength = nameBytes.length;
for (uint256 i; i < nameLength; i++) {
uint8 char = uint8(nameBytes[i]);

    // if char is between a-z
    if (char > 96 && char < 123) {
        continue;
    }

    // if char is between 0-9
    if (char > 47 && char < 58) {
        continue;
    }

    // if char is -
    if (char == 45) {
        continue;
    }

    // invalid if one char doesn't work with the rules above
    return false;
}
return true;

}
}`

Corruptible Upgradability Pattern

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/framework/plugin/repo/PluginRepo.sol#L19-L25

Vulnerability details

Impact

Storage of PluginRepo might be corrupted during upgrading, thus causing wrong version management of the plugin versions within the Aragon DAO framework.

Proof of Concept

As PoC the following inheritance diagram of the PluginRepo contract is given. Note: The contracts highlighted in Orange mean that there are no gap slots defined.

PluginRepo's Inheritance Diagram

graph BT;
        classDef nogap fill:#f96;
    PluginRepo::nogap-->Initializable
    PluginRepo::nogap-->ERC165Upgradeable
    PluginRepo::nogap-->IPluginRepo
    PluginRepo::nogap-->UUPSUpgradeable
    PluginRepo::nogap-->PermissionManager
Loading

No gap storage is implemented on the PluginRepo. Thus, adding new storage variables can potentially overwrite the beginning of the storage layout of the child contract. causing critical misbehaviors in the system.

Tools Used

Manual Review

Recommended Mitigation Steps

Consider defining an appropriate storage gap in each upgradeable parent contract at the end of all the storage variable definitions as follows:

uint256[50] __gap; // gap to reserve storage in the contract for future variable additions

No Storage Gaps For Upgradeable Contracts

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/framework/plugin/repo/PluginRepo.sol#L19-L24

Vulnerability details

Impact

Upgradeable contracts must have storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts.
Refer to the bottom part of this article: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable

Proof of Concept

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/framework/plugin/repo/PluginRepo.sol#L19-L24
The PluginRepo contract is an upgradeable contract but does not contain any storage gap. If in a future upgrade, an additional variable is added to the PluginRepo contract, that new variable will overwrite the storage slot of the child contract inheriting it, causing unintended consequences.

Tools Used

Manual Review

Recommended Mitigation Steps

Add appropriate storage gap at the end of upgradeable contracts such as the below. Please reference OpenZeppelin upgradeable contract templates.

uint256[50] private __gap;

10**6 is too small for RATIO_BASE

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/plugins/utils/Ratio.sol#L6

Vulnerability details

Impact

RATIO_BASE is set as 10**6, and limit of participations is also around 10**6.
RATIO_BASE should be bigger than square of the limit of participations.

Proof of Concept

Currently, RATIO_BASE is set as 10**6.
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/plugins/utils/Ratio.sol#L6

In the contract MajorityVotingBase, we can easily guess that the limit of participations is around 10**6 (= RATIO_BASE).
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/plugins/governance/majority-voting/MajorityVotingBase.sol#L536

For example, let's say that the creator of Voting DAO wants to separate 1/2001 and 1/2002 in the voting ratio.
In other words, "1 yes, 2000 no" is executable and "1 yes, 2001 no" is non-excutable.
So voting ratio should be bigger than 1/2002 and smaller than 1/2001.
Since both 1/2001 and 1/2002 are inside the range (499/1000000, 500/1000000), there is no proper ratio.

Tools Used

(Nothing, just looking the code on VSCode)

Recommended Mitigation Steps

We can set RATIO_BASE as 10**12. Then, the creator can separate every two different fractions with denominators less than 10**6.

QA Report

See the markdown file with the details of this report here.

QA Report

See the markdown file with the details of this report here.

QA Report

See the markdown file with the details of this report here.

QA Report

See the markdown file with the details of this report here.

Agreements & Disclosures

Agreements

If you are a C4 Certified Contributor by commenting or interacting with this repo prior to public release of the contest report, you agree that you have read the Certified Warden docs and agree to be bound by:

To signal your agreement to these terms, add a 👍 emoji to this issue.

Code4rena staff reserves the right to disqualify anyone from this role and similar future opportunities who is unable to participate within the above guidelines.

Disclosures

Sponsors may elect to add team members and contractors to assist in sponsor review and triage. All sponsor representatives added to the repo should comment on this issue to identify themselves.

To ensure contest integrity, the following potential conflicts of interest should also be disclosed with a comment in this issue:

  1. any sponsor staff or sponsor contractors who are also participating as wardens
  2. any wardens hired to assist with sponsor review (and thus presenting sponsor viewpoint on findings)
  3. any wardens who have a relationship with a judge that would typically fall in the category of potential conflict of interest (family, employer, business partner, etc)
  4. any other case where someone might reasonably infer a possible conflict of interest.

No Storage Gap for Upgradeable Contracts

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/core/dao/DAO.sol#L26-L90
https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/core/permission/PermissionManager.sol#L13

Vulnerability details

Impact

For upgradeable contracts, there must be storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts.

Refer to the bottom part of this openZeppelin documentation: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable

Proof of Concept

The 'DAO.sol' inherits the 'PermissionManager', and the 'PermissionManager' does not contain any storage gap. If in a future upgrade a variable is added to the 'PermissionManager' contract the new variable will overwrite the 'EXECUTE_PERMISSION_ID' variable in 'PermissionManager' contract.

Tools Used

Manual Review

Recommended Mitigation Steps

Recommend adding appropriate storage gap at the end of upgradeable contracts such as the below. Please reference OpenZeppelin upgradeable contract templates.

uint256[50] private __gap;

Possible DOS (out-of-gas) on loops

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/token/ERC20/governance/GovernanceERC20.sol#L81

Vulnerability details

Impact

It is possible to get an out-of-gas issue while iterating the for loop in the GovernanceERC20.sol.initialize function.

Please take a look at this link.
https://github.com/wissalHaji/solidity-coding-advices/blob/master/best-practices/be-careful-with-loops.md

Proof of Concept

Let's say deployer need to run for loop in the GovernanceERC20.sol.initialize function and perform the mint operation for an array with a very long length.

Tools Used

Manually

Recommended Mitigation Steps

use bounded loop.

Denial of Service if PermissionCondition.isGranted() Changes State

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/core/permission/PermissionManager.sol#L311-L322

Vulnerability details

Impact

Under the specific circumstances described below, it is possible for a DAO to become locked such that no new permissions can be granted or revoked. If this happens at a time when the DAO is not properly functional, perhaps when new permissions are needed to release tokens, or a plugin needs to be upgraded for compatibility with a token, then this could result in the loss of funds trapped within the DAO.

Description

The function which checks permissions, PermissionManager._isGranted, is a view function. This means that it is enforced, at the level of the EVM, that this function cannot change state.

If a PermissionCondition contract is used for a permission, then isGranted() makes an external call to the defined PermissionCondition contract. If that call reverts for any reason, then permission is denied (_isGranted returns false). see code

However, the PermissionManager contract has no way of ensuring that any other contract's isGranted() function is, in fact, a view function. All it can do is call it from within its own view function, thus ensuring that no changes to state can be made during a permission check. If the PermissionCondition.isGranted() function does attempt to modify state, then the call will revert, and permission will be denied.

The situation in which this could potentially lead to a denial of service is one in which a DAO writes its own PermissionCondition contract which only writes to state under certain circumstances. Perhaps they want to emit an event if certain permissions are checked after a particular date, or increment a counter if some other external condition is met. Although it is true that the interface IPermissionCondition.sol does specify that the function should be view, it is realistic to envisage that a DAO might not realise the significance of this and nevertheless set up an isGranted() function which might write to state and thus unintentionally cause calls from PermissionManager._isGranted to revert. Note that these conditions are not just writing to storage. _isGranted() will also revert if the called function does one of these seemingly benign operations:

  • Emitting events.
  • Calling any function not marked view or pure.
  • Using low-level calls.

In a situation where a DAO has set up a PermissionManager contract of this kind on the ROOT_PERMISSION_ID, then that DAO could potentially become locked out of changing any permissions, wit the impact described above.

Proof of Concept

After applying the below patch, run npx hardhat test test/core/permission/permission-manager.ts
The modified test is it('should call IPermissionCondition.isGranted'. This test demonstrates how a non-view isGranted() can indeed be installed and that the permission is then denied whenever isGranted() writes to state, even if it is trying to return true.
(Note that there is also a newly failing test. This test simply fails as the modified PermissionConditionMock contract now fails by default (because it reverts by writing to state in a view context).)

diff --git a/packages/contracts/src/test/permission/PermissionConditionMock.sol b/packages/contracts/src/test/permission/PermissionConditionMock.sol
index 0656383..bd90f3b 100644
--- a/packages/contracts/src/test/permission/PermissionConditionMock.sol
+++ b/packages/contracts/src/test/permission/PermissionConditionMock.sol
@@ -2,21 +2,44 @@
 
 pragma solidity 0.8.17;
 
-import "../../core/permission/IPermissionCondition.sol";
+interface IPermissionCondition {
+    /// @notice This method is used to check if a call is permitted.
+    /// @param _where The address of the target contract.
+    /// @param _who The address (EOA or contract) for which the permission are checked.
+    /// @param _permissionId The permission identifier.
+    /// @param _data Optional data passed to the `PermissionCondition` implementation.
+    /// @return allowed Returns true if the call is permitted.
+    function isGranted(
+        address _where,
+        address _who,
+        bytes32 _permissionId,
+        bytes calldata _data
+    ) external returns (bool allowed);
+}
+
 
 contract PermissionConditionMock is IPermissionCondition {
     bool internal _hasPermissionsResult = true;
+    bool internal _writeToStorage = true;
+    uint internal _storageVar = 0;
 
     function isGranted(
         address /* _where */,
         address /* _who */,
         bytes32 /* _permissionId */,
         bytes memory /* _data */
-    ) external view returns (bool) {
+    ) external  returns (bool) {
+        if (_writeToStorage) {
+            ++_storageVar; // ++ first to save gas
+        }
         return _hasPermissionsResult;
     }
 
     function setWillPerform(bool _result) external {
         _hasPermissionsResult = _result;
     }
+
+    function setWrite(bool _result) external {
+        _writeToStorage = _result;
+    }
 }
diff --git a/packages/contracts/test/core/permission/permission-manager.ts b/packages/contracts/test/core/permission/permission-manager.ts
index 6e6fd1b..021022e 100644
--- a/packages/contracts/test/core/permission/permission-manager.ts
+++ b/packages/contracts/test/core/permission/permission-manager.ts
@@ -755,6 +755,7 @@ describe('Core: PermissionManager', function () {
         ADMIN_PERMISSION_ID,
         permissionCondition.address
       );
+
       expect(
         await pm.callStatic.isGranted(
           pm.address,
@@ -762,9 +763,9 @@ describe('Core: PermissionManager', function () {
           ADMIN_PERMISSION_ID,
           []
         )
-      ).to.be.equal(true);
+      ).to.be.equal(false);
 
-      await permissionCondition.setWillPerform(false);
+      await permissionCondition.setWrite(false);
       expect(
         await pm.callStatic.isGranted(
           pm.address,
@@ -772,7 +773,7 @@ describe('Core: PermissionManager', function () {
           ADMIN_PERMISSION_ID,
           []
         )
-      ).to.be.equal(false);
+      ).to.be.equal(true);
     });
   });

Tools Used

Manual inspection

Recommended Mitigation Steps

One possibility is to allow state changes in PermissionCondition.isGranted(). That would avoid this issue but could potentially create greater security concerns.
Therefore, my recommendation is to disallow PermissionCondition.isGranted() contracts for the permission ROOT_PERMISSION_ID.

QA Report

See the markdown file with the details of this report here.

QA Report

See the markdown file with the details of this report here.

QA Report

See the markdown file with the details of this report here.

DAOFactory.createDao function is front-runnable and can cause DoS

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/dao/DAOFactory.sol#L76
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/dao/DAOFactory.sol#L35
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/dao/DAORegistry.sol#L60
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/framework/utils/ens/ENSSubdomainRegistrar.sol#L87

Vulnerability details

Impact

DAOFactory.createDao function is a public function and anyone who wants to create dao needs to call this function with input data. one of the inputs is _daoSettings.subdomain. in ENSSubdomainRegistrar.sol#L87 we check the _daoSettings.subdomain, whether it is already registered or not? if already Registered, the call will get reverted AlreadyRegistered(subnode, currentOwner. if not, we will register the _daoSettings.subdomain for the new DAO.

as you know malicious users can listen to the mempool and create new transactions with higher gas feefeesransactions with higher gas feefeesll get mined faster.

Proof of Concept

USERA will call DAOFactory.createDao with _daoSettings.subdomain = "code4rena", the transaction will get created and sent to the mempool. now malicious user see the transaction of USERA in mempool and create new transaction with _daoSettings.subdomain = "code4rena" and higher gas fee and send it to the blockchain. transaction from malicious user will get mine faster because of higher gas fee and now _daoSettings.subdomain = "code4rena" is already Registered. and transfaction from USERA will get revert AlreadyRegistered(subnode, currentOwner).

Tools Used

Manually

Recommended Mitigation Steps

make subdomain more unique in aragon protocol.

QA Report

See the markdown file with the details of this report here.

TEST4

Lines of code

#L1

Vulnerability details

THIS IS A TEST

QA Report

See the markdown file with the details of this report here.

TEST3

Lines of code

#L1

Vulnerability details

THIS IS A TEST

Low level call returns true if the address doesn’t exist

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/core/dao/DAO.sol#L186

Vulnerability details

Impact

Based on the solidity documents, Low-level calls call/delegatecall/staticcall return true even if the account called is non-existent (per EVM design). Account existence must be checked prior to calling if needed.

https://docs.soliditylang.org/en/develop/control-structures.html#error-handling-assert-require-revert-and-exceptions

Proof of Concept

In DAO.sol#L186, we use a Low-level call to make calls to the target address. target address can be EOA or smart contract address.

(bool success, bytes memory response) = to.call{value: _actions[i].value}(
_actions[i].data
);

Per the Solidity docs:
"The low-level functions call, delegatecall, and staticcall return true as their first return value, if the account called, is non-existent, as part of the design of the EVM. Account existence must be checked prior to calling if needed."

https://docs.soliditylang.org/en/develop/control-structures.html#error-handling-assert-require-revert-and-exceptions

So transfers may fail for a target but you consider it done.

Tools Used

manually

Recommended Mitigation Steps

Check for the account's existence prior to transferring.

applySingleTargetPermissions function can consume all gas

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/core/permission/PermissionManager.sol#L150

Vulnerability details

Impact

An extensive array of items can be passed in the function applySingleTargetPermissions, and it can consume all gas, and the transaction can be reverted. As this is an external function, the hacker can call it directly from the DAO contract

Proof of Concept

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/core/permission/PermissionManager.sol#L150

Tools Used

Recommended Mitigation Steps

Provide inspection of how many permission items can be passed in this function

QA Report

See the markdown file with the details of this report here.

Transfering non-zero value will revert because execute function is not payable

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/core/dao/DAO.sol#L168-L215

Vulnerability details

Impact

In DAO.sol contract, in the execute function the payable keyword is missing.

All transfers call with value greater than 0 will revert due to this missing.

Proof of Concept

DAO.sol#L168-L215

DAO.sol#L186-L188

for (uint256 i = 0; i < _actions.length; ) {
    address to = _actions[i].to;
    (bool success, bytes memory response) = to.call{value: _actions[i].value}(
        _actions[i].data
    );

Tools Used

Code review

Recommended Mitigation Steps

diff --git a/packages/contracts/src/core/dao/DAO.sol b/packages/contracts/src/core/dao/DAO.sol
index d7c912d..44001bd 100644
--- a/packages/contracts/src/core/dao/DAO.sol
+++ b/packages/contracts/src/core/dao/DAO.sol
@@ -171,6 +171,7 @@ contract DAO is
         uint256 _allowFailureMap
     )
         external
+        payable
         override
         auth(EXECUTE_PERMISSION_ID)
         returns (bytes[] memory execResults, uint256 failureMap)

TEST

Lines of code

#L1

Vulnerability details

THIS IS A TEST

Missing important input validation

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/plugins/governance/multisig/Multisig.sol#L132

Vulnerability details

Impact

initialize() method in Multisig.sol is missing _members array length check. This will lead to problems if the initial array length is greater than type(uint16).max

Proof of Concept

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/plugins/governance/multisig/Multisig.sol#L132

If the initial length of _addresslistCheckpoints(which is set in initialize()) is greater than type(uint16).max then addAddresses() will be blocked and the contract won't be able to add new members. There is also unsafe downcasting at lines 175 and 414 which will result in truncated value from uint256 to uint16 (if addresslistLength() > type(uint16).max).

These lines of code are also affected as proposal_.approvals can overflow.

// As the list can never become more than type(uint16).max(due to addAddresses check)
// It's safe to use unchecked as it would never overflow.
unchecked {
    proposal_.approvals += 1;
}

Tools Used

Manual review

Recommended Mitigation Steps

Add these lines of code in initialize() method

if (_members.length > type(uint16).max) {
    revert AddresslistLengthOutOfBounds({
        limit: type(uint16).max,
        actual: _members.length
    });
}

The possibility of cause a DOS in the DAO.sol.execute function

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/core/dao/DAO.sol#L168
https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/core/dao/DAO.sol#L186

Vulnerability details

Impact

Here are two scenarios that can cause DOS to occur in the DAO.sol.execute function.

One:
Because of unexpected functions that maybe get calls on the “to” smart contract.

Two:
to.call{value: _actions[i].value} won't be able to be done if one of the multiple “ to ” smart contracts addresses decides to revert the transaction.

These behaviors are unpredictable.

Proof of Concept

struct Action {
address to;
uint256 value;
bytes data;
}

Every action has three variables. address to, uint256 value, bytes data. to is an untrusted external contract or EOA, value The native token value to be sent with the call, data is the bytes-encoded function selector and calldata for the call.

Here are two scenarios that can cause DOS to occur in the DAO.sol.execute function.

One:
In the DAO.sol.execute function, we get Action[] calldata _actions as input. This includes an array of actions. here the value is determined and decided per address but the cost of gas to call a bytes-encoded function selector from an untrusted external contract is unpredictable and is not determined before creating proposal and Action[] calldata _actions.

Let's assume that you charged a smart contract with 1.1 ETH, and your Action[] calldata _action included two actions. Action one with a value of 0.5 eth and action two with a value another 0.5 ETH. If under any conditions the function that gets called in bytes data consumes a lot of gas and more than 1.1 ETH, that can cause DOS to occur in the DAO.sol.execute function. The gas consumption for actions cannot be predicted and can increase or decrease under market conditions or make changes to the logic function that is going to be called.

Two:
to.call{value: _actions[i].value} won't be able to be done if one of the multiple beneficiaries decides to revert the transaction on receival.

Action[] calldata _actions array can have up to 256 actions. That means maybe up to 10 smart contract addresses as the target or to address. If one of these targets Unpredictably decides to don’t accept ETH, that can cause DOS to occur in the DAO.sol.execute function.

You can avoid this problem only if you completely and without any mistakes guess all the unpredictable behaviors that may happen in the targets and complete the Allowing for Failure based on that.

Tools Used

Manually

Recommended Mitigation Steps

you can use try catch pattern

QA Report

See the markdown file with the details of this report here.

No Storage Gap for Upgradeable Contracts

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/core/dao/DAO.sol#L26-L90
https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/core/permission/PermissionManager.sol#L13

Vulnerability details

Impact

For upgradeable contracts, there must be storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts.

Refer to the bottom part of this openZeppelin documentation: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable

Proof of Concept

The 'DAO.sol' inherits the 'PermissionManager', and the 'PermissionManager' does not contain any storage gap. If in a future upgrade a variable is added to the 'PermissionManager' contract the new variable will overwrite the 'EXECUTE_PERMISSION_ID' variable in 'PermissionManager' contract.

Tools Used

Manual Review

Recommended Mitigation Steps

Recommend adding appropriate storage gap at the end of upgradeable contracts such as the below. Please reference OpenZeppelin upgradeable contract templates.

uint256[50] private __gap;

implementation of the isValidSignature() function in the DAO.sol is invalid

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/core/dao/DAO.sol#L257

Vulnerability details

Impact

Failure to validate the returned response from the isValidSignature function can cause that invalid signatures to be assumed to be valid.

Proof of Concept

Currently, externally owned accounts (EOAs) can sign messages with their associated private keys, but contracts cannot. An exemplary use case is a decentralized exchange with an off-chain order book, where buy/sell orders are signed messages. To accept such a request, both, the external service provider and caller need to follow a standard with which the signed message of the caller can be validated.
By supporting the ERC-1271 standard, your DAO can validate signatures via its isValidSignature function that forwards the call to a signature validator contract. The signature validator can be set with the setSignatureValidator function.
Aragon OSx DAO implemented this ability in the core DAO contract by the below function:
function isValidSignature(

bytes32 _hash,

bytes memory _signature

) external view override(IDAO, IERC1271) returns (bytes4) {

if (address(signatureValidator) == address(0)) {

// Return the invalid magic number

return bytes4(0);

}

// Forward the call to the set signature validator contract

return signatureValidator.isValidSignature(_hash, _signature);

}
Now let's check the source document, https://eips.ethereum.org/EIPS/eip-1271. It includes 2 important parts :
bytes4 constant internal MAGICVALUE = 0x1626ba7e;
function isValidSignature(
bytes32 _hash,
bytes memory _signature)
public
view
returns (bytes4 magicValue);
Now let's look at the comments of this document :
/**

  • @dev Should return whether the signature provided is valid for the provided hash
  • @param _hash Hash of the data to be signed
  • @param _signature Signature byte array associated with _hash
  • MUST return the bytes4 magic value 0x1626ba7e when function passes.
  • MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
  • MUST allow external calls
    */
    As you can see, if the signature provided is valid for the provided hash, isValidSignature function MUST return the bytes4 magic value 0x1626ba7e.

By the way, in this document, you can see an example implementation of a contract calling the isValidSignature() function on an external signing contract ;

function callERC1271isValidSignature(
address _addr,
bytes32 _hash,
bytes calldata _signature
) external view {
bytes4 result = IERC1271Wallet(_addr).isValidSignature(_hash, _signature);
require(result == 0x1626ba7e, "INVALID_SIGNATURE");
}

As you can see first we make a call to the isValidSignature and store it on the bytes4 result variable and then we make validation on the returned answer to be equal to the 0x1626ba7e.

Now lets see how isValidSignature function implemented on the DAO.sol#L248 :

function isValidSignature(
bytes32 _hash,
bytes memory _signature
) external view override(IDAO, IERC1271) returns (bytes4) {
if (address(signatureValidator) == address(0)) {
// Return the invalid magic number
return bytes4(0);
}
// Forward the call to the set signature validator contract
return signatureValidator.isValidSignature(_hash, _signature);
}

This function just returning bytes4, and doesn’t make validation on returned bytes4. Similarly is happened on the test file,

https://github.com/code-423n4/2023-03-aragon/blob/ded3784e93e4189cf5bd08e5dd2b82da292b60ba/packages/contracts/src/test/dao/ERC1271Mock.sol#L7
https://github.com/code-423n4/2023-03-aragon/blob/ded3784e93e4189cf5bd08e5dd2b82da292b60ba/packages/contracts/test/core/dao/dao.ts#L888

This can cause invalid signatures to be assumed to be valid.

Tools Used

Manually
https://eips.ethereum.org/EIPS/eip-1271

Recommended Mitigation Steps

add below code to the isValidSignature function :
bytes4 result = signatureValidator.isValidSignature(_hash, _signature);
require(result == 0x1626ba7e, "INVALID_SIGNATURE");

Possible DOS (out-of-gas) on loops GovernanceERC20.sol.initialize function

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/token/ERC20/governance/GovernanceERC20.sol#L81
https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/dd8ca8adc47624c5c5e2f4d412f5f421951dcc25/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol#L185

Vulnerability details

Impact

It is possible to get an out-of-gas issue while iterating the for loop in the GovernanceERC20.sol.initialize function.

Please take a look at this link.
https://github.com/wissalHaji/solidity-coding-advices/blob/master/best-practices/be-careful-with-loops.md

Proof of Concept

You can have loops for any function in the Solidity language. However, if the loop is updating some state variables of a contract, there is possible that your contract could get stuck if the loop iteration is hitting the block's gas limit.

Let's say the deployer needs to run for loop in the GovernanceERC20.sol.initialize function and perform the mint operation for an array with a very long length.

Let's say _mint(_mintSettings.receivers[i], _mintSettings.amounts[i]) is :

function _mint(address account, uint256 amount) internal virtual override {
super._mint(account, amount);
require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes");
_writeCheckpoint(_totalSupplyCheckpoints, _add, amount);
}

It consists of three steps, call the mint function to mint tokens to the user, call require to check one condition, and then call the _writeCheckpoint function to update the total supply Checkpoint.

If you call the initialize function during the deployment process, if the call to this function because of any reason gets reverted, the deployment process will encounter an error.

If you plan to call the initialize function after the deployment process, then you must define an ownership address for the contract and define the condition on this function that only the owner of the contract can call this function. After the deployment process, you can safely call the initialize function, if needed, you can remove the initializer modifier and do the one-time call with a variable ( bool value).

Tools Used

Manually

Recommended Mitigation Steps

Use a separate method for mint tokens to the users.

QA Report

See the markdown file with the details of this report here.

TEST2

Lines of code

#L1

Vulnerability details

THIS IS A TEST

QA Report

See the markdown file with the details of this report here.

DAO.execute(bytes32, Action[], uint256) is vulnerable to re-entrancy attacks

Lines of code

https://github.com/code-423n4/2023-03-aragon/blob/4db573870aa4e1f40a3381cdd4ec006222e471fe/packages/contracts/src/core/dao/DAO.sol#L168-L177

Vulnerability details

Description

The present implementation permits the execution of a predetermined sequence of instructions, where the order of execution is at times crucial, as described in the documentation:

Imagine a DAO is currently governed by a multisig (it has the Multisig plugin installed) and wants to transition the DAO governance to token voting. To achieve this, one of the Multisig members creates a proposal in the plugin proposing to

1. install the TokenVoting plugin
1. uninstall the Multisig plugin
If enough multisig signers approve and the proposals passes, the action array can be executed and the transition happens.

**Here, it is important that the two actions happen in the specified order and both are required to succeed**. Otherwise, if the first action would fail or the second one would happen beforehand, the DAO could end up in a state without having a governance plugin enabled.

In the event that any of the actions performs a callback to the permitted msg.sender, that particular address would gain the ability to dispatch the same set of actions.

Impact

Considering that the msg.sender could be a contract, the importance of the order of execution (at least in certain scenarios), and the potential for reentrancy, it can be concluded that the intended behavior is at risk of compromise

POC

In the following hypothetical scenario:

  1. A contract named DAOsWilling possesses the necessary authorization to invoke DAO.execute.
  2. DAOsWilling has established a predetermined sequence of five ordered actions that, according to its code, are guaranteed to succeed. However, it is imperative that these actions be executed in a specific order.
  3. DAOsWilling permits anyone to invoke a function - let's call it executeDaosWilling - in order to execute this pre-established sequence of ordered actions.
  4. Furthermore, DAOsWilling has a contract that contains a callback function for one of the aforementioned actions, which subsequently calls back to the original caller of executeDaosWilling."

Then, next action could happen:

  1. A DAO has established a set of 5 actions in DAOsWilling contract, the 3° action do a callback in name of executeDaosWilling caller
  2. Bob calls executeDaosWilling through a smart contracts he has designed, after the 3° action a callback is done to DAOsWilling, which calls Bob's contract, and Bob's contracts calls executeDaosWilling again
  3. Then 1 to 5 action is executed in a row (let's suppose that in this other callback done by the 3° action Bob's contract do nothing), and then action 4 and 5 are executed

This POC shows that there scenarios where we cannot guarantee that actions are execute in order.

Mitigation steps

Use ReentrancyGuard contract from openzeppelin and add nonReentrant modifier to execute(bytes32, Action[], uint256) function or assume the risk informing them in documentation.

    function execute(
        bytes32 _callId,
        Action[] calldata _actions,
        uint256 _allowFailureMap
    )
+       nonReentrant
        external
        override
        auth(EXECUTE_PERMISSION_ID)
        returns (bytes[] memory execResults, uint256 failureMap)
    {

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.