codehawks-2023-07-dsc-h02

[H-02] Liquidation Is Prevented Due To Strict Implementation of Liqudation Bonus

보고서

Summary

liquidate 시 10%의 보너스로 인해 담보가 100-110% 사이일 때 예치된 담보 토큰 양보다 많은 양의 토큰을 주려고 하여 언더플로우가 발생한다. 이로 인해 트랜잭션이 revert 되므로 담보 비율이 100-110% 사이일 때는 청산시킬 수 없다.

Keyword

integer underflow, bug, logic flaw

Vulnerability

담보 토큰의 가격이 떨어져서 유저의 담보가 200% 과담보 아래로 떨어지면 아무나 liquidate 함수를 호출해 자신의 DSC 토큰을 소각하며 해당 유저(이하 유저A)의 담보를 청산할 수 있다.

liquidate 함수 호출자는 소각한 DSC의 가치만큼의 담보 토큰을 얻으며 추가로 10%의 보너스 담보 토큰을 더 얻을 수 있다.

하지만 이는 유저A의 담보율이 100-110% 사이일 때는 동작하지 않을 수 있다. 유저A가 예치한 담보 내에서 청산해야 하는데, 보너스 10%까지 합쳤을 때 유저A의 담보 토큰 양보다 함수 호출자에게 줘야하는 토큰의 수가 많아질 수 있기 때문이다.

_redeemCollateral 함수의 s_collateralDeposited[from][tokenCollateralAddress] -= amountCollateral; 에서 유저A가 예치한 담보 토큰보다 많은 양의 토큰을 꺼내가려 시도하므로 언더플로우가 발생, 트랜잭션이 revert 된다.

    function liquidate(address collateral, address user, uint256 debtToCover)
        external
        moreThanZero(debtToCover)
        nonReentrant
    {
        // need to check health factor of the user
        uint256 startingUserHealthFactor = _healthFactor(user);
        if (startingUserHealthFactor >= MIN_HEALTH_FACTOR) {
            revert DSCEngine__HealthFactorOk();
        }
 
@>      uint256 tokenAmountFromDebtCovered = getTokenAmountFromUsd(collateral, debtToCover);
@>      uint256 bonusCollateral = (tokenAmountFromDebtCovered * LIQUIDATION_BONUS) / LIQUIDATION_PRECISION;
@>      uint256 totalCollateralToRedeem = tokenAmountFromDebtCovered + bonusCollateral;
@>      _redeemCollateral(user, msg.sender, collateral, totalCollateralToRedeem);
        _burnDsc(debtToCover, user, msg.sender);
 
        uint256 endingUserHealthFactor = _healthFactor(user);
        if (endingUserHealthFactor <= startingUserHealthFactor) {
            revert DSCEngine__HealthFactorNotImproved();
        }
        _revertIfHealthFactorIsBroken(msg.sender);
    }
 
    function _redeemCollateral(address from, address to, address tokenCollateralAddress, uint256 amountCollateral)
        private
    {
@>      s_collateralDeposited[from][tokenCollateralAddress] -= amountCollateral;
        emit CollateralRedeemed(from, to, tokenCollateralAddress, amountCollateral);
        bool success = IERC20(tokenCollateralAddress).transfer(to, amountCollateral);
        if (!success) {
            revert DSCEngine__TransferFailed();
        }
    }

Impact

담보 비율이 위험 범위로 떨어지더라도 담보의 완전한 청산은 피할 수 있으며, 이는 프로토콜의 안정성과 보안에 문제가 된다.

Mitigation

담보가 100-110% 사이일 때는 10%의 보너스를 주는 대신, 최대로 줄 수 있는 양인 유저A의 예치량까지만 지급하도록 제한한다.

uint256 totalDepositedCollateral = s_collateralDeposited[user][collateral];
if (tokenAmountFromDebtCovered < totalDepositedCollateral && totalCollateralToRedeem > totalDepositedCollateral) {
    totalCollateralToRedeem = totalDepositedCollateral;
}

tags: bughunting, codehawks, smart contract, solidity, integer overflow underflow, logic flaw, severity high