Open code423n4 opened 2 years ago
We acknowledge that a Noun holder can push the refund amount up with a long reason string. We think this is low risk since again this is capped by the number of proposals one can vote on, furthermore buying an expensive Noun just to perform this no-profit attack seems unlikely at the moment.
Having said that, we do plan to mitigate this concern by adding a cap on the gasUsed
variable used in the refund calculation.
Lines of code
https://github.com/code-423n4/2022-08-nounsdao/blob/45411325ec14c6d747b999a40367d3c5109b5a89/contracts/governance/NounsDAOLogicV2.sol#L518-L524 https://github.com/code-423n4/2022-08-nounsdao/blob/45411325ec14c6d747b999a40367d3c5109b5a89/contracts/governance/NounsDAOLogicV2.sol#L533-L544 https://github.com/code-423n4/2022-08-nounsdao/blob/45411325ec14c6d747b999a40367d3c5109b5a89/contracts/governance/NounsDAOLogicV2.sol#L98
Vulnerability details
Voters can burn large amounts of Ether by submitting votes with long reason strings
https://github.com/code-423n4/2022-08-nounsdao/blob/45411325ec14c6d747b999a40367d3c5109b5a89/contracts/governance/NounsDAOLogicV2.sol#L518-L524 https://github.com/code-423n4/2022-08-nounsdao/blob/45411325ec14c6d747b999a40367d3c5109b5a89/contracts/governance/NounsDAOLogicV2.sol#L533-L544 https://github.com/code-423n4/2022-08-nounsdao/blob/45411325ec14c6d747b999a40367d3c5109b5a89/contracts/governance/NounsDAOLogicV2.sol#L98
Description
The only limits to how long a string argument to a function call can be is the block gas limit of the EVM, currently 30 million. It is therefore possible for a user to call
NounsDAOLogicV2.castRefundableVoteWithReason()
with a very, very longreason
string.castRefundableVoteInternal()
emits an event that includesreason
on line 540, which is within the region of code covered by gas refunds (gas refunds are measured fromstartGas
on line 538). Because of this, gas refunds will include the gas price of emitting this event, which could potentially be very large.Impact
This issue is partially mitigated by the fact that the voter will still bear the cost of the massive calldata usage.
NounsDAOLogicV2
covers this with a fixed value ofREFUND_BASE_GAS
(36000), but the real transaction overhead is far larger when submitting areason
string that is many thousands of characters in length. Therefore, the voter ends up losing roughly as much as is drained from theNounsDAOLogicV2
contract by the refund. Nonetheless, I still think this is a valid high funding as the protocol will not want to rely purely on this economic protection. Some risk scenarios: 1) It is quite possible that calldata prices could decrease in future, perhaps as part of catering for rollups. This could make the attack suddenly far more viable. 2) A voter might have some motive to want to emit some arbitrary text as an Ethereum event, and simply exploit this system to do so. 3) A voter might want to maliciously drain the Ether, perhaps to damage the protocol's reputation. 4) As in 3, this could be achieved by emptying out the last funds in`NounsDAOLogicV2
and so denying many other voters their voting refunds.Tools Used
Code inspection, hardhat testing
Recommended Mitigation Steps
2 alternative ideas: 1) Move the
emit VoteCast
outside of the gas calculation region of the code and increaseREFUND_BASE_GAS
to cover an event with a reasonable length of string. 2) Change the type ofreason
tobytes
and add a check to its length incastRefundableVoteWithReason()
, reverting if it is too long.Proof of Concept
The single vote in this test burns around 0.25 Ether from the
NounsDAOLogicV2
contract. This test runs slowly and is assuming a base fee of 500 gwei. Obviously if the base fee were higher, the gas burnt would also be higher. The numbers are printed out with a rather messyconsole.log()
in the middle of the test output. Apologies for the bad presentation, but on the bright side you can adjust the numbers and see different results.