Althea Liquid Infrastructure - DanielArmstrong's results

Liquid Infrastructure.

General Information

Platform: Code4rena

Start Date: 13/02/2024

Pot Size: $24,500 USDC

Total HM: 5

Participants: 84

Period: 6 days

Judge: 0xA5DF

Id: 331

League: ETH

Althea

Findings Distribution

Researcher Performance

Rank: 50/84

Findings: 2

Award: $32.91

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L142

Vulnerability details

Impact

Since zero addresses are inserted into holders array repeatedly, the length of holders array becomes extremely large. Thus, transfer function call of LiquidInfrastructureERC20 token will revert due to the lack of gas (DoS). And also, call of other several functions involving distributeToAllHolders function will revert due to the lack gas too (DoS).

Proof of Concept

LiquidInfrastructureERC20.sol#_beforeTokenTransfer function is the following.

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        require(!LockedForDistribution, "distribution in progress");
        if (!(to == address(0))) {
            require(
                isApprovedHolder(to),
                "receiver not approved to hold the token"
            );
        }
        if (from == address(0) || to == address(0)) {
            _beforeMintOrBurn();
        }
142:    bool exists = (this.balanceOf(to) != 0);
        if (!exists) {
            holders.push(to);
        }
    }

When _beforeTokenTransfer function is called by burn function, to = address(0) holds. Since the balance of zero address always be zero, this.balanceOf(to) = 0 and exist = false holds in L142, and so zero address is pushed into holders array. Thus, whenever burn function is called, zero addresses are pushed into holders array repeatedly.

On the other hand, the zero addresses which are pushed above may be popped in the following LiquidInfrastructureERC20.sol#_afterTokenTransfer function which is called by mint function.

    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        bool stillHolding = (this.balanceOf(from) != 0);
        if (!stillHolding) {
            for (uint i = 0; i < holders.length; i++) {
                if (holders[i] == from) {
                    // Remove the element at i by copying the last one into its place and removing the last element
174:                holders[i] = holders[holders.length - 1];
                    holders.pop();
                }
            }
        }
    }

But if holders[holders.length - 1] = from in L174, the holder moved into holders[i] (=from) will not be removed. That is, at the end of the loop, froms could be remained in holders array. If zero addresses are located at the end of the array, at most half of the zero addresses are not removed from holders array after the mint function call.

Due to the two above issues, when burn function is called many times and mint function is called extremely small times, the length of holders array will become too large to process with zero addresses. Then, transfer function call of LiquidInfrastructureERC20 token will revert due to the lack of gas (DoS). And also, call of other several functions involving distributeToAllHolders function will revert due to the lack gas too (DoS).

Tools Used

Manual Review

Modify LiquidInfrastructureERC20.sol#_beforeTokenTransfer function as follows.

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        require(!LockedForDistribution, "distribution in progress");
        if (!(to == address(0))) {
            require(
                isApprovedHolder(to),
                "receiver not approved to hold the token"
            );
        }
        if (from == address(0) || to == address(0)) {
            _beforeMintOrBurn();
        }
        bool exists = (this.balanceOf(to) != 0);
--      if (!exists) {
++      if (!exists && to != address(0)) {
            holders.push(to);
        }
    }

And modify LiquidInfrastructureERC20.sol#_afterTokenTransfer function as follows.

    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        bool stillHolding = (this.balanceOf(from) != 0);
        if (!stillHolding) {
            for (uint i = 0; i < holders.length; i++) {
                if (holders[i] == from) {
                    // Remove the element at i by copying the last one into its place and removing the last element
                    holders[i] = holders[holders.length - 1];
                    holders.pop();
++                  i--;
                }
            }
        }
    }

Assessed type

DoS

#0 - c4-pre-sort

2024-02-20T06:24:53Z

0xRobocop marked the issue as duplicate of #727

#1 - c4-pre-sort

2024-02-20T06:34:09Z

0xRobocop marked the issue as duplicate of #77

#2 - c4-judge

2024-03-04T13:06:33Z

0xA5DF marked the issue as satisfactory

#3 - c4-judge

2024-03-08T15:08:04Z

0xA5DF changed the severity to 3 (High Risk)

Awards

25.7286 USDC - $25.73

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-87

External Links

Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L444

Vulnerability details

Impact

If distributableERC20s is changed during distribution, this can lead to wrong distribution.

Proof of Concept

LiquidInfrastructureERC20.sol#setDistributableERC20s function which sets distributed tokens is as follows.

function setDistributableERC20s( address[] memory _distributableERC20s ) public onlyOwner { distributableERC20s = _distributableERC20s; }

As we can see above, there isn't any check. If this function is called during distribution, after this time unintended tokens are distributed to users. This process can be caused by front-running of malicious user.

Tools Used

Manual Review

LiquidInfrastructureERC20.sol#setDistributableERC20s function has to be modified as follows.

function setDistributableERC20s( address[] memory _distributableERC20s ) public onlyOwner { + require(!LockedForDistribution, "Still distributting"); distributableERC20s = _distributableERC20s; }

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-02-20T05:55:49Z

0xRobocop marked the issue as duplicate of #260

#1 - c4-judge

2024-03-04T15:30:19Z

0xA5DF marked the issue as satisfactory

#2 - c4-judge

2024-03-08T15:13:05Z

0xA5DF changed the severity to 3 (High Risk)

#3 - c4-judge

2024-03-08T15:26:19Z

0xA5DF changed the severity to 2 (Med Risk)

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