BuyerAgents' requests system can be completely broken by using a malicious contract
The expected operation mode of the system is the following: Swan operators will call BuyerAgent.oraclePurchaseRequest() or BuyerAgent.oracleStateRequest() to create a request in LLMOracleCoordinator for a given taskId, then registered Oracles in LLMOracleRegistry will respond() requests PendingGeneration or validate() requests PendingValidation.
The issue comes from the combination of two facts:
Any address can be registered as generator/validator by calling LLMOracleRegistry.register() function, as long as they pay the required stakeAmount.
LLMOracleCoordinator.assertValidNonce() can be easily bypassed no matter the difficulty, as the hash is deterministically calculated for a given taskId, making it easy for a malicious contract to respond() or validate() a request succesfully. See code in ToolsUsed section.
Because of this, a user could deploy a malicious contract that calls LLMOracleRegistry.register() to get registered in the registry and respond() and validate() all the request in the first try. As a request only accepts the same generator/validator to respond()/validate() only once, multiple instances of the malicious contract can be deployed to attack all the required generations and validations of every request. Malicious user can see the state of all existing requests, responses and validations as they are public variables.
A user is encouraged to perform this attack in order to receive the generatorFee and validatorFee that is granted to responders and validators in LLMOracleCoordinator.finalizeValidation(). The malicious contracts will use similar 'scores' values for a same request to make sure that they stay into the range with the mean and stddev of all scores values.
Attack path:
Malicious contract is deployed and calls LLMOracleRegistry.register(), paying the required amount to get registered as an Oracle (generator or validator).
Malicious contract calls getValidNonce() (coded in ToolsUsed section) with corresponding params to look for a valid nonce (no matter difficulty) for a given taskId.
Malicious contract calls respond() or validate() with the valid nonce, the function will not revert and contract is set as a generator/validator of the request. The contract will receive corresponding fee when request is completed.
Deploy multiple malicious contracts and generate and validate for all the requests. Malicious contracts will get all the fees and the system will be broken.
Malicious contracts registered in Registry will take all the generatorFees and validatorFees for respond() and validate() requests, as they look for a valid nonce before calling the mentioned functions. Other registered Oracle addresses will be left with no request to response() or validate(), and therefore they will get no fees.
Manual review, Remix testing.
This the function that makes it necessary to look for valid nonce in response() / validate():
Malicious contract will have the following function that gets valid nonce for a given taskId, basically it consists on trying all nonces until the hash is low enough to satisfy the condition. In this example parameters are manually input (as they are public), but they could be read directly from LLMOracleCoordinator. After this, then contract will call respond() or validate() with the returned nonce value.
Modify the LLMOracleRegistry.register() function to be callable by trusted addresses, to guarantee that no malicious contract is registered.
Changing the method for validating a nonce depending on the difficulty is also possible, but more complex. Take into account that true randomness would be required in this case, any parameter that is calculated deterministically will probably have the same vulnerability.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.