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) 로 주어진다. 연속된 파라미터라면 end 와 start_next 가 동일해야 하는데, 이러한 상황이 발생하면 트랜잭션을 취소시킨다. 따라서 파라미터 검증 규칙을 지정할 수 없다.
Keyword
off by one error, parsing
Vulnerability
- https://github.com/code-423n4/2024-10-kleidi/blob/ab89bcb443249e1524496b694ddb19e298dca799/src/Timelock.sol#L1119-L1123
- https://github.com/code-423n4/2024-10-kleidi/blob/ab89bcb443249e1524496b694ddb19e298dca799/src/BytesHelper.sol#L35-L67
콜 파라미터를 확인할 때는 콜 파라미터의 [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