Joyn contest - hyh's results

Launchpad for collaborative web3 media projects with blueprints, building blocks, and community support.

General Information

Platform: Code4rena

Start Date: 30/03/2022

Pot Size: $30,000 USDC

Total HM: 21

Participants: 38

Period: 3 days

Judge: Michael De Luca

Total Solo HM: 10

Id: 104

League: ETH

Joyn

Findings Distribution

Researcher Performance

Rank: 7/38

Findings: 5

Award: $1,386.49

🌟 Selected for report: 2

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: kirk-baird

Also found by: Ruhum, hyh

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

724.5042 USDC - $724.50

External Links

Lines of code

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L31 https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L165-L166

Vulnerability details

Impact

Splitter.currentWindow can be made large enough so that claimForAllWindows() run out of gas on the each run, making it unavailable.

Furthermore, as balanceForWindow array will become large enough, finding a window with any substantial amount to claim becomes a task too hard to be solved manually, so it will require some automated solutions from all the users to locate any meaningful amounts to claim via claim(window, allocation, proof), which become the only way to claim.

Running claim(i, ...) for all i will involve too much transaction costs to be economically feasible for the most users.

This way cost of usage will increase and reputational risk emerges.

Setting severity to be medium as core functionality availability is substantially diminished as the result of the attack.

Proof of Concept

Suppose platformFee is 100, i.e. 1%.

Bob the attacker can do say 10^3 repetitions of the steps (1-2):

  1. send 100 wei of royaltyAsset to the royaltyVault balance

  2. run sendToSplitter(), which have no access controls, distributing the whole current royaltyAsset balance of the vault to platform (1 wei) and splitter (99 wei):

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L31

On each run of (2) currentWindow will be incremented and balanceForWindow array expanded by one element:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L165-L166

One simple solution is to check in sendToSplitter() that getVaultBalance() is greater than MIN_ROYALTY_AMOUNT, which is a new additional configuration threshold variable, proceeding with splitting, sending and window increase only when this threshold is met.

This will substantially increase the cost of the attack and also reduce overall transaction overhead of the system by removing the low amount actions.

#0 - sofianeOuafir

2022-04-14T19:18:39Z

In my opinion, the severity level should be 3 (High Risk) instead of 2 (Med Risk) duplicate of #3

#1 - deluca-mike

2022-04-22T02:19:16Z

This is more a duplicate of #6, since it pertains to the possibility of claimForAllWindows being made prohibitively expensive, regardless if its from an attacker abusing incrementWindow, or natural growth of currentWindow without claims.

Findings Information

🌟 Selected for report: hyh

Also found by: Ruhum, WatchPug, hubble, kirk-baird, leastwood, pedroais, rayn, saian, securerodd

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

70.1719 USDC - $70.17

External Links

Lines of code

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L78-L97

Vulnerability details

Impact

Reinitialization is possible for CoreCollection as initialize function sets initialized flag, but doesn't control for it, so the function can be rerun multiple times.

Such types of issues tend to be critical as all core variables can be reset this way, for example payableToken, which provides a way to retrieve all the contract funds.

However, setting priority to be medium as initialize is onlyOwner. A run by an external attacker this way is prohibited, but the possibility of owner initiated reset either by mistake or with a malicious intent remains with the same range of system breaking consequences.

Proof of Concept

initialize doesn't control for repetitive runs:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L87

Add onlyUnInitialized modifier to the initialize function:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L46-L49

#0 - sofianeOuafir

2022-04-14T19:42:04Z

This is a high severity issue and we intend to fix it. The mitigation step looks great and will be considered to fix the issue.

In my opinion, the severity level should be 3 (High Risk) instead of 2 (Med Risk)

#1 - deluca-mike

2022-04-22T02:30:08Z

Not convinced this is a high severity issue, since erroneously changing payableToken via a re-initialization can simply be corrected by a re-re-initialization to set it back correctly. Further, as the warden mentioned, the initialize function is behind onlyOwner.

However, if it can be shown that users other than the owner can end up losing value due to the owner abusing or erroneously using initialize, then it can be promoted to High Severity.

And just as I say that, #17 points that out clearly. So, yes, agreed, this is a High Severity issue.

Findings Information

🌟 Selected for report: kirk-baird

Also found by: 0xDjango, Dravee, Ruhum, TomFrenchBlockchain, WatchPug, defsec, hubble, hyh, leastwood, minhquanym

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

85.0569 USDC - $85.06

External Links

Lines of code

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L67-L70

Vulnerability details

Impact

Placing severity to medium here as setting out of bounds platformFee is relatively easy (different systems have different fee decimals, there is no standard at the moment, so it's not hard to mix things up), while the impact can be strong enough.

For example, if platformFee > 10000 all CoreCollection's transfers will be blocked as _beforeTokenTransfer -> sendToSplitter call will always revert:

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L40-L41

Another example, if platformFee == 10000 the same call will always revert, just a little bit later as splitterShare be zero, while incrementWindow requires it to be positive:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L164

Proof of Concept

platformFee is supposed to be in (0, 10000) range, but it is not controlled by RoyaltyVault and RoyaltyVaultFactory:

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L67-L70

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVaultFactory.sol#L58

Consider adding range check for platformFee in RoyaltyVault's setPlatformFee

#0 - sofianeOuafir

2022-04-15T12:48:40Z

duplicate of #9

Findings Information

🌟 Selected for report: hyh

Also found by: robee

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

362.2521 USDC - $362.25

External Links

Lines of code

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L51-L57 https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L164

Vulnerability details

Impact

When royaltyAsset is an ERC20 that doesn't allow zero amount transfers, the following griefing attack is possible, entirely disabling CoreCollection token transfer by precision degradation as both reward distribution and vault balance can be manipulated.

Suppose splitterProxy is set, all addresses and fees are configured correctly, system is in normal operating state.

POC:

Bob the attacker setup a bot which every time it observes positive royaltyVault balance:

  1. runs sendToSplitter(), distributing the whole current royaltyAsset balance of the vault to splitter and platform, so vault balance becomes zero

  2. sends 1 wei of royaltyAsset to the royaltyVault balance

  3. each next CoreCollection token transfer will calculate platformShare = (balanceOfVault * platformFee) / 10000, which will be 0 as platformFee is supposed to be less than 100%, and then there will be an attempt to transfer it to platformFeeRecipient

If royaltyAsset reverts on zero amount transfers, the whole operation will fail as the success of IERC20(royaltyAsset).transfer(platformFeeRecipient, platformShare) is required for each CoreCollection token transfer, which invokes sendToSplitter() in _beforeTokenTransfer() as vault balance is positive in (3).

Notice, that Bob needn't to front run the transfer, it is enough to empty the balance in a lazy way, so cumulative gas cost of the attack can be kept moderate.

Setting severity to medium as on one hand, the attack is easy to setup and completely blocks token transfers, making the system inoperable, and it looks like system has to be redeployed on such type of attack with some manual management of user funds, which means additional operational costs and reputational damage. On the another, it is limited to the zero amount reverting royaltyAsset case or the case when platformFee is set to 100%.

That is, as an another corner case, if platformFee is set to 100%, platformShare will be 1 wei and splitterShare be zero in (3), so this attack be valid for any royaltyAsset as it is required in Splitter's incrementWindow that splitterShare be positive.

Proof of Concept

As royaltyAsset can be an arbitrary ERC20 it can be reverting on zero value transfers:

https://github.com/d-xo/weird-erc20#revert-on-zero-value-transfers

_beforeTokenTransfer runs IRoyaltyVault(royaltyVault).sendToSplitter() whenever royaltyVault is set and have positive balance:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L307

sendToSplitter() leaves vault balance as exactly zero as splitterShare = balanceOfVault - platformShare, i.e. no dust is left behind:

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L41

This way the balance opens up for the tiny amount manipulation.

One require that can fail the whole operation is platformShare transfer:

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L51-L57

Another is positive royaltyAmount = splitterShare requirement:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L164

The issue is that token transfer, which is core system operation, require fee splitting to be done on the spot. More failsafe design is to try to send the fees and record the amounts not yet distributed, not requiring immediate success. The logic here is that transfer itself is more important than fee distribution, which is simple enough and can be performed in a variety of ways later.

Another issue is a combination of direct balance usage and the lack of access controls of the sendToSplitter function, but it only affects fee splitting and is somewhat harder to address.

As one approach consider trying, but not requiring IRoyaltyVault(royaltyVault).sendToSplitter() to run successfully as it can be executed later with the same result.

Another, a simpler one (the same is in Griefing attack is possible making Splitter's claimForAllWindows inaccessible issue), is to introduce action threshold, MIN_ROYALTY_AMOUNT, to sendToSplitter(), for example:

Now:

/** * @dev Send accumulated royalty to splitter. */ function sendToSplitter() external override { uint256 balanceOfVault = getVaultBalance(); require( balanceOfVault > 0, "Vault does not have enough royalty Asset to send" ); ... emit RoyaltySentToSplitter(...); emit FeeSentToPlatform(...); }

To be:

/** * @dev Send accumulated royalty to splitter if it's above MIN_ROYALTY_AMOUNT threshold. */ function sendToSplitter() external override { uint256 balanceOfVault = getVaultBalance(); if (balanceOfVault > MIN_ROYALTY_AMOUNT) { ... emit RoyaltySentToSplitter(...); emit FeeSentToPlatform(...); } }

Findings Information

Awards

144.508 USDC - $144.51

Labels

bug
QA (Quality Assurance)

External Links

1. No zero address check for new FeeRecipient

Impact

Fees will be lost after FeeRecipient be set to zero by mistake or as a part of griefing attack.

Proof of Concept

platformFeeRecipient isn't controlled to be non-zero address:

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L81

It is, however, controlled on the factory level:

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVaultFactory.sol#L66-L76

Consider unifying the approach and add the check to the RoyaltyVault

2. amountFromPercent and scaleAmountByPercentage use different fraction decimals

Impact

Having different fraction/percentage conventions within one system is error prone and can easily lead to wrong amount calculations and end up with fund losses.

Proof of Concept

amountFromPercent use 10^2:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L223

scaleAmountByPercentage use 10^4:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L103

Consider unifying the fraction/percentage decimals convention by making it 10^4 everywhere

3. Initialize function doesn't perform config parameters check

Impact

A range of malfunctions is possible if core configuration parameters are set by mistake to any values that make little sense. It is also not always right away evident that such a mistake took place

Proof of Concept

CoreCollection initialize() doesn't check the parameters supplied:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L78-L97

initialize() is run by end users via CoreFactory, that doesn't check the parameters either:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L142-L166

Consider adding a layer of control to the CoreCollection initialization

4. PERCENTAGE_SCALE differs from 10000 that's used in calculations

Impact

Amount calculations can become incorrect with a range of impacts up to fund loss and insolvency if contract dedicated constant value and the values hard coded across the implementation differ and are used in various parts of the code with the same intent.

Proof of Concept

Now PERCENTAGE_SCALE is set to be 10^5 and is not used:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L14

While hard coded 10^4 is used in scaleAmountByPercentage:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L103

scaleAmountByPercentage is involved in calculations (while PERCENTAGE_SCALE is not):

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L54

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L137

Consider unifying the fraction/percentage decimals convention by making it 10^4 everywhere, and removing hard code, utilizing PERCENTAGE_SCALE in all related calculations

5. Most events aren't indexed

Impact

Filtering on unindexed events is disabled, which makes it harder to programmatically use and analyse the system.

Proof of Concept

CoreCollection events aren't indexed at all:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L29-L35

Splitter's WindowIncremented one too:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L28

CoreCollection's events also:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L15-L20

Consider adding indexes to ids and addresses in the all important events to improve their usability

6. transferSplitAsset has ETH based comments

Proof of Concept

transferSplitAsset comments state that it's ETH transfers, while it's not:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L235

Update comments to reflect ERC20 token transfer

7. User facing functions miss emergency lever

Impact

If there be any emergency with system contracts or outside infrastructure, there is no way to temporary stop the operations.

Proof of Concept

All core user-facing contracts do not have pausing functionality for new operation initiation.

For example, both CoreFactory’s user facing functions cannot be temporary paused:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L70

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L108

sendToSplitter also can benefit from having pausing functionality:

https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L31

Consider making all new actions linked user facing functions pausable.

For example, by using OpenZeppelin's approach:

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/Pausable.sol

8. Floating pragma is used in most contracts

As different compiler versions have critical behavior specifics if the contracts get accidentally deployed using another compiler version compared to the one they were tested with, various types of undesired behavior can be introduced

Proof of Concept

pragma solidity ^0.8.4 and pragma solidity ^0.8.0 is used in most contracts, for example:

https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L2

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L2

Consider fixing the version to 0.8.x across all the codebase, for example set x to 10

9. New collection contract creation result isn't checked in CoreFactory._createCollection

Impact

_createCollection's initialize call will revert with low level message on deploy failure.

Proof of Concept

_createCollection doesn't check new CoreProxy call result, invokes initialize in all the cases:

https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L147-L164

Require non zero coreCollection address before proceeding with initialize.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter