Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: low
Valid

`LLMOracleCoordinator::request` lacks a check for non-empty `task.input`, making `assertValidNonce` easier to pass due to reduced uniqueness

Summary

In LLMOracleCoordinator::request, there is no check to ensure task.input is non-empty. This allows users to leave task.input empty, making it easier to pass the assertValidNonce function.

Vulnerability Details

As given in the NatSpec the Input should be non-empty.

In the LLMOracleCoordinator::request function, any requester can leave task.value empty, which makes passing assertValidNonce easier. Since task.value is part of the message composition, an empty value reduces its uniqueness, lowering the nonce validation difficulty.

function assertValidNonce(uint256 taskId, TaskRequest storage task, uint256 nonce) internal view {
bytes memory message = abi.encodePacked(taskId, task.input, task.requester, msg.sender, nonce);
if (uint256(keccak256(message)) > type(uint256).max >> uint256(task.parameters.difficulty)) {
revert InvalidNonce(taskId, nonce);
}
}
/// @notice Request LLM generation.
@> /// @dev Input must be non-empty.
/// @dev Reverts if contract has not enough allowance for the fee.
/// @dev Reverts if difficulty is out of range.
/// @param protocol The protocol string, should be a short 32-byte string (e.g., "dria/1.0.0").
/// @param input The input data for the LLM generation.
/// @param parameters The task parameters
/// @return task id
function request(
bytes32 protocol,
bytes memory input,
bytes memory models,
LLMOracleTaskParameters calldata parameters
) public onlyValidParameters(parameters) returns (uint256) {
(uint256 totalfee, uint256 generatorFee, uint256 validatorFee) = getFee(parameters);
// check allowance requirements
//e all good- why this allowncce check without approve -> approved by buyer funciton called form there
uint256 allowance = feeToken.allowance(msg.sender, address(this));
if (allowance < totalfee) {
revert InsufficientFees(allowance, totalfee);
}
// ensure there is enough balance
uint256 balance = feeToken.balanceOf(msg.sender);
if (balance < totalfee) {
revert InsufficientFees(balance, totalfee);
}
// transfer tokens
feeToken.transferFrom(msg.sender, address(this), totalfee);
// increment the task id for later tasks & emit task request event
uint256 taskId = nextTaskId;
unchecked {
++nextTaskId;
}
emit Request(taskId, msg.sender, protocol);
// push request & emit status update for the task
requests[taskId] = TaskRequest({
requester: msg.sender,
protocol: protocol,
input: input,
parameters: parameters,
status: TaskStatus.PendingGeneration,
generatorFee: generatorFee,
validatorFee: validatorFee,
platformFee: platformFee,
models: models
});
emit StatusUpdate(taskId, protocol, TaskStatus.None, TaskStatus.PendingGeneration);
return taskId;
}

Impact

A malicious user who realizes they can leave task.input empty could exploit this to pass the assertValidNonce check more easily, introducing a bias in the system. Without task.input contributing to the message hash, the uniqueness of each request is reduced, lowering the computational effort needed to find a valid nonce and weakening the intended security.

Since the message in assertValidNonce relies on task.input for uniquiness, an empty task.input simplifies the hash and reduces the difficulty of the nonce check. This allows users to bypass validation with minimal effort, undermining nonce integrity. Adding a requirement for a non-empty task.input would help ensure the expected security level of the validation.

Tools Used

Manual Review

Recommendations

/// @notice Request LLM generation.
@> /// @dev Input must be non-empty.
/// @dev Reverts if contract has not enough allowance for the fee.
/// @dev Reverts if difficulty is out of range.
/// @param protocol The protocol string, should be a short 32-byte string (e.g., "dria/1.0.0").
/// @param input The input data for the LLM generation.
/// @param parameters The task parameters
/// @return task id
function request(
bytes32 protocol,
bytes memory input,
bytes memory models,
LLMOracleTaskParameters calldata parameters
) public onlyValidParameters(parameters) returns (uint256) {
(uint256 totalfee, uint256 generatorFee, uint256 validatorFee) = getFee(parameters);
+ // ensure the input parameter is not empty.
+ require(input.length != 0, "invalid input");
// check allowance requirements
//e all good- why this allowncce check without approve -> approved by buyer funciton called form there
uint256 allowance = feeToken.allowance(msg.sender, address(this));
if (allowance < totalfee) {
revert InsufficientFees(allowance, totalfee);
}
// ensure there is enough balance
uint256 balance = feeToken.balanceOf(msg.sender);
if (balance < totalfee) {
revert InsufficientFees(balance, totalfee);
}
// transfer tokens
feeToken.transferFrom(msg.sender, address(this), totalfee);
// increment the task id for later tasks & emit task request event
uint256 taskId = nextTaskId;
unchecked {
++nextTaskId;
}
emit Request(taskId, msg.sender, protocol);
// push request & emit status update for the task
requests[taskId] = TaskRequest({
requester: msg.sender,
protocol: protocol,
input: input,
parameters: parameters,
status: TaskStatus.PendingGeneration,
generatorFee: generatorFee,
validatorFee: validatorFee,
platformFee: platformFee,
models: models
});
emit StatusUpdate(taskId, protocol, TaskStatus.None, TaskStatus.PendingGeneration);
return taskId;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

No validation of input and models in `request` function

Appeal created

sovaslava Auditor
7 months ago
inallhonesty Lead Judge
7 months ago
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

No validation of input and models in `request` function

Support

FAQs

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