the world between processes is unreliable

File & Network Access

TOCTOU file races, partial writes, dropped packets, and retries without idempotency — the failures the Fallacies of Distributed Computing warn about.

Disk and network calls fail partway, arrive twice, or never arrive at all.

01in the wild

In the wild

TOCTOU File Race

Checking then using a path leaves a gap where the file can change underneath you.

example.py
# SMELL: check-then-use -- the file can change in the gap (TOCTOU)
if os.path.exists(path):          # an attacker swaps the file here...
    with open(path) as f:          # ...so you open something else
        return f.read()

# RIGHT: just try to open it; let one syscall decide atomically
try:
    with open(path) as f:
        return f.read()
except FileNotFoundError:
    return None
Between exists() and open() the path can be replaced (a symlink swap). Opening directly collapses check and use into one atomic operation.
// observed
check-then-open: races, symlink swaps, surprises
try/except:     one atomic syscall, no gap
example.sh
# SMELL: test then act -- classic TOCTOU window
if [ -f "$f" ]; then
  rm "$f"          # $f may have changed between test and rm
fi

# RIGHT: act and tolerate the race
rm -f "$f"         # idempotent: no error if it is already gone
The [ -f ] test and the rm are two steps with a gap between them. rm -f is a single idempotent action that doesn't care about the race.
// observed
[ -f ] then rm: window for a swap
rm -f:         atomic, idempotent

Retry & Timeout

A timed-out call may have already succeeded; a call with no deadline can hang forever.

example.py
# SMELL: blind retry of a non-idempotent call -> double charge
for _ in range(3):
    try:
        return charge(card, amount)   # a timeout is not a failure!
    except Timeout:
        continue                       # the first call may have won

# RIGHT: an idempotency key makes retries safe to repeat
key = idempotency_key(order)
for _ in range(3):
    try:
        return charge(card, amount, idempotency_key=key)
    except Timeout:
        continue        # the server dedupes by key -> at most one charge
A timeout means 'unknown', not 'failed'. Retrying a non-idempotent call can double it; an idempotency key lets the server collapse retries to one effect.
// observed
blind retry: a timed-out success is charged again
idem key:    server collapses retries to one charge
example.go
// SMELL: no timeout -- a stalled peer hangs the caller forever
resp, err := http.Get(url)            // can block indefinitely

// RIGHT: bound every network call with a context deadline
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)
Without a deadline, one slow peer ties up a connection (and a goroutine) forever. A context timeout fails fast and frees the resource.
// observed
no timeout: one slow peer exhausts the pool
with ctx:   fails fast at 2s, resources freed
02cross-pollination

Where this compounds

Data Corruption
03weakness catalog

Mapped weaknesses (CWE)

On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.