code4rena-2023-06-angle-protocol-m03

[M-03] Read-only reentrancy is possible

보고서

Summary

재진입으로 인해 담보가 과도하게 잡힌 것으로 판단하게 하여 이자를 발행할 수 있다.

Keyword

reentrancy, stablecoin

Vulnerability

redeem과 swap 관련 로직에 재진입 가드가 없다. 따라서 일반적인 재진입 공격이 가능하다.

또한, 읽기 전용 함수에 대해서도 재진입 공격이 가능하며(재진입으로 인해 view 함수의 값이 깨진다는 의미), 이로 인해 agToken이 잘못 mint될 수 있다.

  1. collatRatio가 BASE_9(100%) 라고 가정하자. Alice가 담보 토큰을 agToken으로 swap하려고 한다.
  2. _swap 함수에서, 담보 토큰을 먼저 입금하고 agToken을 mint 한다. 담보 토큰이 ERC777인 경우 훅이 실행되고 재진입이 가능하다.
        if (mint) {
            uint128 changeAmount = (amountOut.mulDiv(BASE_27, ts.normalizer, Math.Rounding.Up)).toUint128();
            // The amount of stablecoins issued from a collateral are not stored as absolute variables, but
            // as variables normalized by a `normalizer`
            collatInfo.normalizedStables += uint216(changeAmount);
            ts.normalizedStables += changeAmount;
            if (permitData.length > 0) {
                PERMIT_2.functionCall(permitData);
            } else if (collatInfo.isManaged > 0)
                IERC20(tokenIn).safeTransferFrom(
                    msg.sender,
                    LibManager.transferRecipient(collatInfo.managerData.config),
                    amountIn
                );
            else IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); //@audit reentrancy
            if (collatInfo.isManaged > 0) {
                LibManager.invest(amountIn, collatInfo.managerData.config);
            }
            IAgToken(tokenOut).mint(to, amountOut);
        }
  3. Alice가 훅을 통해 SavingsVest.accrue를 호출한다. 이 함수는 과도하게 담보가 존재하는 경우 agToken을 민팅하는 함수이다. 이 때, agToken이 아직 mint되지 않았기 때문에 _transmuter.getCollateralRatio()는 잘못된 비율을 리턴할 것이다.
        function accrue() external returns (uint256 minted) {
            if (block.timestamp - lastUpdate < updateDelay && !accessControlManager.isGovernorOrGuardian(msg.sender))
                revert NotAllowed();
            ITransmuter _transmuter = transmuter;
            IAgToken _agToken = IAgToken(asset());
            (uint64 collatRatio, uint256 stablecoinsIssued) = _transmuter.getCollateralRatio();
            ...
        }
  4. getCollateralRatio는 실제보다 큰 collatRatio를 리턴한다.
        if (stablecoinsIssued > 0)
            collatRatio = uint64(totalCollateralization.mulDiv(BASE_9, stablecoinsIssued, Math.Rounding.Up));
  5. SavingsVest.accrue에서 collatRatio > BASE_9 + BASE_6의 조건을 맞추면 담보가 과도하게 잡혔다고 판단한다. 이 경우 이자로 agToken을 mint해준다.
        if (collatRatio > BASE_9 + BASE_6) {
            // The surplus of profit minus a fee is distributed through this contract
            minted = (collatRatio * stablecoinsIssued) / BASE_9 - stablecoinsIssued;
            // Updating normalizer in order not to double count profits
            _transmuter.updateNormalizer(minted, true);
            uint256 surplusForProtocol = (minted * protocolSafetyFee) / BASE_9;
            address _surplusManager = surplusManager;
            _surplusManager = _surplusManager == address(0) ? address(_transmuter) : _surplusManager;
            _agToken.mint(_surplusManager, surplusForProtocol);

Impact

담보가 과도하게 잡힌 것으로 판단하게 하여 이자를 발행한다.

Mitigation

getCollateralRatio view 함수에도 재진입 가드를 추가한다.


tags: bughunting, angle protocol, smart contract, solidity, stablecoin, reentrancy, severity medium