Open codehawks-bot opened 1 year ago
[APPEAL] This finding seems invalid as no fund loss occurs in this scenario.
As the POC proves, when the organizer calls the ProxyFactory#deployProxyAndDistribute()
method, the transaction reverts. But no tokens are getting stuck or incorrectly transferred.
Here is how the method looks like:
/**
* @notice deploy proxy contract and distribute winner's prize
* @dev the caller can only control his own contest
* @param contestId The contest id
* @param implementation The implementation address
* @param data The prize distribution data
* @return The proxy address
*/
function deployProxyAndDistribute(bytes32 contestId, address implementation, bytes calldata data)
public
returns (address)
{
bytes32 salt = _calculateSalt(msg.sender, contestId, implementation);
if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered();
// can set close time to current time and end it immediately if organizer wish
//@audit-issue The comment above is not true. The organizer has to wait!
if (saltToCloseTime[salt] > block.timestamp) revert ProxyFactory__ContestIsNotClosed();
address proxy = _deployProxy(msg.sender, contestId, implementation);
_distribute(proxy, data);
return proxy;
}
If the data
includes address(0)
, then the _distribute()
call will revert - as proven in the POC. That means this whole transaction will revert, including the proxy deployment.
After this happens, the organizer may simply call the method again, this time without the problematic address(0)
- and the funds will get distributed correctly. The only loss that occurs in this scenario is the gas cost for the organizer which initially called the method with incorrect parameter.
[APPEAL] This finding seems invalid as no fund loss occurs in this scenario.
As the POC proves, when the organizer calls the
ProxyFactory#deployProxyAndDistribute()
method, the transaction reverts. But no tokens are getting stuck or incorrectly transferred.Here is how the method looks like:
/** * @notice deploy proxy contract and distribute winner's prize * @dev the caller can only control his own contest * @param contestId The contest id * @param implementation The implementation address * @param data The prize distribution data * @return The proxy address */ function deployProxyAndDistribute(bytes32 contestId, address implementation, bytes calldata data) public returns (address) { bytes32 salt = _calculateSalt(msg.sender, contestId, implementation); if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered(); // can set close time to current time and end it immediately if organizer wish //@audit-issue The comment above is not true. The organizer has to wait! if (saltToCloseTime[salt] > block.timestamp) revert ProxyFactory__ContestIsNotClosed(); address proxy = _deployProxy(msg.sender, contestId, implementation); _distribute(proxy, data); return proxy; }
If the
data
includesaddress(0)
, then the_distribute()
call will revert - as proven in the POC. That means this whole transaction will revert, including the proxy deployment.After this happens, the organizer may simply call the method again, this time without the problematic
address(0)
- and the funds will get distributed correctly. The only loss that occurs in this scenario is the gas cost for the organizer which initially called the method with incorrect parameter.
Where you see this check, if data
include address(0)
to revert? There no have such a check , and transaction will not revert. As this will lose winner tokens!
POC itself demonstrates that revert. The revert I am talking about is happening inside the ERC20's transfer. Note also that the OZ safeTransfer is used to execute the transfer.
Yes I also concurs with asobiesk, I saw this very quickly in the audit but discarded right away as safeTransfer
will always revert on address(0) transfer.
The revert will happen here:
Distributor.sol
erc20.safeTransfer(winners[i], amount);
ERC20.sol
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
Okay, I've been thinking about what you have said on Discord @B353N and I think we are all patrially right and partially wrong.
The Distributor
contract indeed uses OpenZeppelin's safeTransfer, but that only means it will revert if the token's transfer
returned false. @dontonka has mentioned the ERC20.sol
code, but the Distributor
code uses IERC20.sol
to wrap the token's address. As such, it does not mean that the actual token's implementation will be alligned with the OZ's standard.
There still may be a token that will not revert on transfer to zero address and will return true. DAI is an example of such a token. If DAI is used in the price pool, the transfer to address zero will indeed succeed.
Having said that, I still argue that this particular issue is incorrect - it clearly mentions revert as a proof of token loss, while the token loss occur in a completely opposite scenario - when there is no revert. However, there is one potentially valid finding under the label - #721. This seems to be the only one that mentions the actual issue.
Those findings may still be deemed invalid based on the fact that the organizer is a trusted role, so their mistakes are out-of-scope. This probably will be a tough call for judges as docs only mention owner as a trusted role, but organizer was also stated to be trusted on Discord by the contest's sponsors (and the fact that they are trusted may be implicitly deducted from the known issue "We may build a reputation system in the futue to handle the issue of the system being maliciously used, e.g., sybil attack."). This dillema is likely going to appear in multiple appeals.
If the judges decide that the organizer is not trusted, then I would say #721 should be accepted as a valid solo medium.
Okay, I've been thinking about what you have said on Discord @B353N and I think we are all patrially right and partially wrong.
The
Distributor
contract indeed uses OpenZeppelin's safeTransfer, but that only means it will revert if the token'stransfer
returned false. @dontonka has mentioned theERC20.sol
code, but theDistributor
code usesIERC20.sol
to wrap the token's address. As such, it does not mean that the actual token's implementation will be alligned with the OZ's standard.There still may be a token that will not revert on transfer to zero address and will return true. DAI is an example of such a token. If DAI is used in the price pool, the transfer to address zero will indeed succeed.
Having said that, I still argue that this particular issue is incorrect - it clearly mentions revert as a proof of token loss, while the token loss occur in a completely opposite scenario - when there is no revert. However, there is one potentially valid finding under the label - #721. This seems to be the only one that mentions the actual issue.
Those findings may still be deemed invalid based on the fact that the organizer is a trusted role, so their mistakes are out-of-scope. This probably will be a tough call for judges as docs only mention owner as a trusted role, but organizer was also stated to be trusted on Discord by the contest's sponsors (and the fact that they are trusted may be implicitly deducted from the known issue "We may build a reputation system in the futue to handle the issue of the system being maliciously used, e.g., sybil attack."). This dillema is likely going to appear in multiple appeals.
If the judges decide that the organizer is not trusted, then I would say #721 should be accepted as a valid solo medium.
You are wrong, the issue state ithe impact is that sponsors funds will be lost if winner address is zero address, please read the impact again . It's that and not because token transfer will revert . There is a slight wrong thing in PoC because it used a standard ERC20 and not one that will allow transfer to zero address such as DAI . That being said this issue is valid as the impact of setting address to zero is that sponsors can lose fund ( although it's not mentionned that specific tokens as DAI will cause this lost of funds ) but it's still valid. Judge may be look for another duplicate that states this for selection. Overral Issue is valid.
@dontonka DAI is indeed listed as a reference in the whitelisted tokens. Furthermore, we do not have a confirmed list of whitelisted tokens that will be used, but only an “example”. It is possible there are more tokens that potentially would’ve been used, which there transfers would not revert, if not for the audit shedding light. Also, not all of the issue’s duplicates mention the loss of funds upon revert instead of on send.
Due to what I highlighted above and what I’ve seen across different judging phases and contests, I haven’t encountered a case where all issues with tokens that revert are invalidated whilst the ones that go through are validated, the fact of the matter is that a scenario/s that leads to loss of funds due to this reason exists, which could be very easily mitigated with a check for address(0). The rest I leave to the judges.
First this issue is caused by input error, yes it is great to have a check that can prevent this input error. This does not mean that the issue above is a valid high. Funds can also be lost if organizer inputs the incorrect addresses. Should we also have another check to ensure all addresses are correct, how would that even work? I feel like missing 0 address checks issues should at most be medium, if not always low. Especially in this scenario where the organizer is assumed to be trusted. It is weird to trust an organizer with the disbursal of funds but then later say, actually we don't trust that you might mess up the addresses and input a 0 address.
From the codehawks docs, we can reference the matrix...
Likelihood is low. impact, ill be fair and say high.
According to the codehawks docs, this at most should be a medium. I am leaning towards low.
@dontonka DAI is indeed listed as a reference in the whitelisted tokens. Furthermore, we do not have a confirmed list of whitelisted tokens that will be used, but only an “example”. It is possible there are more tokens that potentially would’ve been used, which there transfers would not revert, if not for the audit shedding light. Also, not all of the issue’s duplicates mention the loss of funds upon revert instead of on send.
Due to what I highlighted above and what I’ve seen across different judging phases and contests, I haven’t encountered a case where all issues with tokens that revert are invalidated whilst the ones that go through are validated, the fact of the matter is that a scenario/s that leads to loss of funds due to this reason exists, which could be very easily mitigated with a check for address(0). The rest I leave to the judges.
Agreed. My mistake and lack of knowledge here, missed this was only an interface and the actual ERC20 implementation might have such flaw, and didn't knew DAI was a clear example of that, good learning for me, thank you XD.
this is a valid finding however an organiser can mistakenly put a random non-zero address. address(0) just means funds are gone forever. if you put a wrong non-zero address a slim chance someone controls the corresponding private key and is willing to return the funds. my point is checking for address(0) won't really do much. still nice to have though. overall it's a no brainer check on the frontend side anyways or whatever means an organiser uses to close the contest.
Organizers are trusted. Owners are trusted. That alone invalidates this issue.
I agree with @justefg - and this issue should be downgraded to low
, info
, or invalidated
altogether. While doing a require winner != address(0)
is standard, the root cause is still a user input error. This should also be in line with the discussions held here: https://github.com/Cyfrin/2023-08-sparkn/issues/897 of user input error not being able to go as high/medium
findings.
If we recognize the user input errors, then the same high/medium findings could pertain to a random non-zero address address(1)
, address(2)
, etc. Or just any mistakenly inputted address to someone who did not participate and win the contest.
As I described in the findings one must refer directly to the code itself to understand and validate the issue and precisely not to make assumptions or subjective rethoric of "trusted" or "not trusted" because those are only assumptions per se, which are not valid at all.
The code itself has zero address checkings for:
the organizer and implementation address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L109
the token address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L120
the factory and stadium address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L77
the proxy address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L212
and the whitelisted tokens addresses: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L84
which is exactly why they need to verify winners. Moreover, out there exists vast documentation regarding strict policies in relation of never allowing zero addresses (a.k.a https://forum.openzeppelin.com/t/removing-address-0x0-checks-from-openzeppelin-contracts/2222) among others and in several other audits, this checking is crucial (depending of course on the context given) but without a doubt it should be considered as an important issue. Examples:
Honestly arguing just by saying trusted not trusted, or even without referring to the code or by implying human error diminishes the fact that we are talking about smart contracts, everything should be checked on chain, on the code, and this is one of the basic premises here. If not, then better to write a document if you want to make assumptions instead of a smart contract.
Regards,
As I described in the findings one must refer directly to the code itself to understand and validate the issue and precisely not to make assumptions or subjective rethoric of "trusted" or "not trusted" because those are only assumptions per se, which are not valid at all.
The code itself has zero address checkings for:
the organizer and implementation address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L109
the token address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L120
the factory and stadium address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L77
the proxy address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L212
and the whitelisted tokens addresses: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L84
which is exactly why they need to verify winners. Moreover, out there exists vast documentation regarding strict policies in relation of never allowing zero addresses (a.k.a https://forum.openzeppelin.com/t/removing-address-0x0-checks-from-openzeppelin-contracts/2222) among others and in several other audits, this checking is crucial (depending of course on the context given) but without a doubt it should be considered as an important issue. Examples:
Honestly arguing just by saying trusted not trusted, or even without referring to the code or by implying human error diminishes the fact that we are talking about smart contracts, everything should be checked on chain, on the code, and this is one of the basic premises here. If not, then better to write a document if you want to make assumptions instead of a smart contract.
Regards,
Following this line of thought, known-issues should be submitted as bugs as well because.. code == code. This does not work in audit contests.
I agree with the general idea of auditing the code, not human assumptions.
However, people get punished for having too many invalid issues as per CodeHawks docs. This means that whenever a sponsors communicates or sets a restraint, you are expected not to report the restraint as a valid issue because it could invalidate your correct ones. There's a reason why people don't report known issues.
I do agree that in platforms like Immumefi, code should be code and that's it. If you find a bug, it's a bug.
As I described in the findings one must refer directly to the code itself to understand and validate the issue and precisely not to make assumptions or subjective rethoric of "trusted" or "not trusted" because those are only assumptions per se, which are not valid at all. The code itself has zero address checkings for: the organizer and implementation address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L109 the token address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L120 the factory and stadium address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L77 the proxy address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L212 and the whitelisted tokens addresses: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L84 which is exactly why they need to verify winners. Moreover, out there exists vast documentation regarding strict policies in relation of never allowing zero addresses (a.k.a https://forum.openzeppelin.com/t/removing-address-0x0-checks-from-openzeppelin-contracts/2222) among others and in several other audits, this checking is crucial (depending of course on the context given) but without a doubt it should be considered as an important issue. Examples: https://solodit.xyz/issues/h-01-someone-can-create-non-liquidatable-auction-if-the-collateral-asset-fails-on-transferring-to-address0-code4rena-yield-yield-witch-v2-contest-git https://solodit.xyz/issues/h-1-attackers-can-use-reclaimcontract-to-transfer-assets-in-protocol-to-address0-sherlock-bullvbear-bull-v-bear-git Honestly arguing just by saying trusted not trusted, or even without referring to the code or by implying human error diminishes the fact that we are talking about smart contracts, everything should be checked on chain, on the code, and this is one of the basic premises here. If not, then better to write a document if you want to make assumptions instead of a smart contract. Regards,
Following this line of thought, known-issues should be submitted as bugs as well because.. code == code. This does not work in audit contests.
I agree with the general idea of auditing the code, not human assumptions.
However, people get punished for having too many invalid issues as per CodeHawks docs. This means that whenever a sponsors communicates or sets a restraint, you are expected not to report the restraint as a valid issue because it could invalidate your correct ones. There's a reason why people don't report known issues.
I do agree that in platforms like Immumefi, code should be code and that's it. If you find a bug, it's a bug.
Following this line of thought, known-issues should be submitted as bugs as well because.. code == code. This does not work in audit contests. // I do not know if all known issues, this is just considering the context of this particular code and taking into account the inners of the application itself. Thats why when quoting the examples in solodit I highlighted depending on context.
However, people get punished for having too many invalid issues as per CodeHawks docs. This means that whenever a sponsors communicates or sets a restraint, you are expected not to report the restraint as a valid issue because it could invalidate your correct ones. There's a reason why people don't report known issues. // I would agree with this if everything will be clear since the beginning within its code or documentation, but then I feel there are a lot of contradictions still, and the proof is this discussion we are having. Specifically in this situation, you see the code and then you see they check address zero for everything except the winners, you then check the documentation prepared that is not very descriptive in this way or clear and then you are expected to dig tons and tons of assumptions in chat/conversations etc for having a clarification. Thats why I recalled on code (which honestly should be their ultimate goal), and if we are assumming they want to use smart contracts capabilities and they miss one single check that can provoke something unexpected I believe its a valid issue.
I do agree that in platforms like Immumefi, code should be code and that's it. If you find a bug, it's a bug.// Yes agreed.
Thanks for your feedback!
Note: Zero address validation can't be compared with organizers putting wrong winners address in the array.
I beg codehawks to introduce penality on any escalation made, i mean money penality. If one does an escalation on an issue he haven't submitted based purely on assumptions without providing a PoC that unvalidate the issue, hé must be punished. We can't tolerate so many comments on a single escalation that are purely based on assumptions. This waste judge time for nothing, and remember judges are working for free atm, so it should be a no-go zone for escalation based on assumptions.
This issue have already been proven valid by the original escalator, any more comments should provide a pure PoC and not assumptions. We are not doing politics here.
Appeal!
This is a low issue. It's a user mistake issue which should be considered as a Low severity issue.
Escalate
Overinflated severity.
[APPEAL] I thought a lot about why the protocol has the
distributeByOwner
function. It seems that thedistributeByOwner
function is exactly for this case. According to the selected report the funds can be locked at the contract balance. Let's look at thedistributeByOwner
function:196 * @notice Owner can rescue funds if token is stuck after the deployment and contest is over for a while 205 function distributeByOwner( 206 address proxy, 207 address organizer, 208 bytes32 contestId, 209 address implementation, 210 bytes calldata data 211 ) public onlyOwner { 217 _distribute(proxy, data); 218 }
It is obvious that the owner can send a new
data
to the_distribute
. So even if the funds were not distributed by thedeployProxyAndDistribute
function they still can be distributed by thedistributeByOwner
function. So the severity of the lack zero check issue is Low/Informational.
You have completely missed the point of this issue and its duplicates, it is not about funds getting stuck but getting lost if sent to Address(0) since there are tokens that do not revert on transfer to addr0 but pass.
Which is a user mistake. Still low
User mistakes are not a valid finding, @PatrickAlphaC clarified this when he closed this finding: https://github.com/Cyfrin/2023-08-sparkn/issues/769#issuecomment-1709508206
This is a user mistake as well so it should be closed. Not sure where the inconsistency is coming from.
Organizer being trusted was specified in the discord after readme was already released without that information, as agreed upon by CodeHawks. There are auditors who are not active in the discord chat and could not have known that now Organizer is suddenly trusted.
Other issues regarding malicious organizer able to distribute earnings to arbitrary address are also marked as valid.
If that is the case regarding the judge’s decision, then in this issue the Organizer can maliciously distribute to Addr(0), so, not user-mistake.
User mistakes are not a valid finding, @PatrickAlphaC clarified this when he closed this finding: https://github.com/Cyfrin/2023-08-sparkn/issues/769#issuecomment-1709508206
This is a user mistake as well so it should be closed. Not sure where the inconsistency is coming from.
Prev I thought this is a low. But after reading Patrick's comment, I can say that this should be invalid not even low. Thanks @0xhahax0 for pointing out
Organizer being trusted was specified in the discord after readme was already released without that information, as agreed upon by CodeHawks. There are auditors who are not active in the discord chat and could not have known that now Organizer is suddenly trusted.
Other issues regarding malicious organizer able to distribute earnings to arbitrary address are also marked as valid.
If that is the case regarding the judge’s decision, then in this issue the Organizer can maliciously distribute to Addr(0), so, not user-mistake.
Unfortunately this decision has been made after the SparkN contest has finished, in a discord channel, without an announcement. Following your logic - this decision should not be valid because not every auditor is active on Discord.. right?
There was a already a precedent set as explained in https://github.com/Cyfrin/2023-08-sparkn/issues/506#issuecomment-1711098870
Cyrfin team-members, sponsors and precedent all point to communication during the SparkN contest being part of the audit. It is what it is.
I do agree that it's good to know that for all contests from now on, the specs will be the final judgement. I hope the team makes an announcement via official channels because 'not all auditors use Discord', lol.
I accidentally had this marked as high. Thank you for the appeals. Looking at the invalidation notes now.
Going to leave it as low, as organizers are not necessarily trusted. Moving forward, zero address check and any "user input error" are likely going to be invalidated. This has been a great conversation.
Looking forward to people making a PR to the docs to implement/adjust rules as they see fit.
There might be an assumption that DAI will also revert, and it does not for address(0). So, yes, leaving as low.
Let's submit a finding to adjust our rules on this.
Funds can be lost in case winners == address(0).
Severity
High Risk
Summary
If the winners’ addresses are erroneously set with the zero address, the distribution will result in a loss of funds.
Vulnerability Details
The contract can be successfully deployed with a zero address for the winners (aka supporters), which should not be allowed.
In the contracts, there are specific checks for nonzero address in
the organizer and implementation address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L109
the token address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L120
the factory and stadium address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L77
the proxy address: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L212
and the whitelisted tokens addresses: https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/ProxyFactory.sol#L84
But even though there is a specific check that reverts when the winners array length is equal to 0 (https://github.com/Cyfrin/2023-08-sparkn/blob/main/src/Distributor.sol#L125), the winner’s addresses itself are not verified against the zero address.
Consider the following PoC using the HelperContract.t.sol. As mentioned it is possible to make a deployment using the address(0) for the winners as nothing prevents to do it and there is no check implemented; therefore we can make a minor modification to change the variable
user1
toaddress(0)
, where all the actors are declared to test our scenario:After executing the test where all conditions are met, so the contest should succeed
testIfAllConditionsMetThenUsdcSendingCallShouldSuceed
, we get a revert when distributing the prizes because one of the winners is having an address(0), which we know in advance is user1, as this user is used throughout the function test:Impact
Sponsor funds can get lost in contract if winners’ addresses are erroneously set as a zero address.
Tools Used
Static review
Recommendations
There should be zero address checkings in place for the winners (supporters) to prevent loss of funds.