DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: medium
Valid

`SiloFacet::transferDeposit` does not verify if amount is 0, leading to full withdrawal DoS for any recipient

Description

The SiloFacet::transferDeposit function allows any user to transfer their deposit to another user. However, the function does not check the amount being transfered. Worse still, if this amount is 0, it repeatedly pushes the same depositId into s.accts[account].depositIdList[token]:

function addDepositToAccount(
...
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 depositId = LibBytes.packAddressAndStem(token, stem);
// add a depositId to an account's depositList, if there is not an existing deposit.
@> if (s.accts[account].deposits[depositId].amount == 0) {
@> s.accts[account].depositIdList[token].push(depositId);
}
...
}

A user can call SiloFacet::transferDeposit with an amount of 0 and any recipient multiple times to inflate the recipient's array length, causing a DoS for all functions that use s.accts[account].depositIdList[token] in memory (due to excessive gas costs for memory) or in a loop (due to excessive gas costs for executing instructions).

The real issue is that this variable is used in critical parts of the code (tree representing where functions are called):

- SiloGettersFacet::getTokenDepositIdsForAccount - Full DoS
- SiloGettersFacet::getTokenDepositsForAccount - Full DoS
- SiloGettersFacet::getDepositsForAccount - Full DoS
- LibTokenSilo::findDepositIdForAccount - Full DoS
- LibTokenSilo::removeDepositIDfromAccountList - Full DoS
- LibTokenSilo::removeDepositFromAccount - DoS if all assets of a token is removed/withdraw/transfer
- LibConvert::_withdrawTokens - DoS if all assets of a token is removed/withdraw/transfer
- PipelineConvertFacet::pipelineConvert - DoS if all assets of a token is removed/withdraw/transfer
- ConvertFacet::convert - DoS if all assets of a token is removed/withdraw/transfer
- LibSilo::_removeDepositFromAccount - DoS if all assets of a token is removed/withdraw/transfer
- TokenSilo::_withdrawDeposit - DoS if all assets of a token is removed/withdraw/transfer
- SiloFacet::withdrawDeposit - DoS if all assets of a token is removed/withdraw/transfer
- TokenSilo::_transferDeposit - DoS if all assets of a token is removed/withdraw/transfer
- SiloFacet::transferDeposit - DoS if all assets of a token is removed/withdraw/transfer
- TokenSilo::_transferDeposits - DoS if all assets of a token is removed/withdraw/transfer
- SiloFacet::transferDeposits - DoS if all assets of a token is removed/withdraw/transfer
- LibSilo::_removeDepositsFromAccount - DoS if all assets of a token is removed/withdraw/transfer
- TokenSilo::_withdrawDeposits - DoS if all assets of a token is removed/withdraw/transfer
- SiloFacet::withdrawDeposits - DoS if all assets of a token is removed/withdraw/transfer
- EnrootFacet::enrootDeposit - DoS if all assets of a token is removed/withdraw/transfer

This bug only causes a full withdrawal/transfer/removal DoS because LibTokenSilo::removeDepositIDfromAccountList is called in LibTokenSilo::removeDepositFromAccount with this condition:

// Full remove
if (crateAmount > 0) {
delete s.accts[account].deposits[depositId];
removeDepositIDfromAccountList(account, token, depositId);
}

Risk

Likelyhood: High

  • Before the migration, executing this attack will be costly, but once the migration is completed, it can be executed on anyone at a very low cost.

Impact: Medium

  • Key functions will revert if a user attempts to move/withdraw the whole amount of a token.

  • Impossible to withdraw/transfer all the funds of a token: dust will be stuck in the contract.

Recommended Mitigation

Before pushing any new ID into the depositIdList array, check if the amount is 0.

function addDepositToAccount(
...
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 depositId = LibBytes.packAddressAndStem(token, stem);
// add a depositId to an account's depositList, if there is not an existing deposit.
- if (s.accts[account].deposits[depositId].amount == 0) {
+ if (s.accts[account].deposits[depositId].amount == 0 && amount > 0) {
s.accts[account].depositIdList[token].push(depositId);
}
...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Zero transfers (or 1 wei) and deposits in Silo corrupt array `depositIdList`

Support

FAQs

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