Course / 09 Web security / Lesson 10 of 28

Race conditions

Estimated time: 16 min reading · 12 min activity

Two requests can overlap even when each follows the same code. Concurrent work means their steps can be interleaved; it need not execute at the exact same instant. A race condition occurs when the result depends on an unsafe ordering of those steps.

A common boundary is the gap between checking a value and changing it. Atomic means the relevant operation is indivisible to competing work. A transaction groups database changes, but it needs suitable isolation or locking for the invariant being protected; merely adding transaction syntax is not a guarantee against races.

Before you begin

Estimated time
16 min reading, 12 min activity. Setup and optional practice may take longer.
Where you work
Read the original fictional requests, source fragments and state tables in a current desktop or mobile browser. The required activity needs no server, database, account or installed security tool. Do not send the printed requests to any service.
Without a lab
Use the complete printed evidence to trace the data or state on paper. Write the decision, the fact supporting it and one limit of the model, then compare with the activity rubric. This does not establish live service behavior. Reading works without JavaScript. Checking requires JavaScript and a secure browser context; saving also requires usable browser storage.

After this lesson you can:

  • Define a race as an interleaving of a check and a change.
  • Read the stale trace as two sales after two reads of the same stock.
  • Say whether B's read in the ordered trace sees A's sale.

01Example

This fresh reservation model demonstrates an atomic state change. The supplied stock timeline is a separate activity, not an observed checkout.

Initial reservation: free
Atomic rule: change free → reserved only if still free.
Worker C performs that change: succeeds.
Worker D then checks: state is reserved, so refuses.
Only the worker that changes the state obtains the reservation.

02The gap between read and write

The worked reservation model accepts only the worker that changes free to reserved. In the ordered activity rows, A writes before B reads, so B can use the updated stock. The separate stale rows supply checkpoint evidence. Trace actions in order rather than assuming that the starting stock equals the number of accepted sales.

The fitting defense is to make the check and state change one protected operation, using the datastore's concurrency features. A real implementation needs the right isolation, locks or conditional update and appropriate constraints. A rapid double-click does not prove a race: evidence needs the actual relevant ordering and resulting state.

Read a fixed timeline in which two workers both pass a check before either update, and compare it with an ordered timeline.Model. A teaching model drawn by hand. It shows the idea, not real tool output.

Read the full explanation below.

Supplied protected C/D model

The reservation starts free; no worker holds it.

Protected reservation

ReservationOriginal: freeNow: freeinitial

Worker results

Worker COriginal: not attemptedNow: not attemptedpending
Worker DOriginal: not attemptedNow: not attemptedpending

The check and state change form one protected operation in this model; no concurrent checkout is run.

The reservation starts free; no worker holds it.

Follow the explanation

  1. The reservation starts free; no worker holds it.
  2. C atomically changes free to reserved and obtains the reservation.
  3. D sees reserved and refuses; only C obtained it.
Read every state and value

1. The reservation starts free; no worker holds it.

Example
Supplied protected C/D model
Items
Value: free; Status: initial · Value: not attempted; Status: pending · Value: not attempted; Status: pending
Active Item
slot
Event Label
The reservation starts free; no worker holds it.
Visible Limit
The check and state change form one protected operation in this model; no concurrent checkout is run.

2. C atomically changes free to reserved and obtains the reservation.

Example
Supplied protected C/D model
Items
Value: reserved; Status: protected · Value: succeeds; Status: accepted · Value: not attempted; Status: pending
Active Item
slot · c
Event Label
C atomically changes free to reserved and obtains the reservation.
Visible Limit
The check and state change form one protected operation in this model; no concurrent checkout is run.

3. D sees reserved and refuses; only C obtained it.

Example
Supplied protected C/D model
Items
Value: reserved; Status: unchanged · Value: succeeds; Status: accepted · Value: refuses; Status: refused
Active Item
slot · d
Event Label
D sees reserved and refuses; only C obtained it.
Visible Limit
The check and state change form one protected operation in this model; no concurrent checkout is run.
The check and state change form one protected operation in this model; no concurrent checkout is run.

03Read B's row on the ordered trace

Read the evidence before comparing your reasoning with the activity rubric. Do not run the simulator.

Read the supplied ORDERED A/B rows without running the simulatorModel. A teaching model drawn by hand. It shows the idea, not real tool output.

Read the full explanation below.

ORDERED A/B only

A reads stock 2 before any recorded sale.

Ordered source state

Current supplied CSV rowOriginal: ordered,1,A,read,2,0Now: ordered,1,A,read,2,0source row
Stock seen in this rowOriginal: 2Now: 2source field
Sales recorded in this rowOriginal: 0Now: 0source field

Ordered workers

The check and state change form one protected operation in this model; no concurrent checkout is run.

