Sparkn

CodeFox Inc.
DeFiFoundryProxy
15,000 USDC
View results
Submission Details
Severity: medium

Underflow Bypass in Distribution Logic

Summary

In the _distribute function, if the percentages array is empty, the loop calculating totalPercentage will never run. As a result, totalPercentage will remain zero. This can lead to an underflow issue when the check totalPercentage != (10000 - COMMISSION_FEE) is performed. Since 10000 - COMMISSION_FEE is always positive, the condition will pass even when totalPercentage is zero, which is not the intended behavior.

Vulnerability Details

See the summary
Here is the vulnerable part in code

function _distribute(address token, address[] memory winners, uint256[] memory percentages, bytes memory data)
internal
{
// token address input check
if (token == address(0)) revert Distributor__NoZeroAddress();
if (!_isWhiteListed(token)) {
revert Distributor__InvalidTokenAddress();
}
// winners and percentages input check
if (winners.length == 0 || winners.length != percentages.length) revert Distributor__MismatchedArrays();
uint256 percentagesLength = percentages.length;
uint256 totalPercentage;
for (uint256 i; i < percentagesLength;) {
totalPercentage += percentages[i];
unchecked {
++i;
}
}
// check if totalPercentage is correct
if (totalPercentage != (10000 - COMMISSION_FEE)) {
revert Distributor__MismatchedPercentages();
}
IERC20 erc20 = IERC20(token);
uint256 totalAmount = erc20.balanceOf(address(this));
// if there is no token to distribute, then revert
if (totalAmount == 0) revert Distributor__NoTokenToDistribute();
uint256 winnersLength = winners.length; // cache length
for (uint256 i; i < winnersLength;) {
uint256 amount = totalAmount * percentages[i] / BASIS_POINTS;
erc20.safeTransfer(winners[i], amount);
unchecked {
++i;
}
}
// send commission fee as well as all the remaining tokens to STADIUM_ADDRESS to avoid dust remaining
_commissionTransfer(erc20);
emit Distributed(token, winners, percentages, data);
}

Impact

With this issue an attacker can distribute tokens without adhering to the intended distribution logic. The condition for a valid distribution (totalPercentage equals 10000 - COMMISSION_FEE) could be bypassed, leading to unintended token distribution. And result in financial losses, as tokens might be distributed incorrectly or unfairly to recipients.

  • Here is the Exploitation Plan:
    The attacker will call the distribute function with an empty winners array and percentages array. This will result in totalPercentage remaining at zero.
    Since totalPercentage is zero, the condition totalPercentage != (10000 - COMMISSION_FEE) will evaluate to true, despite the intended logic.
    Exploitation Steps:

  • Attacker's Call
    The attacker crafts a transaction to call the distribute function with empty arrays:

distribute(tokenAddress, [], [], maliciousData);
  • Execution Path:
    The attacker's call will not enter the loop calculating totalPercentage, as the percentages array is empty.
    Thus, totalPercentage will remain zero.
    The condition totalPercentage != (10000 - COMMISSION_FEE) evaluates to true (0 != 9500).
    The intended logic of verifying a valid distribution is bypassed.

The attacker successfully tricks the contract into considering the distribution as valid, despite the fact that no winners have been specified and the totalPercentage is zero.
The contract will proceed to transfer tokens according to the invalid distribution logic, leading to financial losses and potentially transferring tokens to unintended addresses.
Output :
COMMISSION_FEE is 500. If the attacker's call is successful, the output:

Distributed event emitted with invalid distribution data:
- Token: tokenAddress
- Winners: []
- Percentages: []
- Data: maliciousData

Tools Used

Manual review

Recommendations

check at the _distribute function to ensure that the winners array is not empty
As this

require(winners.length > 0, "No winners specified");

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.