code4rena-2024-10-kleidi-m02

[M-02] Wrong handling of call data check indices, forcing it sometimes to revert

보고서

Summary

바이트를 파싱하여 정해진 규칙에 맞는 파라미터인지 확인할 때 [start, end) 로 범위를 지정하게 한다. 파라미터가 연속적으로 있다면 다음 파싱 바이트는 [start_next, end_next) 로 주어진다. 연속된 파라미터라면 endstart_next 가 동일해야 하는데, 이러한 상황이 발생하면 트랜잭션을 취소시킨다. 따라서 파라미터 검증 규칙을 지정할 수 없다.

Keyword

off by one error, parsing

Vulnerability

콜 파라미터를 확인할 때는 콜 파라미터의 [start, end), 즉 [start] 부터 [end-1] 인덱스를 파싱해 확인한다. 예를 들어 start가 4, end가 6이라고 하자. sliceBytes 는 총 2바이트를 파싱하며, 파싱된 바이트는 data[4],data[5] 이다.

function sliceBytes(bytes memory toSlice, uint256 start, uint256 end)
    public
    pure
    returns (bytes memory)
{
    require(
        start < toSlice.length,
        "Start index is greater than the length of the byte string"
    );
    require(
        end <= toSlice.length,
        "End index is greater than the length of the byte string"
    );
    require(start < end, "Start index not less than end index");
 
    uint256 length = end - start;
    bytes memory sliced = new bytes(length);
 
    for (uint256 i = 0; i < length; i++) {
@>      sliced[i] = toSlice[i + start];
    }
 
    return sliced;
}

따라서 파라미터가 여러개 있는 함수의 파라미터를 확인한다면 다음 인덱스의 startIndex가 이전 인덱스의 endIndex와 동일하게 설정되어야 한다.

예를 들어 function deposit(uint16, uint16) 함수를 허용하고 두 파라미터를 확인한다고 하자. 이를 확인하기 위해 콜 파라미터는 다음과 같이 파싱된다.

  • function signature: data[0]~data[3]
  • 첫번째 파라미터: data[4]~[19]
  • 두번째 파라미터: data[20]~[35]

이를 파싱하기 위해서는 다음과 같이 Index가 설정되어야 한다.

  • 첫번째 파라미터 확인을 위한 Index _calldataList[contractAddress][selector][0]
    • startIndex: 4, endIndex: 20
  • 두번째 파라미터 확인을 위한 Index _calldataList[contractAddress][selector][1]
    • startIndex: 20, endIndex: 36

하지만 Index를 등록할 때, 다음 startIndex와 이전 endIndex가 겹치게 설정할 수 없도록 제한한다. 따라서 연달아 있는 파라미터를 확인할 수 없다.

function _addCalldataCheck(
    address contractAddress,
    bytes4 selector,
    uint16 startIndex,
    uint16 endIndex,
    bytes[] memory data
) private {
    ...
    {
        bool found;
        for (uint256 i = 0; i < indexes.length; i++) {
            ...
            require(
@>              startIndex > indexes[i].endIndex
@>                  || endIndex < indexes[i].startIndex,
                "CalldataList: Partial check overlap"
            );
        }
        ...
    }
    ...
}

Impact

Hot signer 호출 파라미터를 정확하게 검증할 수 없다.

Mitigation

다음 startIndex와 이전 endIndex가 겹치게 설정될 수 있도록 한다.


tags: bughunting, kleidi, smart contract, solidity, gnosis safe, wallet, off by one error, severity medium