sherlock-2023-10-looksrare-m04
[M-04] fulfillRandomWords() could revert under certain circumstances
Summary
fulfillRandomWords 가 Chainlink VRF의 최대 가스 한도보다 많은 양의 가스를 요구한다면 revert 된다. 이로 인해 VRF 서비스와의 통합이 깨지고, 프로토콜이 더이상 동작하지 않게 된다.
Keyword
chainlink, vrf, gas limit, dos
Vulnerability
Chainlink VRF 문서를 보면, VRF coordinator의 최대 가스 한도는 2,500,000이다.
또한 코드상에서도 랜덤값을 요청할 때 가스 리밋을 2,500,000 로 하드코딩하여 설정하였다.
function _requestForRandomness() private {
uint256 requestId = VRF_COORDINATOR.requestRandomWords({
keyHash: KEY_HASH,
subId: SUBSCRIPTION_ID,
minimumRequestConfirmations: uint16(3),
callbackGasLimit: uint32(2_500_000),
numWords: uint32(1)
});
즉, fulfillRandomWords()가 사용할 수 있는 최대 가스는 2,500,000이며 이를 초과하면 revert 된다.
다음은 fulfillRandomWords에서 사용 가스가 2,500,000을 초과할 수 있음을 보이는 PoC이다. AGENTS_TO_WOUND_PER_ROUND_IN_BASIS_POINTS 값을 개발자가 예정했던 값보다 늘려(20→30) fulfillRandomWords() 에서 개발자가 예상한 것보다 더 많은 가스를 사용하게 상황을 만들었다.
이 테스트 코드를 실행해보면 소모한 가스가 2,500,000를 초과하므로, fulfillRandomWords()는 상황에 따라 실패할 수 있다. Chainlink VRF 문서에 따르면 fulfillRandomWords 함수가 한 번 revert 되면 VRF 서비스가 다시는 fulfillRandomWords를 호출해주지 않을 수 있다며, fulfillRandomWords 함수가 절대로 실패하지 않도록 하라 명시하였다.
function test_fulfillRandomWords_revert() public {
_startGameAndDrawOneRound();
_drawXRounds(48);
uint256 counter = 0;
uint256[] memory wa = new uint256[](30);
uint256 totalCost = 0;
for (uint256 j=2; j <= 6; j++)
{
(uint256[] memory woundedAgentIds, ) = infiltration.getRoundInfo({roundId: j});
uint256[] memory costs = new uint256[](woundedAgentIds.length);
for (uint256 i; i < woundedAgentIds.length; i++) {
costs[i] = HEAL_BASE_COST;
wa[counter] = woundedAgentIds[i];
counter++;
if(counter > 29) break;
}
if(counter > 29) break;
}
totalCost = HEAL_BASE_COST * wa.length;
looks.mint(user1, totalCost);
vm.startPrank(user1);
_grantLooksApprovals();
looks.approve(TRANSFER_MANAGER, totalCost);
infiltration.heal(wa);
vm.stopPrank();
_drawXRounds(1);
}Impact
fulfillRandomWords가 revert 되어 VRF가 더이상 fulfillRandomWords를 호출해주지 않으면 프로토콜이 중단된다.
Mitigation
AGENTS_TO_WOUND_PER_ROUND_IN_BASIS_POI가 충분히 작게 설정되도록 제한을 둔다.fulfillRandomWords에서는 랜덤값을 단순히 저장만 한 후 Chainlink VRF 문서에서 제안하는 것과 같이 별도의 컨트랙트콜에서 복잡한 후속작업을 처리한다.
Memo
처음에는 Invalid로 취급되었지만 에스컬레이션 기간에 협상하여 결국 끌어올렸다.
컨테스트 설명의 Accepted risks 섹션에 이에 대해 언급하지 않았고, 네트워크 상황에 따라 우연히 일어나는 문제가 아니라 파라미터에 의해 확정적으로 일어날 수 있는 문제라며, 이슈의 심각성을 반복해 주장했다.
tags: bughunting, looksrare, smart contract, solidity, gamefi, nft, chainlink, chainlink vrf, gas limit, dos, severity medium