Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: high
Invalid

LLMOracleCoordinator.sol funds can be completely drained

Summary

Since the numGenerationscan be equal to 0, the request will never be completed. And any oracle generator can keep on responding to retrieve the generatorFees (Note that the request sender will give 0 fees as numGenerations == 0, thus other users money will be stolen in the attack mentioned below).

Vulnerability Details

Assume the following scenario:
1. Bob creates a request with the LLMOracleTastParameters as :

difficulty = 1,
numGenerations = 0,
numValidations = 0

2.Thus the fees Bob has to pay will be calculated will be as follows:
(uint256 totalfee, uint256 generatorFee, uint256 validatorFee) = getFee(parameters);

function getFee(LLMOracleTaskParameters calldata parameters)
public
view
returns (uint256 totalFee, uint256 generatorFee, uint256 validatorFee)
{
uint256 diff = (2 << uint256(parameters.difficulty));
generatorFee = diff * generationFee;
validatorFee = diff * validationFee;
totalFee =
platformFee + (parameters.numGenerations * (generatorFee + (parameters.numValidations * validatorFee)));
}

(https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/llm/LLMOracleManager.sol#L110-L120)

Since the numGenerations == 0, Bob will only have to pay the platformFeefor this request.

3.Thus Bob has created a task by paying just the platformFee.
4. Now any oracle can respondfor this task.
5. When such an oracle responds to this, since the numValidations == 0, their allowance is increased and they can withdraw the amount.
6. But the issue occurs, because during the first response, the response[taskId]array is pushed with the response the oracle provided. Making the responses[taskId].length = 1

TaskResponse memory response =
TaskResponse({responder: msg.sender, nonce: nonce, output: output, metadata: metadata, score: 0});
responses[taskId].push(response);
// emit response events
emit Response(taskId, msg.sender);
// send rewards to the generator if there is no validation
if (task.parameters.numValidations == 0) {
_increaseAllowance(msg.sender, task.generatorFee);
}
bool isCompleted = responses[taskId].length == uint256(task.parameters.numGenerations); // => Bug is here

(https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/llm/LLMOracleCoordinator.sol#L225-L238)

7. Therefore when the code tries to check if the generations are completed using isCompleted, it will give as false.
Since the responses[taskId].length = 1 and the numGenerations = 0. Which makes the isCompleted = false.
So eventhough only 0 generations were asked, an infinite number of generations can be done on such a request.(since responses[taskId].lengthwill only keep on increasing with more responses, while numGenerationsremains 0). Each generation will cost the generatorFeewhich was not deposited by Bob. So an innocent request maker Alice's funds will be taken by Bob.

Thus Bob has deposited only the platformFeebut can withdraw an infinite amount of tokens from the contract. (Using different Oracle generator each time (which can be created easily by staking and unstaking the amount)). Making the likelihood of such an attack high as it is highly incentivized and pretty easy to perform.


Impact

Draining of the protocol coupled with stealing tokens from other users can be done. Which is a very high impact.

Tools Used

Manual Review

Recommendations

Make sure that the numGenerations != 0. This will stop this attack path.

Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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