Race Conditions Explained

Share

TL;DR

Race conditions occur when the outcome depends on timing between concurrent operations. In security, TOCTOU (time-of-check to time-of-use) bugs let attackers change state between when you check something and when you act on it. Use atomic operations, database transactions, and proper locking to prevent them.

Common Race Condition Examples

Double-Spend in Wallet Applications

Vulnerable: check-then-update pattern
app.post('/transfer', async (req, res) => {
  const { amount, toUser } = req.body;

  // Check balance
  const balance = await db.getBalance(req.user.id);
  if (balance < amount) {
    return res.status(400).json({ error: 'Insufficient funds' });
  }

  // TIME GAP HERE - attacker sends many requests simultaneously!

  // Deduct and transfer
  await db.updateBalance(req.user.id, balance - amount);
  await db.updateBalance(toUser, await db.getBalance(toUser) + amount);
});

// With $100 balance, send 50 requests for $100 each
// Many will pass the check before any deduction happens!

Coupon Code Double-Use

Same pattern: check if coupon is used, then mark it as used. With concurrent requests, multiple orders can use the same coupon.

How to Prevent Race Conditions

Safe: using database transactions
app.post('/transfer', async (req, res) => {
  const { amount, toUser } = req.body;

  await db.transaction(async (tx) => {
    // Lock the row with FOR UPDATE
    const result = await tx.query(
      'SELECT balance FROM users WHERE id = $1 FOR UPDATE',
      [req.user.id]
    );

    if (result.rows[0].balance < amount) {
      throw new Error('Insufficient funds');
    }

    // Atomic update
    await tx.query(
      'UPDATE users SET balance = balance - $1 WHERE id = $2',
      [amount, req.user.id]
    );
    await tx.query(
      'UPDATE users SET balance = balance + $1 WHERE id = $2',
      [amount, toUser]
    );
  });
});

How do I test for race conditions?

Send many concurrent requests using tools like curl with parallel or custom scripts. Look for inconsistent state after the requests complete. Burp Suite has a race condition testing feature.

Does JavaScript single-threading prevent races?

No. While JS is single-threaded, async operations can interleave. Between await calls, other requests can modify shared state. Always use atomic operations for critical sections.

Test for Race Conditions

Our scanner helps identify potential race condition vulnerabilities.

Start Free Scan
Vulnerability Guides

Race Conditions Explained