Description
The _fulfillWeatherUpdate
function accepts and stores weather data without validating if it falls within the valid enum range.
Risk
Severity: High
Likelihood: Medium
Summary
Weather data from external sources is stored without validation, potentially corrupting NFT metadata with invalid weather states.
Vulnerability Details
Root Cause:
uint8 weather = abi.decode(response, (uint8));
s_tokenIdToWeather[tokenId] = Weather(weather);
Initial State:
NFT exists with valid weather state
Weather update requested via Chainlink
Attack Scenario:
Oracle returns corrupted/malicious data
Invalid weather value (e.g., 255) received
Value blindly cast to Weather enum
Invalid state stored permanently
Proof of Concept
contract WeatherNftTest {
function testInvalidWeatherData() public {
uint256 tokenId = 1;
bytes32 requestId = bytes32(uint256(1));
bytes memory invalidResponse = abi.encode(uint8(255));
vm.prank(address(oracle));
weatherNft.fulfillRequest(
requestId,
invalidResponse,
""
);
Weather storedWeather = weatherNft.s_tokenIdToWeather(tokenId);
assertEq(uint8(storedWeather), 255);
}
}
Impact
-
Corrupted NFT metadata
-
Invalid weather states stored
-
System reliability compromised
-
Potential system crashes when accessing invalid enum values
-
Loss of NFT value
Tools Used
Recommendations
Add weather data validation:
contract WeatherNft {
error WeatherNft__InvalidWeatherValue(uint8 weather);
function isValidWeather(uint8 weather) internal pure returns (bool) {
return weather <= uint8(Weather.SNOW);
}
function _fulfillWeatherUpdate(bytes32 requestId, bytes memory response, bytes memory err) internal {
if (response.length == 0 || err.length > 0) {
emit WeatherUpdateFailed(requestId, err);
return;
}
uint256 tokenId = s_funcReqIdToTokenIdUpdate[requestId];
uint8 weather = abi.decode(response, (uint8));
if (!isValidWeather(weather)) {
emit InvalidWeatherReceived(requestId, weather);
revert WeatherNft__InvalidWeatherValue(weather);
}
s_weatherNftInfo[tokenId].lastFulfilledAt = block.timestamp;
s_tokenIdToWeather[tokenId] = Weather(weather);
emit NftWeatherUpdated(tokenId, Weather(weather));
}
}
Alternative Implementation with Fallback:
contract WeatherNft {
Weather public constant DEFAULT_WEATHER = Weather.SUNNY;
function _handleInvalidWeather(uint256 tokenId, uint8 invalidWeather) internal {
emit InvalidWeatherReceived(tokenId, invalidWeather);
Weather currentWeather = s_tokenIdToWeather[tokenId];
if (currentWeather == Weather(0)) {
s_tokenIdToWeather[tokenId] = DEFAULT_WEATHER;
}
}
}