β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5262

Lockless mtw_cmdq[] ring: producers store .func before the args with no barrier while mtw_cmdq_cb consumes concurrently under MTW_LOCK (which producers never take) - torn publish executes callbacks with stale pointers

Summary

All 7 producers run without MTW_LOCK (comment admits LOR) and store .func FIRST; consumer reads func then args under a lock the producers never take - torn publish runs the callback with the PREVIOUS ring entry's pointers (type confusion: key_set_cb(vap_old,k_old), k->wk_cipher deref + wk_pad write in wrong object); slow consumer additionally lets MTW_CMDQ_GET wrap onto in-flight entries. Guest sim: 748,510 torn observations in 3s + deterministic stale-vap/key execution with wk_pad write. Twin of DF-5249. args-first+sfence+func-last fix at every producer.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5262 Β· 5 files
FileTypeDescriptionSize
cmdq_race.c β€” 7.2 KB view raw
env.txt β€” 183 B view raw
fix.diff β€” 508 B view raw
run.log β€” 0 B ↓ download
verdict.json β€” 3.6 KB view raw

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored and verified with 'git apply --check' against the pristine sys/ tree, but cannot be kernel-tested: the guest has no USB controller and no MT7601U device model, so neither baseline nor patched kernel behavior is exercisable. Hardware-gated: not_testable.

fix.diff (git-apply clean); VERDICT.md
↓ fix.diffper-fix-DF-5262

Confirmed kernel references

Detail

Exploit chain

two cmdq producers racing the cmdq task -> consumer sees func!=NULL with previous slot's arg1/k -> mtw_key_set_cb(vap=old, k=old) reads k->wk_cipher->ic_cipher and writes k->wk_pad in an unrelated (possibly freed) key/vap -> type-confused callback on stale pointers

Evidence (decisive lines)

run.log: 'torn publishes observed ... : 748510' + 'deterministic torn run: callback executed on stale freed vap/key: YES (key wk_pad write happened: YES)'; cmdq_race.c torn mode

PoC changes

Harness written from scratch; observer thread polls in mtw_cmdq_cb's read order; deterministic section forces the preemption point

Verified recommended fix

Populate arg0/arg1/k first, then a write barrier (cpu_sfence), then publish .func last (all producer sites)

Verdict

All cmdq producers (mtw_key_set 1935-1941, mtw_update_beacon 3862-3866, mtw_newassoc 2380-2386, mtw_tx 3211-3215, mtw_updateslot 4124-4128, mtw_updateprot 3926-3930, tx-timeout 2887-2892) run WITHOUT MTW_LOCK (the comment at if_mtw.c:131-134 says the lock cannot be used because of a LOR with the node lock) and store cmdq[i].func FIRST, the args (arg0/arg1/k/mac) AFTER, with no memory barrier. mtw_cmdq_cb() (920-947) concurrently reads func and the args under MTW_LOCK - a lock that excludes nothing the producers do - and executes func(arg0) or func(&cmdq[i]) as soon as func != NULL. If the consumer observes the func store before the arg stores, the callback runs with the PREVIOUS entry's pointers in that slot (16-slot ring, MTW_CMDQ_MAX=16, wraps every 16 enqueues); mtw_key_set_cb would then fetch vap/k from an unrelated prior entry (type confusion), and a slow consumer additionally lets MTW_CMDQ_GET() wrap onto entries still awaiting execution, overwriting their args mid-flight. Simulated on the guest with the exact store/read order: 748510 torn states observed by a cmdq_cb-order reader in 3s, and a forced preemption between the two stores makes the consumer execute the callback with the stale freed vap/key (including the k->wk_pad write). In-kernel reproduction hardware-gated.