Dria

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

Score manipulation through validator collusion drains funds

Summary

The LLMOracleCoordinator's validation system is vulnerable to manipulation by colluding malicious validators. Due to the permissionless nature of validator registration and a statistical scoring mechanism that can be gamed, malicious actors can unfairly penalize honest generators while earning validation rewards.

Vulnerability Details

The vulnerability exists in the LLMOracleRegistry and LLMOracleCoordinator due to three key issues:

Permissionless validator registration:

// LLMOracleRegistry.sol
function register(LLMOracleKind kind) public {
uint256 amount = getStakeAmount(kind);
// Anyone can register as validator by just staking tokens - @audit
if (isRegistered(msg.sender, kind)) {
revert AlreadyRegistered(msg.sender);
}
token.transferFrom(msg.sender, address(this), amount);
registrations[msg.sender][kind] = amount;
}

No removal mechanism for malicious validators:

  • Once registered, validators cannot be removed even if malicious behavior is detected

  • They can continue participating as long as they maintain their stake

Exploitable score aggregation:

// LLMOracleCoordinator.sol
function finalizeValidation(uint256 taskId) private {
// Vulnerable statistical filtering
(uint256 _stddev, uint256 _mean) = Statistics.stddev(scores);
// Scores within 1 stddev are accepted
if ((score >= _mean - _stddev) && (score <= _mean + _stddev)) {
innerSum += score;
innerCount++;
// Malicious validators get paid - @audit
_increaseAllowance(validations[taskId][v_i].validator, task.validatorFee);
}
}

Impact

High. The vulnerability can result in:

  • Financial losses for honest generators who don't get paid despite good work

  • Draining of protocol funds through validator rewards to malicious actors

  • Degradation of oracle service quality

  • Loss of protocol credibility and trust

  • Wasted gas fees for honest participants

Likelihood

High. The attack:

  • Requires no special permissions or complex setup

  • Only needs sufficient funds to stake as validators

  • Can be executed by a small group of colluding validators

  • Has no detection or prevention mechanisms

  • Can be repeated indefinitely

Proof of Concept

A malicious actor can profit by controlling both validators and generators to drain funds from the protocol by:

  1. Suppressing honest generator scores

  2. Inflating their own generator's scores

  3. Collecting both generator and validator fees unfairly

Simple Attack Scenario:

// Attacker controls:
- 3 validator accounts (majority)
- 1 generator account
// When a task comes in:
1. Honest generator -> Submits good work
2. Attacker generator -> Submits bad/copied work
// Attacker's validators all collude:
- Give honest generator very low scores (10-20)
- Give attacker's generator very high scores (90-100)
Result:
- Honest generator: Gets ~15 score, receives no payment
- Attacker's generator: Gets ~95 score, receives full payment
- Attacker's validators: All receive validation fees
Profit per task = 1 generator fee + 3 validator fees

Economic Impact:

If:

  • Generator fee = 100 USDC

  • Validator fee = 50 USDC

Then attacker profits:

Per task profit = 100 + (3 * 50) = 250 USDC

The attack can be repeated on every task since there's no way to remove malicious validators once they're registered.

This kills the protocol because:

  1. Honest participants lose money and leave

  2. Protocol pays for low quality work

  3. Reputation system gets corrupted

Recommendation

Short-term fixes:

  • Implement validator permissioning:

contract LLMOracleRegistry {
mapping(address => bool) public whitelistedValidators;
modifier onlyWhitelistedValidator() {
require(whitelistedValidators[msg.sender], "Not whitelisted");
_;
}
function register(LLMOracleKind kind) public {
if (kind == LLMOracleKind.Validator) {
require(whitelistedValidators[msg.sender], "Not whitelisted");
}
// Rest of registration logic
}
}
  • Add validator removal capability:

function removeValidator(address validator) external onlyOwner {
require(isRegistered(validator, LLMOracleKind.Validator), "Not registered");
// Return stake
uint256 amount = registrations[validator][LLMOracleKind.Validator];
token.approve(validator, amount);
delete registrations[validator][LLMOracleKind.Validator];
emit ValidatorRemoved(validator);
}

Long-term improvements:

  1. Integrate with EigenLayer AVS for improved validator security

  2. Implement a reputation system for validators

  3. Add minimum score requirements and score variance checks

  4. Consider multiple rounds of validation

  5. Implement economic penalties for provably malicious behavior

Updates

Lead Judging Commences

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

Unbounded score values in `validate` function

Appeal created

0xtheblackpanther Submitter
12 months ago
inallhonesty Lead Judge
12 months ago
inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Unbounded score values in `validate` function

Support

FAQs

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