Skip to main content

TOCTOU race synthesizer

Four check-then-act race detectors. They cover the highest-impact race shapes beyond the file races the existing filesystem detector already handles. Each rule fires when the check and the act sit in the same function body with no atomic guard between them.

RuleShapeSeverity
TOCTOU-DB-001SELECT ... FROM x WHERE id = ? followed by UPDATE x SET ... WHERE id = ? with no FOR UPDATE, with_lock, version stamp, or transaction-level isolation between themHigh
TOCTOU-BALANCE-001balance >= amount check followed by balance -= amount / UPDATE wallets SET balance = balance - ? WHERE id = ? without the check folded into the same UPDATE's WHERECritical
TOCTOU-QUEUE-001SELECT * FROM jobs WHERE status = 'pending' followed by UPDATE jobs SET status = 'running' WHERE id = ? without a WHERE status = 'pending' predicateHigh
TOCTOU-IDOR-ACL-001can_access(user, resource_id) / is_owner(...) / has_permission(...) followed by a mutation on the same id without a refetch + recheck under transactionMedium

TOCTOU-BALANCE-001: the double-spend shape

This is the classic gift-card / wallet / credit-pool race:

async def pay(user_id, amount):
if balance >= amount: # check
balance -= amount # act
db.execute("UPDATE wallets SET balance = ? WHERE id = ?", (balance, user_id))

Two concurrent requests both pass the check. Both subtract the amount. The wallet goes negative by amount while the user gets two purchases.

Fix. Fold the check into the act:

async def pay(user_id, amount):
result = db.execute(
"UPDATE wallets SET balance = balance - $1 WHERE id = $2 AND balance >= $1",
(amount, user_id),
)
if result.rowcount != 1:
raise InsufficientBalance()

The database's row-level locking now guarantees the check and the act are atomic.

Pairs with

  • The filesystem check-then-act (TOCTOU) detector for the filesystem-shaped race.
  • Reverse-reach to find every entry point that can race a flagged check-then-act pair.