codehawks-2023-07-codehawks-escrow-contract-m04

[M-04] Fixed i_arbiterFee can prevent payment

보고서

Summary

사용하는 토큰이 rebase 토큰인 경우 예치해둔 토큰수가 리베이스 되어 적어질 수 있다. 수수료를 설정할 때 고정된 토큰 개수로 설정하는데, 리베이스 되어 예치된 토큰 수보다 수수료가 커지면 트랜잭션이 revert 되어 토큰 분배가 불가하다.

Keyword

rebase token, erc20, asset lock, business logic vul

Vulnerability

resolveDispute 함수는 분쟁이 일어난 경우 중재자가 호출하여 토큰을 분배해주는 함수이다. 구매자에게 환불해주고, 중개자는 수수료를 받으며, 남은 토큰은 판매자에게 전달한다.

  • buyerAward: 중재자가 설정한, 구매자에게 환불해줄 토큰 개수
  • i_arbiterFee: Escrow 컨트랙트 생성자에서 설정된, 중재자에게 수수료로 줄 토큰 개수
  • tokenBalance: Escrow 컨트랙트에 예치된 토큰 개수

uint256 totalFee = buyerAward + i_arbiterFee; 로, 중재자가 구매자에게 환불해줄 토큰 양인 buyerAward와 수수료로 받을 i_arbiterFee 개수를 더하고, 이것이 totalFee > tokenBalance 즉 예치된 토큰 양보다 많은지 확인한다. 필요한 토큰이 더 많다면 resolveDispute 함수는 revert 되어 분배가 불가하다.

    function resolveDispute(uint256 buyerAward) external onlyArbiter nonReentrant inState(State.Disputed) {
        uint256 tokenBalance = i_tokenContract.balanceOf(address(this));
        uint256 totalFee = buyerAward + i_arbiterFee; // Reverts on overflow
        if (totalFee > tokenBalance) {
            revert Escrow__TotalFeeExceedsBalance(tokenBalance, totalFee);
        }
 
        s_state = State.Resolved;
        emit Resolved(i_buyer, i_seller);
 
        if (buyerAward > 0) {
            i_tokenContract.safeTransfer(i_buyer, buyerAward);
        }
        if (i_arbiterFee > 0) {
            i_tokenContract.safeTransfer(i_arbiter, i_arbiterFee);
        }
        tokenBalance = i_tokenContract.balanceOf(address(this));
        if (tokenBalance > 0) {
            i_tokenContract.safeTransfer(i_seller, tokenBalance);
        }
    }

이 토큰이 rebase 토큰이라면 상황에 따라 토큰 양이 자동으로 조절된다. rebase 토큰은 일반적으로 주소 별 지분 정보를 저장해두고, balanceOf로 토큰 개수 조회 시 지분과 계산 로직에 의해 즉석으로 계산해 리턴한다. 즉, 토큰 개수가 유동적이다.

따라서 리베이싱되어 토큰의 양이 줄어든 경우, 고정된 토큰 개수로 설정된 i_arbiterFee보다 양이 적어진다면 분쟁 처리가 불가하고 토큰 분배가 불가하다.

Impact

rebase 토큰이 컨트랙트에 잠길 수 있다. 토큰 분배가 불가하다.

Mitigation

i_arbiterFee를 고정된 토큰 개수로 지정하는 대신 백분율로 지정한다.

Memo

프로젝트에서 특정 토큰만 다루겠다고 명시하지 않는다면 rebase 토큰에 대한 지원이 되는지 확인하자. 여러 ERC20을 포괄적으로 다루는 프로젝트에서는 이를 고려해야 한다.

약간의 논쟁이 있었다. 심판은 토큰을 더 예치하면 자금이 묶이는 문제를 해결 가능하며, 이를 해결하기 위한 토큰은 buyerAward를 조정하여 환급하면 되므로 Low로 내려야 한다고 주장했다. 또한 rebase token이 사용되어 요건이 충족될 가능성은 매우 낮고 자산은 위험하지 않다고 했다.

구매자와 판매자가 rebase 토큰으로 거래를 하기로 합의를 먼저 하고 토큰을 이용할 것이고, 중재자 수수료는 일반적으로 1020%가 될 것이다. 이 취약점이 트리거되려면 토큰 가치가 8090% 감소해야 할 것이다. 그런데 애초에 이런 변동성이 큰 토큰은 거래에 이용하지 않을 것이다. 또한, 이런 상황이 발생했다면 남은 가치는 중재자 수수료도 감당을 못할 수준이 된 것이다. totalFee > tokenBalance 의 상황에 도달한다면 토큰을 받아도 어차피 별 가치가 없다. 분쟁을 거는 동기는 구매자가 판매자에게 불만족하여 자신의 자금 일부를 돌려받거나, 판매자가 구매자가 지불하지 않는 대금을 받고싶을 때이다. 하지만 토큰의 가치가 없다면 분쟁의 시작조차 하지 않을 것이다.

하지만 escalation 기간에 몇가지 논의를 하고 Medium으로 유지했다. 취약점의 영향력는 높고, 가능성은 낮기 때문에 Medium으로 결론냈다. 다음은 제시된 몇가지 근거이다.

  • 프로토콜은 임의의 토큰을 사용할 수 있게 구현되어 있으며, rebase 토큰은 꽤나 금액이 자주 변동된다.
  • 누가 필요한 양만큼 토큰을 추가 예치할 것인가? 구매자? 중재자? 중재자는 수수료보다 많은 양의 토큰을 투자하고 싶지 않을 것이다. 중재자가 판매자에게 모든 토큰을 주려고 한다면, 구매자가 토큰을 처리하기 위해 추가 토큰을 넣지 않을 것이다. 이를 정하기 어렵다.
  • codehawks docs를 보면 프로토콜 기능 또는 가용성 방해는 Medium에 속한다.
  • rebase 토큰의 양은 변경될 수 있지만, 그 가치는 동일하다. 따라서 Escrow에 추가적으로 토큰을 넣는다는 것은 가치를 추가한다는 의미와 마찬가지다. 이는 누군가 자산을 잃는다는 것을 의미한다.
  • rebase 토큰이 업그레이더블이어서, V1 시점에 거래를 시작했는데 V2가 되어 가격이 극심하게 변동되는 상황도 있지 않겠나?
  • 단순히 rebase 토큰을 사용함으로 인해 프로토콜 기능이 중단될 수 있다는 사실은 Medium으로 봐야한다.
  • 추가적인 작업으로 토큰을 구제할 수 있더라도 자금이 lock될 수 있다는 것 자체가 취약한 것으로 간주된다. (업그레이더블한 컨트랙트에 자금이 잠기면 업그레이드 해서 꺼내면 되는데, 오딧팅할때 그런 해결 방법이 있으므로 심각도를 낮추지는 않음)
  • 예를 들어 OHM 리베이스 토큰을 보라. 굉장히 변동이 컸다.

tags: bughunting, codehawks, smart contract, solidity, rebase token, erc20, asset lock, business-logic-vul, severity medium