code4rena-2023-09-centrifuge-m03

[M-03] Cached DOMAIN_SEPARATOR is incorrect for tranche tokens potentially breaking permit integrations

보고서

Summary

ERC20 이름이 변경되더라도 미리 계산해 캐싱한 DOMAIN_SEPARATOR를 업데이트 하지 않는다. permit이 실패할 수 있다.

Keyword

erc2612, erc712, erc20, signature, wrong state, logic flaw

Vulnerability

새 트랜치 토큰이 배포되면 생성자에서 _DOMAIN_SEPARATOR를 계산하여 캐싱한다. domain seperator는 EIP712에 ERC20의 이름, 버전(EIP712 서명 버전), 체인 ID, 컨트랙트 주소를 해싱한 값이다.

ERC20의 이름(name 변수)은 생성자에서 초기화되지 않으므로 빈 문자열이 들어있다.

    bytes32 private immutable _DOMAIN_SEPARATOR;
 
    constructor(uint8 decimals_) {
        ...
        deploymentChainId = block.chainid;
        _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);
    }
 
    function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
        return keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes(name)),
                keccak256(bytes(version)),
                chainId,
                address(this)
            )
        );
    }    

트랜치 토큰의 이름과 심볼은 이후 file 함수를 호출하여 설정된다. 이로 인해 _DOMAIN_SEPARATOR의 값은 올바르지 않게 된다. ERC20의 이름이 바뀌었기 때문에 해시값을 다시 구하는 게 맞다.

    function newTrancheToken(
        uint64 poolId,
        bytes16 trancheId,
        string memory name,
        string memory symbol,
        uint8 decimals,
        address[] calldata trancheTokenWards,
        address[] calldata restrictionManagerWards
    ) public auth returns (address) {
        ...
        TrancheToken token = new TrancheToken{salt: salt}(decimals);
 
        token.file("name", name);
        ...
    }

실제 ERC20 이름을 이용하여 서명한 유저는 정상적으로 permit할 수 없을 것이다.

또한 updateTrancheTokenMetadata를 이용해 트랜치 토큰의 이름과 심볼을 변경할 수도 있다. 이름을 변경한다면 동일하게 _DOMAIN_SEPARATOR 값이 틀어질 것이다.

    function updateTrancheTokenMetadata(
        uint64 poolId,
        bytes16 trancheId,
        string memory tokenName,
        string memory tokenSymbol
    ) public onlyGateway {
        TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId));
        require(address(trancheToken) != address(0), "PoolManager/unknown-token");
 
        trancheToken.file("name", tokenName);
        trancheToken.file("symbol", tokenSymbol);
    }

Impact

DOMAIN_SEPARATOR가 실제와 맞지 않아 트랜치 토큰의 permit 함수가 실패할 수 있다.

Mitigation

캐시된 _DOMAIN_SEPARATOR를 계산하기 전에 이름을 초기화하거나, 이름이 변경되면 _DOMAIN_SEPARATOR를 재계산한다.


tags: bughunting, centrifuge, smart contract, solidity, erc2612, erc712, erc20, signature, wrong state, logic flaw, severity medium