Open c4-bot-9 opened 5 months ago
Related to #622
Picodes marked the issue as primary issue
othernet-global (sponsor) confirmed
callFromDAO now wrapped in a try/catch
https://github.com/othernet-global/salty-io/commit/5f1a5206a04b0f3fe45ad88a311370ce12fb0135
Picodes marked the issue as satisfactory
Picodes marked the issue as selected for report
I think this is a duplicate of #362.
From Code4rena docs:
Similar exploits under a single issue
Findings are duplicates if they share the same root cause.
More specifically, if fixing the Root Cause (in a reasonable manner) would cause the finding to no longer be exploitable, then the findings are duplicates.
The vulnerabilty's root cause of both #362 and #1009 is that proposals don't expire. It's not a problem in itself that proposals might not reach quorum or that they might revert. Both things are expected behaviors of most governance contracts and simply adding an expiration/cancelling mechanism fixes them.
Note that at least one submission (#490) mentioned both ramifications of the issue but was split in two.
On the other hand, I'm not convinced that adding a try/catch and finalizing failed proposals is a good idea. If a proposal's execution could be DoS by making the target call unavailable even for just a block, the proposal would end, though it would be a lot harder to DoS it during a long time. Look at other governance systems and probably most won't bother with transactions reverting. A few examples: OpenZeppelin Governor, Zodiac's RealityModule, Kleros Governor. IMO proposals not expiring should be fixed, not transactions failing.
@fnanni-0 thanks for your comment. I still think there are 2 distinct issues here although as you're correctly pointing out we could wrap them in a larger one.
finalize
because of https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/dao/Proposals.sol#L385
This issue is about the fact that sometimes the underlying call reverts so you can't call finalize
either
We can imagine adding a function to fix both issues at once, but most suggested mitigations only fix one.
Most reports discuss only one of these 2 things, so to me it's fair to consider there are 2 separate issues.
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L180 https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L219 https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L219
Vulnerability details
Description
The
DAO._executeApproval
function does not handle an external contract call error:Given an approved
CALL_CONTRACT
ballot that can be finalized, the ballot won't be marked as finalized if the external contract call (from above) reverts.Impact
A permanent revert leaves the ballot unfinalized, and the user that posted it with an active proposal (in the
_userHasActiveProposal
mapping); a state that prevents the user account from creating a new proposal. At this point the user has two options to sort out the situation:A. Unstake and transfer its SALT into a new account. B. Convince the other users to reach quorum on NO and finalize the ballot without calling the external contract.
Proof Of Concept
New contract to be created in
/src/dao/tests
:Add the following test in
DAO.t.sol
:Tools Used
Test and manually reviewed.
Recommended Mitigation Steps
A trivial solution is to handle the reverted external contract call with a
try..catch
and allow to always mark the approved ballot as finalized. A new ballot can always be created if the desired effects of the call were not applied on the first call.Amend the
CALL_CONTRACT
case in theDAO._executeApproval
function:Add the following test in
DAO.t.sol
:Assessed type
Governance