Dria

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

[H-1] unregister::LLMOracleRegistry.sol is opened to front running (race condition)

Description

function unregister(LLMOracleKind kind) public returns (uint256 amount) {
amount = registrations[msg.sender][kind];
// ensure the user is registered
if (amount == 0) {
revert NotRegistered(msg.sender);
}
// unregister the user
delete registrations[msg.sender][kind];
emit Unregistered(msg.sender, kind);
// approve its stake back
token.approve(msg.sender, token.allowance(address(this), msg.sender) + amount);
}

Exploitation Walkthrough

  1. Initial Setup:

    • User A has registered with 100 tokens.

    • The registrations mapping has an entry: registrations[User A] = 100.

    • Assume the initial allowance is 0.

  2. Unregister Call:

    • User A calls unregister().

    • Inside the unregister function, the current allowance (allowance) is retrieved, and then the function increments it by the registered amount (100 tokens).

  3. Race Condition:

    • Suppose a malicious actor (or a bot controlled by User A) detects that unregister() has been called. It quickly sends a transaction that uses up this allowance before the second approve call completes.

    • This allows the malicious actor to drain the tokens incrementally. If the contract does not reset the allowance to zero between calls, this action can be repeated, exploiting the incremented allowance.

  4. Result:

    • By exploiting the race condition, User A (or an attacker) could withdraw more than 100 tokens by calling other transactions in between the approval increments.

    Impact

    . Unauthorized Token Transfers (Double-Spending)

    .Loss of Contract Funds

Proof of Concepts

// Assume msg.sender is User A
function exploitAllowance() public {
// Step 1: User A calls unregister() to increment allowance
contract.unregister();
// Step 2: In parallel, while unregister() is still processing
// (before the allowance is finalized), User A sends another
// transaction to transfer tokens up to the current allowance.
token.transferFrom(address(this), attackerAddress, amount);
}

Recommended mitigation

This approach ensures that the allowance is safely reset before being reassigned, which makes the function resistant to race condition exploits.

function unregister() public returns (uint256 amount) {
amount = registrations[msg.sender][kind];
if (amount == 0) {
revert NotRegistered(msg.sender);
}
// unregister the user
delete registrations[msg.sender][kind];
emit Unregistered(msg.sender, kind);
// Set allowance to zero, then set to the correct amount
token.approve(msg.sender, 0); // Prevent race condition
token.approve(msg.sender, amount);
return amount;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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