`LiquidInfrastructureERC20::distributeToAllHolders()` - Gas griefing combo DoS attack vector: Due to lack of access control and/or lack of throttling/frequency-limiting mechanisms, an attacker can repeatedly call this function manually or via mempool bots and DoS contract/protocol functionality. #728
An attacker can repeatedly call this function manually or via mempool bots (with higher gas prices if necessary) to effectively gobble up block gas limits quickly and thereby make it difficult for any other function calls to be included into current block during the ongoing attack, effectively DoS-ing any other contract/protocol functionalities for as long a the attack continues.
This attack will be more effective when the holders array length is large enough. However, due to the existing gas gobbling implementation of the distribute() function, holders.length doesn't need to be that large at all.
Should either add access control here so only owner can call this function, or implement mechanisms to throttle or limit the frequency at which this function can be called, to help mitigate this attack vector.
This should be the primary mitigation approach. Secondary measures would be to gas optimize the hell out of the distribute() function.
IMPACT:
at best would cause unnecessary gas usage and fees when normal users unsuccessfully call this function when it reverts due to out of gas errors.
at worst an attacker takes advantage of the gas guzzling nature of the function to reach block gas limit quickly and interfere with successful block inclusion of the distribute() function (or other function calls) when owner or normal user calls it directly with less holders to bypass out of gas errors.
Ideal scenario for attacker:
If the attacker repeatedly calls distributeToAllHolders() or burnAndDistribute() when the holders.length is too large for the function call to succeed before running out of gas, then the attacker could exploit this to guzzle up the block gas limit quickly, which would potentially prevent anyone from successfully calling the distribute() function directly where the holders.length value can be controlled, effectively DoS-ing critical distribution functionality of the contract, including any other function calls.
This DoS attack can keep going for as long as the attacker can maintain it, which could also DoS any attempts to change the holders.length value via onlyOwner function call approveHolder().
Since the attacker can increase their gas price to prioritize their transactions over the contract owner's or normal users' function calls, ensuring that attacker's function calls are included first in the block(s), the attacker can additionally do this:
Attacker could leverage mempool bots to monitor for protocol mitigation attempts like increasing gas price for direct distribute() calls, and then he immediately calls the gas-guzzling functions distributeToAllHolders() or burnAndDistribute() with a higher gas price to ensure his function calls get prioritized for inclusion into the block(s), potentially intensifying the impact of the gas-based DoS attack, via bots.
Alternatively, the attacker could call distribute() directly as per below explanation:
Attacker could call this function directly in quick succession with parameter numDistributions = 1 each time, which should guzzle up the block gas limit really quickly because this uses more gas overall, compared to calling either of the other two functions just once for the full holders array, but this might not be as long-lasting as the attack via the other two functions, but it could DoS contract functionality for at least one block.
Mitigation/Recommendation:
Should implement access control or some type of cooldown period to prevent this DoS attack.
Lines of code
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L161-L169 https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L178 https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L289
Vulnerability details
An attacker can repeatedly call this function manually or via mempool bots (with higher gas prices if necessary) to effectively gobble up block gas limits quickly and thereby make it difficult for any other function calls to be included into current block during the ongoing attack, effectively DoS-ing any other contract/protocol functionalities for as long a the attack continues.
This attack will be more effective when the
holders
array length is large enough. However, due to the existing gas gobbling implementation of thedistribute()
function,holders.length
doesn't need to be that large at all.Should either add access control here so only owner can call this function, or implement mechanisms to throttle or limit the frequency at which this function can be called, to help mitigate this attack vector. This should be the primary mitigation approach. Secondary measures would be to gas optimize the hell out of the
distribute()
function.IMPACT:
distribute()
function (or other function calls) when owner or normal user calls it directly with less holders to bypass out of gas errors.Ideal scenario for attacker: If the attacker repeatedly calls
distributeToAllHolders()
orburnAndDistribute()
when theholders.length
is too large for the function call to succeed before running out of gas, then the attacker could exploit this to guzzle up the block gas limit quickly, which would potentially prevent anyone from successfully calling thedistribute()
function directly where theholders.length
value can be controlled, effectively DoS-ing critical distribution functionality of the contract, including any other function calls.This DoS attack can keep going for as long as the attacker can maintain it, which could also DoS any attempts to change the
holders.length
value via onlyOwner function callapproveHolder()
.Since the attacker can increase their gas price to prioritize their transactions over the contract owner's or normal users' function calls, ensuring that attacker's function calls are included first in the block(s), the attacker can additionally do this:
Attacker could leverage mempool bots to monitor for protocol mitigation attempts like increasing gas price for direct
distribute()
calls, and then he immediately calls the gas-guzzling functionsdistributeToAllHolders()
orburnAndDistribute()
with a higher gas price to ensure his function calls get prioritized for inclusion into the block(s), potentially intensifying the impact of the gas-based DoS attack, via bots.Alternatively, the attacker could call
distribute()
directly as per below explanation:Attacker could call this function directly in quick succession with parameter
numDistributions = 1
each time, which should guzzle up the block gas limit really quickly because this uses more gas overall, compared to calling either of the other two functions just once for the fullholders
array, but this might not be as long-lasting as the attack via the other two functions, but it could DoS contract functionality for at least one block.Mitigation/Recommendation:
Assessed type
DoS