Open c4-submissions opened 1 year ago
return false of transferFrom()
from eip-20
Callers MUST handle false from returns (bool success). Callers MUST NOT assume that false is never returned!
token should comply with ERC20votes standard, but revert on failure is not ERC20 standard.
https://github.com/code-423n4/2023-10-ens-findings/issues/90 is similar to this one, so combine.
141345 marked the issue as primary issue
141345 marked the issue as sufficient quality report
Arachnid marked the issue as disagree with severity
Agreed this is a valid issue - to be ERC20 compliant we must check the return value. Given the low likelihood - most token implementations and all known implementations of ERC20Votes revert - I would argue this should be rated as Medium.
Arachnid (sponsor) confirmed
Medium severity is appropriate with the low likelihood.
hansfriese changed the severity to 2 (Med Risk)
hansfriese marked the issue as satisfactory
hansfriese marked the issue as selected for report
hansfriese marked the issue as not selected for report
hansfriese marked the issue as duplicate of #90
hansfriese marked the issue as not a duplicate
hansfriese changed the severity to 3 (High Risk)
hansfriese marked the issue as primary issue
hansfriese marked the issue as selected for report
Why has this been recategorised as high? As discussed elsewhere, there are no known implementations of erc20votes that return rather than revert, so this is not exploitable. In my mind that makes it a medium.
@hansfriese you agreed with medium here: https://github.com/code-423n4/2023-10-ens-findings/issues/91#issuecomment-1775087374
@Arachnid I agree that it falls between High and Medium. Following a discussion on this submission, I decided to split it into 2 impacts.
ERC20.transfer
.I don't understand how you think this can be high when you'd have to use it with a token contract that presently doesn't exist in order for it to be exploitable.
For #2, I'm not sure I understand what distinction you are drawing here. What's the "unexpected behaviour" being referred to that isn't covered by #1?
I don't understand how you think this can be high when you'd have to use it with a token contract that presently doesn't exist in order for it to be exploitable.
I shared the same view when I participated in the audit contest. Here's a line of reasoning that can convince me about the high impact.
The contract is expected to work against the ERC20 standard itself, not just the ENS token. The ERC20 standard only states that failed transfer should throw, but not a must. Thereby the contract should be evaluated against the ERC20 standard itself, and not just tokens currently in existence.
The reason this is high severity is because future tokens that correctly follows ERC20 standards will have this issue when integrating with the multi-delegate token. The likelihood of this happening is not randomized or probabilistic by nature, and we would not want an audited code that is expected to work with ERC20 standards to be in use but actually not work on ERC20-compliant tokens.
Had the scope of the contest being on ENS token only, I would agree that this is QA (invalid, even).
I agree with the skepticism about dividing impacts though, this is quite a hard thing to consider. Although I will respect the judge's decision regardless.
The possibility of this issue materializing is still an external requirement, and not a direct risk. That is the characterization of Medium. Furthermore, it is highly unlikely.
It also boils down to the ambiguity of which tokens this is intended to support. As quoted in this report @Arachnid said it should support "wrapped ERC20Votes tokens". What did you mean by this? If you meant the actual ERC20Votes by OpenZeppelin, then this issue is invalid. ERC20Votes is not a standard, so I don't think one can conclude from this that the supported tokens must be ERC20 compliant. In fact ERC20 tokens are explicitly not supported by the contract; they must have a voting delegation functionality which transfers voting power on token transfer, which is not ERC20. Therefore it doesn't make sense to simply say that the contract should be ERC20 compliant. The supported tokens are either to be wrapped ERC20Votes tokens, which DO revert on failure, or else it is not clear what tokens are supported.
I feel like the same debate is being transposed here. Everything important was said under the long thread here in the 48h we had to debate about it: https://github.com/code-423n4/2023-10-ens-findings/discussions/697#discussioncomment-7385930
And it feels like, every time, the last person talking thinks their point will win, which is making this get out of hand. As a reminder, the post-judging QA period has ended 16 hours ago. Unless the judge or sponsor specifically ask for more information, I'd advise wardens against pursuing the matter or answering/asking questions here any further.
Whatever happens, we should be fine with it, the judge is doing his best in the middle of all our different point of views and with this particular scenario. Given https://docs.code4rena.com/awarding/fairness-and-validity#expectations-of-participants and https://github.com/code-423n4/2023-10-ens-findings/discussions/697#discussion-5777510, ultimately it may be a matter of the judge's personal thoughts of what feels right given C4's own rules:
Judges should be impartial and free to act independently to do what they see best in a given contest within the guidelines they are provided.
Reminders [...] C4 judges have final authority in determining validity and severity.
For #2, I'm not sure I understand what distinction you are drawing here. What's the "unexpected behaviour" being referred to that isn't covered by #1?
There are two impacts to consider here:
After talking it over with another judge, we agreed to categorize them as two separate impacts. Based on your comment and the severity classification (C4), it seems reasonable to label this as High as the No Revert on Failure
assumption is not that unrealistic.
Your comment - By us, yes, but consider the goal of the audit to be against any wrapped erc20votes token, not just $ens
C4 Severity Categorization - 3 — High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).
There are two impacts to consider here:
- Steal funds from proxy/other users by manipulating the delegation balances
- Free minting of ERC20MultiDelegate tokens without further impacts
Aren't these the same thing? The latter enables the former.
Based on your comment and the severity classification (C4), it seems reasonable to label this as High as the
No Revert on Failure
assumption is not that unrealistic.Your comment -
By us, yes, but consider the goal of the audit to be against any wrapped erc20votes token, not just $ens
Can you cite any erc20votes token implementations that behave this way? If not, that seems like a "hand-wavy hypothetical" that warrants downgrading to medium.
Alright. I understand. My primary goal was to separate them into two different impacts. If you insist, I'll decrease the severity of this one to a Medium level. Moreover, #90 will be downgraded to QA.
hansfriese changed the severity to 2 (Med Risk)
Lines of code
https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L148 https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L160 https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L170 https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L101-L115
Vulnerability details
Forenote
There's an appropriately invalidated finding found by the bots during the bot-race about the unsafe use of
transferFrom
on non-standard ERC20 tokens: bot-report.md#d24-unsafe-use-of-erc20-transfertransferfrom. The finding is mostly invalid because, here, we're usingERC20Votes
tokens, notERC20
ones, hence the mentioned tokens like USDT aren't good arguments. I would like to argue, however, that the recommendation that would've been true here would be to wrap thetransferFrom
calls in arequire
statement, as thetransferFrom
functions used inERC20Votes
are still from the inheritedERC20
interface and therefore could be returning a boolean (transferFrom(address from, address to, uint256 amount) returns bool
, see OpenZeppelin's implementation) instead of reverting, depending on the existence of such anERC20Votes
token. The assumption of anERC20Votes
token returningtrue
orfalse
instead of reverting will therefore be used in this argumentation and be considered a possibility, especially since the list of potentialERC20Votes token
s used by this contract isn't specified (ENSToken
isn't enforced). Also, see these posts from the Discord channel:About this finding
This finding is the second one in a series of 2 findings using a similar set of arguments, but the first is used here as a chain:
ERC20MultiDelegate
tokensERC20Votes
tokensSome parts are similar between the two findings, but because they each deserved their own analysis and "should fix"-argumentation, they are submitted as separate findings.
Impact
Draining all
ERC20Votes
tokens.Proof of Concept
Starting assumptions
The
token
used asERC20Votes
returns the booleanfalse
withtransferFrom
instead of reverting (Not very likely implementation but still a possible and damaging edge-case).MockERC20Votes contract
The following
test/mocks/MockERC20Votes.sol
file is a simplifiedERC20Votes
token that wraps the originaltransferFrom()
function to return abool
instead of reverting:The tests
test_transferFromReturningTrue
andtest_transferFromReturningFalse
are provided to showcase an example implementation of anERC20Votes
token that, instead of reverting, would return a booleansuccess == false
. The reason for such a token's existence won't be discussed as the sheer possibility of its existence is the only argument that is of interest to us (and demands from customers are sometimes surprising). As yet again another reminder: the "standard" is still respected in this argumentation.Foundry Setup
Add
require("@nomicfoundation/hardhat-foundry")
inhardhat.config.js
and run this to be able to run the POC:Test contract
test/delegatemulti.t.sol
file containing the code below and focus ontest_directDraining()
:forge test --mt test_directDraining
and see this test passingHere the layout of what's happening (the first 3 steps are like "Bug1: test_freeMinting"):
ERC20Votes
tokens (5
) but noERC20MultiDelegate
tokensdelegateMulti()
by targeting existing IDs onERC20MultiDelegate
and inputtingamount == 100
for each of themtransferFrom()
function in this example returns a boolean) makes it that the silent failure enables Alice to mint any amount on any ID onERC20MultiDelegate
ERC20MultiDelegate
tokens by callingdelegateMulti()
, with this time the deployed proxy contracts assource
sERC20Votes
tokens got drained from all deployed proxies and were transferred to AliceHere we're both breaking accounting (bug1) and taking advantage of approved funds to the main contract by the deployed proxies to drain all
ERC20Votes
tokens.Again, this contract's security shouldn't depend on the behavior of an external
ERC20Votes
contract (it leaves vectors open), hence this is a "Should fix" bug, meaning at least Medium severity. The token-draining part makes an argument for a higher severity, hence the submission as High Severity.Remediation
While wrapping the
transferFrom()
statements in arequire
statement is a good idea that was suggested in the previous bug, it would also be advisable to try and enforce an invariant by checking for thesource
's balance inside_reimburse()
, just like it is done inside_processDelegation()
(albeit, there, it's for theERC20MultiDelegate
's internal balance, and notERC20Votes
's external balance check. The principle still holds and adding a check would increase security. Note that, while the existingassert()
can be sidestepped, and this is detailed in another finding, it wouldn't be the case withERC20Votes
's external balance due to the immediate transfer)Assessed type
Token-Transfer