A reads stock 2 before any recorded sale.

Follow the explanation

  1. A reads stock 2 before any recorded sale.
  2. A records a sale before B reads.
  3. B reads stock 1 after A records the sale.
  4. The final ordered row records the second sale.
Read every state and value

1. A reads stock 2 before any recorded sale.

Example
ORDERED A/B only
Items
Value: ordered,1,A,read,2,0; Status: source row · Value: 2; Status: source field · Value: 0; Status: source field
Active Item
row · seen
Event Label
A reads stock 2 before any recorded sale.
Visible Limit
The check and state change form one protected operation in this model; no concurrent checkout is run.

2. A records a sale before B reads.

Example
ORDERED A/B only
Items
Value: ordered,2,A,record sale,2,1; Status: source row · Value: 2; Status: source field · Value: 1; Status: source field
Active Item
row · sales
Event Label
A records a sale before B reads.
Visible Limit
The check and state change form one protected operation in this model; no concurrent checkout is run.

3. B reads stock 1 after A records the sale.

Example
ORDERED A/B only
Items
Value: ordered,3,B,read,1,1; Status: source row · Value: 1; Status: updated stock · Value: 1; Status: source field
Active Item
row · seen
Event Label
B reads stock 1 after A records the sale.
Visible Limit
The check and state change form one protected operation in this model; no concurrent checkout is run.

4. The final ordered row records the second sale.

Example
ORDERED A/B only
Items
Value: ordered,4,B,record sale,1,2; Status: source row · Value: 1; Status: source field · Value: 2; Status: source field
Active Item
row · sales
Event Label
The final ordered row records the second sale.
Visible Limit
The check and state change form one protected operation in this model; no concurrent checkout is run.
The check and state change form one protected operation in this model; no concurrent checkout is run.
trace,time,worker,action,stock_seen,sales_recorded
stale,1,A,read,1,0
stale,2,B,read,1,0
stale,3,A,record sale,1,1
stale,4,B,record sale,1,2
ordered,1,A,read,2,0
ordered,2,A,record sale,2,1
ordered,3,B,read,1,1
ordered,4,B,record sale,1,2

Download the timeline

"""Count recorded sales in one named trace. Does not sleep or start threads.

 The ordered trace on the card prints 2, matching two record-sale rows. The field question uses the stale story instead. The file does not start threads.
"""
import csv


def sales(path, trace):
    total = 0
    with open(path, newline="") as handle:
        for row in csv.DictReader(handle):
            if row["trace"] == trace and row["action"] == "record sale":
                total += 1
    return total


if __name__ == "__main__":
    print("ordered", sales("race-timeline.csv", "ordered"))

Download the sale counter

  1. Find the ordered row where B reads. Write stock_seen and sales_recorded on that row.
  2. Find the ordered row before it and name A's action.
  3. Check: B reads after A records a sale, and B's stock_seen is 1. B saw the updated stock. The stale trace is the other interleaving.
  4. Stop. Do not hammer a live checkout to imitate the timeline.

04Optional challenge

Describe an atomic update in one sentence of pseudocode. The rubric is: one step reads the stock and records the sale together, so the second worker cannot pass the old check. About ten minutes.

05Fresh cases for the checks

Use these fictional cases for the checks below. They have different inputs from the worked examples. Read them here or make notes; no installation or external service is needed.

Race case A: fresh ordered interleavingOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

Initial stock: 2; each successful sale consumes one unit.
Workers only check their own previously read value (>0), then later record one sale.
T1: A reads stock 2.
T2: B reads stock 2.
T3: C reads stock 2.
T4: A records sale using its earlier read.
T5: B records sale using its earlier read.
T6: C records sale using its earlier read.
No recheck or protected combined operation occurs. This is a model, not checkout evidence.

Race case B: independent protected transitionOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

Initial stock: 1. A single atomic operation checks current stock >0, then reduces it and records a sale together.
Worker D completes that operation before E begins it.
An atomic operation cannot be interleaved between its check and change in this model.

06Explain your evidence

Optional written self-check: explain one decision using the supplied case. No tool run is required.

Check your explanation: trace the actual interleaving; distinguish saved from current values; count accepted sales against starting capacity; explain which combined transition prevents stale decisions without claiming a live race was tested.

07Common mistakes

If B's ordered read seems to use the initial stock, find A's preceding write and use the value after that write.

If a transaction sounds automatically safe, ask which isolation or concurrency rule prevents another worker from using stale state.

A live double-click can produce a different schedule. The fixed timeline supports reasoning about this model, not a claim about a real checkout.

References

  • PortSwigger: Race conditions

    Explains the failure mechanism and prevention. Use it for further reading after reasoning from this page's original model; a topic page is not evidence that a lab was launched.