Description
The fulfillRequest
function lacks proper validation for unknown request IDs, potentially leading to silent failures and state inconsistencies.
Risk
Severity: Medium
Likelihood: Medium
Summary
When processing oracle responses, the contract doesn't properly validate request IDs or handle unknown requests, leading to potential state corruption.
Vulnerability Details
Root Cause:
function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override {
if (s_funcReqIdToUserMintReq[requestId].user != address(0)) {
}
else if (s_funcReqIdToTokenIdUpdate[requestId] > 0) {
}
}
Initial State:
Oracle response received
Request ID doesn't match any pending request
No error handling or logging occurs
Attack Scenario:
Invalid/malicious request ID received
Function silently fails
No event emission
No state cleanup
Debugging becomes difficult
Proof of Concept
function testInvalidRequestId() public {
bytes32 invalidRequestId = bytes32(uint256(999));
bytes memory fakeResponse = abi.encode(uint8(1));
vm.prank(address(oracle));
weatherNft.fulfillRequest(
invalidRequestId,
fakeResponse,
""
);
}
Impact
Tools Used
Recommendations
Add proper request validation and error handling:
contract WeatherNft {
error WeatherNft__InvalidRequestId(bytes32 requestId);
event RequestProcessingFailed(bytes32 indexed requestId, string reason);
function isValidRequest(bytes32 requestId) internal view returns (bool) {
return s_funcReqIdToUserMintReq[requestId].user != address(0) ||
s_funcReqIdToTokenIdUpdate[requestId] > 0;
}
function fulfillRequest(
bytes32 requestId,
bytes memory response,
bytes memory err
) internal override {
if (!isValidRequest(requestId)) {
emit RequestProcessingFailed(requestId, "Invalid request ID");
revert WeatherNft__InvalidRequestId(requestId);
}
if (s_funcReqIdToUserMintReq[requestId].user != address(0)) {
s_funcReqIdToMintFunctionReqResponse[requestId] = MintFunctionReqResponse({
response: response,
err: err
});
emit MintRequestProcessed(requestId);
}
else if (s_funcReqIdToTokenIdUpdate[requestId] > 0) {
_fulfillWeatherUpdate(requestId, response, err);
emit UpdateRequestProcessed(requestId);
}
}
}
Alternative Implementation with Request Tracking:
contract WeatherNft {
enum RequestStatus { PENDING, PROCESSED, FAILED }
mapping(bytes32 => RequestStatus) public requestStatus;
function fulfillRequest(
bytes32 requestId,
bytes memory response,
bytes memory err
) internal override {
require(requestStatus[requestId] == RequestStatus.PENDING, "Invalid request status");
bool success = false;
try this._processRequest(requestId, response, err) {
success = true;
requestStatus[requestId] = RequestStatus.PROCESSED;
} catch {
requestStatus[requestId] = RequestStatus.FAILED;
emit RequestFailed(requestId);
}
}
}