Closed code423n4 closed 1 year ago
payloads are verified ahead of execution by the DAO, QA.
0xean changed the severity to QA (Quality Assurance)
0xean marked the issue as grade-c
0xean marked the issue as grade-b
0xean marked the issue as grade-c
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
DAO management is done using the
execute
function that lets permissioned entities execute arbitrary actions from the DAO contract. These actions are composed of a target address, a value and an arbitrary data payload that is then used as calldata. Theexecute
will run through each action executing a low levelcall
to the target address with the associated calldata and value.https://github.com/code-423n4/2023-03-aragon/blob/main/packages/contracts/src/core/dao/DAO.sol#L168-L215
Low level calls to accounts with no code will succeed. Quoting the reference for the CALL opcode in evm.codes:
This means that an action (that potentially has calldata, to discriminate the case of simple transfer of value) to an account with no code will succeed without any errors. The
success
variable will be true and theexecute
will treat these actions as correctly executed (no revert, no failure).Proof of Concept
The following test illustrates the issue. Here we bootstrap a DAO and execute an action to an empty account (
address(0xdeadc0de)
) with a calldata that simulates an ERC20 transfer. The action will succeed and theexecute
function won't revert.Note: the snippet shows only the relevant code for the test. Full test file can be found here.
Recommendation
In case the action has calldata (i.e.
action.data.length > 0
) theexecute
function can first validate that theto
account has non-empty code.Another alternative would be to use a strategy similar to how
verifyCallResultFromTarget
works in theAddress
library of OpenZeppelin contracts, in which they check the account's code length if the returndata length is zero. Again, this path should also contemplate the case when calldata is empty to allow transfers of value that are not intended to execute any code.Example: