Weather Witness

First Flight #40
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Insufficient Request ID Validation in Request Fulfillment

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)) {
// Process mint request
}
else if (s_funcReqIdToTokenIdUpdate[requestId] > 0) {
// Process update
}
// No handling for invalid requestId
}

Initial State:

  1. Oracle response received

  2. Request ID doesn't match any pending request

  3. No error handling or logging occurs

Attack Scenario:

  1. Invalid/malicious request ID received

  2. Function silently fails

  3. No event emission

  4. No state cleanup

  5. Debugging becomes difficult

Proof of Concept

// Test file demonstrating the vulnerability
function testInvalidRequestId() public {
bytes32 invalidRequestId = bytes32(uint256(999));
bytes memory fakeResponse = abi.encode(uint8(1)); // Some weather data
// Call with invalid request ID
vm.prank(address(oracle));
weatherNft.fulfillRequest(
invalidRequestId,
fakeResponse,
""
);
// No way to detect failure
// No events emitted
// No state changes tracked
}

Impact

  • Silent failures

  • Lost oracle responses

  • Difficult debugging

  • Potential state inconsistencies

  • Increased operational complexity

Tools Used

  • Manual Review


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);
}
}
}
Updates

Appeal created

bube Lead Judge 12 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of ownership check in `fulfillMintRequest` function

There is no check to ensure that the caller of the `fulfillMintRequest` function is actually the owner of the `requestId`. This allows a malicious user to receive a NFT that is payed from someone else.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.