Open code423n4 opened 1 year ago
Picodes changed the severity to 2 (Med Risk)
Picodes marked the issue as duplicate of #280
MikeHathaway marked the issue as sponsor confirmed
Picodes marked the issue as satisfactory
Picodes changed the severity to QA (Quality Assurance)
Lines of code
https://github.com/code-423n4/2023-05-ajna/blob/fc70fb9d05b13aee2b44be2cb652478535a90edd/ajna-grants/src/grants/base/Funding.sol#L112-L142
Vulnerability details
Impact
A malicious user can create a proposal with the AjnaToken address as the target, which will cause the proposal execution to fail when the time comes. Although the funds are not lost and can be eventually returned to the treasury, the protocol's functionality and availability could be negatively affected,
Proof of Concept
To create a standard or extraordinary proposal, users must provide the following information:
In this case targets will be the addresses that are supposed to receive the funds if the proposal goes through.
In this case, targets represent the addresses receiving funds if the proposal is approved. For
values[]
, each value must be 0 because we are transferring Ajna tokens. Regardingcalldatas
, each individual calldata must contain0xa9059cbb
, which is the function selector fortransfer(address,uint256)
, the address of the receiver and the amount to be received. This function will be called on the AjnaToken address when it is time for execution. The_validateCalldatas
function is responsible for validating this information.https://github.com/code-423n4/2023-05-ajna/blob/fc70fb9d05b13aee2b44be2cb652478535a90edd/ajna-grants/src/grants/base/Funding.sol#L104-L108
The issue with the current implementation is that while values and calldatas are validated, targets are not.
In some cases, this could impact the functionality or availability of the protocol because the proposal execution will fail.
https://github.com/code-423n4/2023-05-ajna/blob/fc70fb9d05b13aee2b44be2cb652478535a90edd/ajna-grants/src/grants/base/Funding.sol#L60-L65
Once a proposal is created, the target cannot be changed, because the
proposalId
is a hash of the targets, calldatas and values.It is important to note that there is no way to check the proposal execution receiver without examining the transaction calldata since there are no functions that expose the proposal creation data.
https://github.com/code-423n4/2023-05-ajna/blob/fc70fb9d05b13aee2b44be2cb652478535a90edd/ajna-grants/src/grants/base/Funding.sol#L154-L161
The actual execution of the proposal happens here 👇
https://github.com/code-423n4/2023-05-ajna/blob/fc70fb9d05b13aee2b44be2cb652478535a90edd/ajna-grants/src/grants/base/Funding.sol#L51-L66
Which calls AjnaToken's withdraw function to transfer the funds to the target
https://github.com/code-423n4/2023-05-ajna/blob/fc70fb9d05b13aee2b44be2cb652478535a90edd/ajna-grants/src/token/AjnaToken.sol#L11-L14
However, upon observing AjnaToken's functionality, we see that the
_beforeTokenTransfer
hook ensures that tokens cannot be transferred to the AjnaToken contract itself.https://github.com/code-423n4/2023-05-ajna/blob/fc70fb9d05b13aee2b44be2cb652478535a90edd/ajna-grants/src/token/AjnaToken.sol#L27-L29
A malicious user can take advantage of that and create a proposal with the AjnaToken address as target, which will brick the execution of the proposal when the time comes.
Consider the case when the proposal is Standard:
Normally, this wouldn't be a huge deal,but each distribution period is 3 months. So in this case the proposal would go through various stages to figure in 3 months that the proposal won't get executed.
Since there are assets not at direct risk, but the protocol's function or availability could be certainly impacted, I'm rating this as a Medium.
Here's a PoC that showcases the scenario.
Running the test, yields us the following result:
Regrettably, the resources allocated to a specific proposal become permanently locked within it, with the protocol lacking the functionality to recoup them back into the treasury.
Within Ajna, grants are allocated during distinct distribution periods, each spanning three months.
Subject to certain prerequisites, a new distribution period can be activated by calling the following function:
https://github.com/code-423n4/2023-05-ajna/blob/fc70fb9d05b13aee2b44be2cb652478535a90edd/ajna-grants/src/grants/base/StandardFunding.sol#L120-L140
Should the validation confirming the absence of an active distribution period be successful, the function subsequently retrieves and yields the unused funds from the preceding two distribution periods. This is to cover the scenario where for example there are no proposals to execute, but funds were allocated to the distribution period.
In instigating a new distribution, the funds entangled in the unexecutable proposal are expected to be reimbursed to the treasury.
Nonetheless, since the proposal has been approved and is listed in the
fundedSlateHash
, theupdateTreasury
function will infer it as executable and awaiting payment. (that's by design). So, it will add itstokenRequested
to thetotalTokensRequested
.This situation culminates in the treasury not receiving a refund for the total number of tokens requested in that specific proposal. Yet, the proposal itself cannot be executed, resulting in the permanent entrapment of the funds. This outcome is further substantiated by the update to the
_isSurplusFundsUpdated
mapping, causing the ensnared funds to become irreversibly unobtainable.Considering all of these factors, I propose that the appropriate severity for this finding should be classified as 'High', based on the C4 categorization criteria.
Evidently, a credible and feasible attack path exists. First, one could contend that the faulty proposal might enter the leading slate. Yet, it could be replaced by a new top slate featuring different proposals before the period ends, providing it is more optimal. This would then potentially exclude the invalid proposal.
Nonetheless, if the proposal pool during the screening period is under 10 (no new proposals can be considered after the screening period has ended), or if a user or a coalition has accumulated enough votes to propel a malicious proposal forward, these considerations become immaterial.
Tools Used
Manual Review
Recommended Mitigation Steps
In the
_validateCallDatas
function, validate the token receiver's address, ensuring it is not the AjnaToken's address.Assessed type
DoS