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

TOCTOU in MACCMD_LIST: as_nacls read without lock before buffer sizing β€” heap OOB write (grow) and uninit heap info leak (shrink)

Summary

ieee80211_acl.c:313 space=as->as_nacls*IEEE80211_ADDR_LEN read WITHOUT ACL_LOCK (not taken until :328). :319 kmalloc(space,M_TEMP,M_INTWAIT) NO M_ZERO. :328 ACL_LOCK. :329-331 TAILQ_FOREACH writes ap[i++] for every entry under lock. Grow race: concurrent ADDMAC between :313 and :328 widens list foreach writes past ap end = heap OOB write of attacker-controlled MAC bytes. Shrink race: concurrent DELMAC/FLUSH shrinks list ap tail uninitialized copyout ships uninit heap to userland = info leak. Local-root trigger: two threads SIOCS80211 ADDMAC + SIOCG80211 MACCMD_LIST same vap. Attacker-controlled content (MAC addresses) + slab grooming = arbitrary kernel write primitive. Fix: take ACL_LOCK before reading as_nacls add M_ZERO bound foreach to space/ADDR_LEN.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0732 Β· 21 files
FileTypeDescriptionSize
VERDICT.md verdict full narrative: line-by-line TOCTOU, harness methodology, grow+shrink evidence, fix before/after 10.5 KB ↓ raw
README.md readme summary + reproduce instructions 2.5 KB ↓ raw
harness.c trigger-source deterministic userspace transcription of acl_getioctl MACCMD_LIST (PRIMARY proof; BUGGY + -DFIXED builds) 12.5 KB view raw
harness_mod.c exploit-chain real-kernel object-level harness module exercising actual acl_getioctl via fake vap + /dev/df0732 7.9 KB view raw
Makefile trigger-source build for harness_mod.ko (bsd.kmod.mk) 452 B ↓ download
trigger.c trigger-source userspace racer: drives LIST ioctl, detects grow panic + shrink leak 4.6 KB view raw
leakcheck.c trigger-source definitive WEIRD_ADDR residue classifier (distinguishes real heap leak from valid MACs) 3.7 KB view raw
build.sh build-script builds BUGGY + FIXED harness transcriptions 517 B view raw
run.sh run-script runs all 4 scenarios (buggy/fixed x grow/shrink) 1.5 KB view raw
fix.diff suggested-fix git-apply-able: ACL_LOCK before as_nacls read + M_ZERO + bounded foreach 1.6 KB view raw
build.log build-log harness build output (BUGGY + FIXED, -Wall clean) 275 B view raw
run.log run-log deterministic harness run: BUGGY both CONFIRMED, FIXED both NOT TRIGGERED 2.3 KB view raw
trigger_run.log run-log real-kernel buggy trigger: 7 shrink-leak hits then guest panicked 665 B view raw
fix_trigger_run.log run-log real-kernel FIXED trigger: 241489 ioctls, no panic, guest UP 861 B view raw
fix_run.log run-log FIXED harness run + real-kernel leakcheck: WEIRD_ADDR residue=0 2.9 KB view raw
fix_build.log build-log wlan_acl.ko fixed build (-Werror clean) 1.3 KB view raw
panic.txt panic-signature real-kernel grow-race panic: acl_getioctl+0xd6 movw %si,0x4(%rcx) 715 B view raw
leak_sample.txt leak-sample real-kernel shrink-race leak: kernel string residue '..Copy' + varying bytes 1.0 KB view raw
env.txt environment uname, cc version, module/ifconfig state 554 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme summary + reproduce instructions
↓ download raw

DF-0732 β€” TOCTOU in wlan_acl acl_getioctl MACCMD_LIST

Verdict: REPRODUCED (heap OOB write + uninit-heap info leak); fix VALIDATED. Severity: High. CWE: CWE-787 (OOB write), CWE-908 (uninit memory).

The bug

sys/netproto/802_11/wlan_acl/ieee80211_acl.c:313 reads as->as_nacls WITHOUT ACL_LOCK (taken only at :328) to size the kmalloc(space, M_TEMP, M_INTWAIT) at :319 (no M_ZERO), then TAILQ_FOREACH (:329-332) writes every current list entry unbounded. A concurrent ADDMAC/DELMAC between :313 and :328 makes the list diverge from the buffer size: - Grow β†’ OOB heap write of attacker-controlled MAC bytes past the buffer. - Shrink β†’ uninitialized tail shipped to userland by copyout (info leak).

Reproduce (userspace deterministic harness β€” PRIMARY proof)

sh build.sh     # builds harness (BUGGY) + harness_fixed (FIXED)
sh run.sh       # BUGGY => both races CONFIRMED; FIXED => both NOT TRIGGERED

Reproduce (real-kernel object-level harness β€” needs root to kldload)

# build harness_mod.ko (in guest, as root):
cd /root/df0732 && make SYSDIR=/usr/src/sys KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
kldload wlan; kldload wlan_acl; sysctl debug.use_malloc_pattern=1
kldload ./harness_mod.ko          # exposes /dev/df0732 (0666)
# as unprivileged user:
cc -O2 -pthread -o trigger trigger.c    && ./trigger  200000 4   # unpatched => panic
cc -O2 -pthread -o leakcheck leakcheck.c && ./leakcheck 100000 4  # fixed => WEIRD_ADDR=0

Runtime reachability

The live ACL ioctl path needs a wlan(4) vap on a wifi radio β€” absent on this KVM guest (ifconfig -l = vtnet0 lo0). The bug is proven at the object/harness level (real acl_getioctl panic + leak) and via deterministic transcription. On a wifi host with a vap, a local user could race ADDMAC vs MACCMD_LIST.

Fix

fix.diff: move ACL_LOCK before the as_nacls read, add M_ZERO, bound the foreach. Validated: rebuilt wlan_acl.ko clean, reloaded, no panic + zero WEIRD_ADDR residue.

Files

  • harness.c β€” deterministic userspace transcription (PRIMARY proof)
  • harness_mod.c / Makefile β€” real-kernel object-level harness module
  • trigger.c β€” userspace racer (grow panic / shrink leak)
  • leakcheck.c β€” definitive WEIRD_ADDR residue classifier
  • fix.diff β€” git-apply-able fix (lock-before-read + M_ZERO + bounded foreach)
  • VERDICT.md β€” full narrative + line-by-line analysis + before/after
  • *.log β€” full untrimmed build/run/panic/leak logs
VERDICT.md verdict full narrative: line-by-line TOCTOU, harness methodology, grow+shrink evidence, fix before/after
↓ download raw

DF-0732 β€” TOCTOU in wlan_acl acl_getioctl MACCMD_LIST

Verdict

REPRODUCED β€” heap OOB write (grow race) and uninitialized-heap info leak (shrink race), confirmed at two levels:

  1. Deterministic userspace harness (harness.c) β€” transcribes acl_getioctl MACCMD_LIST verbatim with a controlled interleaving point between the unlocked as_nacls read and ACL_LOCK. Prints GROW RACE OOB WRITE CONFIRMED and SHRINK RACE UNINIT LEAK CONFIRMED.
  2. Real-kernel object-level harness (harness_mod.ko + trigger.c / leakcheck.c) β€” exercises the actual acl_getioctl via a fake vap + /dev/df0732, racing iac_add/iac_flush kthreads against the LIST ioctl. Produces a real kernel panic acl_getioctl+0xd6 (grow-race OOB write faulting the page) and live shrink-race heap-residue leaks.

Fix VALIDATED (Phase 8): the fix.diff (lock-before-read + M_ZERO + bounded foreach) rebuilds wlan_acl.ko clean (-Werror) and eliminates both manifestations in the real kernel β€” no panic, zero WEIRD_ADDR residue.

Runtime reachability note (the realistic-threat caveat): the live 802.11 ACL ioctl path requires a wlan(4) vap on a parent wifi radio. This KVM audit guest has no wifi radio (ifconfig -l = vtnet0 lo0; no ath/iwm/iwn; wlan_acl is a loadable KLD, not in X86_64_GENERIC). So an unprivileged runtime trigger is impossible on this guest; the bug is proven at the object/harness level (real acl_getioctl panic + leak) and via deterministic transcription. On a wifi-equipped host with a vap, a local user could race SIOCS80211 ADDMAC vs SIOCG80211 MACCMD_LIST for OOB write / leak. This is the same harness-precedent class as DF-0393/0594/0616/0753/0754.

The bug β€” line-by-line (sys/netproto/802_11/wlan_acl/ieee80211_acl.c)

acl_getioctl handles IEEE80211_MACCMD_LIST (:312-340):

Line Code Problem
313 space = as->as_nacls * IEEE80211_ADDR_LEN; UNLOCKED read of as_nacls. ACL_LOCK is NOT held here.
314-317 if (ireq->i_len == 0) { i_len = space; return 0; } size-probe path (also uses the unlocked space).
319-320 ap = kmalloc(space, M_TEMP, M_INTWAIT); NO M_ZERO β€” buffer is uninitialized heap.
327 i = 0;
328 ACL_LOCK(as); Lock taken only NOW β€” after the size read AND after kmalloc.
329-332 TAILQ_FOREACH(acl, &as->as_list, acl_list) { ADDR_COPY(ap[i++].ml_macaddr, acl->acl_macaddr); } writes every current list entry; not bounded by space/ADDR_LEN.
333 ACL_UNLOCK(as);
334-338 copyout(ap, ireq->i_data, ...) ships ap to userland.

ACL_LOCK/ACL_UNLOCK are real lockmgr(&as->as_lock, LK_EXCLUSIVE) locks (ieee80211_dragonfly.h:606/619). acl_add (:199-215) and _acl_free (:150-159) both mutate as_nacls/as_list under ACL_LOCK. So between the unlocked read at :313 and the lock at :328, a concurrent ADDMAC/DELMAC can change the list out from under the lister.

Grow race β†’ heap OOB write (CWE-787)

Thread A (lister) reads as_nacls = N at :313, allocates N*6 bytes at :319. Thread B (adder) inserts K MACs under the lock. Thread A takes the lock at :328 and TAILQ_FOREACH writes N+K entries into the N*6-byte buffer: ap[N..N+K-1] are written K*6 bytes past the end. The MAC bytes are attacker-controlled (SIOCS80211 ADDMAC takes the MAC from userland). Real-kernel proof: Fatal trap 12 ... supervisor write data, page not present ... Stopped at acl_getioctl+0xd6: movw %si,0x4(%rcx) β€” the OOB write faulted into an unmapped page.

Shrink race β†’ uninitialized-heap info leak (CWE-908)

Thread A reads as_nacls = N, allocates N*6 bytes (no M_ZERO). Thread B (flusher) clears the list to N-K. Thread A locks, TAILQ_FOREACH writes only N-K entries; the tail K slots ap[N-K..N-1] are never written and hold uninitialized heap residue. copyout ships all N*6 bytes to userland. Real-kernel proof: [shrink-leak] ret_len=49152 entries=8192 ... bytes: 00 00 43 6f 70 79 ("..Copy" β€” kernel string residue), varying run-to-run.

Harness methodology

harness.c β€” deterministic userspace transcription (PRIMARY proof)

Faithfully transcribes acl_getioctl MACCMD_LIST with the data structures (struct aclstate, struct ieee80211req_maclist = 6 bytes __packed, ACL_LOCK = pthread mutex). A racer thread mutates the list at a controlled interleaving point between the unlocked as_nacls read and the lock. A poisoned allocator fills the slab + red-zone with 0xAA canary so OOB writes and uninit tails are observable. Builds in two modes: - cc -O2 -pthread -o harness harness.c β€” BUGGY transcription. - cc -O2 -pthread -DFIXED -o harness_fixed harness.c β€” FIXED transcription (lock-before-read + M_ZERO + bounded foreach).

Results (run.log): BUGGY β†’ GROW RACE OOB WRITE CONFIRMED (24 OOB bytes for N=8,K=4) + SHRINK RACE UNINIT LEAK CONFIRMED (4 uninit tail slots). FIXED β†’ both NOT TRIGGERED.

harness_mod.c + trigger.c / leakcheck.c β€” real-kernel object-level harness

harness_mod.ko (built against the running kernel) allocates a minimal fake ieee80211vap, attaches the real "mac" aclator (wlan_acl.ko), exposes /dev/df0732 (0666) whose ioctl calls acl->iac_getioctl(fake_vap, &ireq), and runs adder + flusher kthreads that race the LIST path. The unprivileged trigger/leakcheck drive it. This exercises the actual vulnerable kernel function. kldload here loads the test harness, not an exploit β€” it is primitive characterization of an otherwise-runtime-unreachable path (the DF-0594/0616 object-harness precedent), not an escalation chain.

Results (panic.txt, leak_sample.txt, trigger_run.log): grow race β†’ acl_getioctl+0xd6 panic (OOB write); shrink race β†’ live heap-residue leak (kernel string "..Copy", varying bytes).

Exploit chain / impact ceiling

Primitive: attacker-controlled 6-byte heap writes into the slab object(s) adjacent to the ap buffer (grow race); 6-byte uninit heap reads per shrunk entry (shrink race). The ap buffer is kmalloc(N*6, M_TEMP); for typical N this lands in kmalloc-128/256/512. Attacker controls N (list size) and the MAC bytes written. On a noinv kernel this is a classic slab-groom β†’ corrupt-adjacent β†’ escalate candidate (forge ucred/ops vector with no SMAP/SMEP/KASLR on this guest). On default GENERIC, INVARIANTS slab checks (WEIRD_ADDR poisoning, chunk_mark_allocated) catch the cross-slab corruption and panic β€” confirmed by the real-kernel acl_getioctl+0xd6 trap.

Valid hard blocker (why no uid=0 here): the vulnerable runtime path (SIOCG80211 MACCMD_LIST on a wlan vap) requires a wifi radio driver, which is absent on this KVM guest. The primitive is therefore characterized at the harness/object level (real acl_getioctl panic + leak, deterministic transcription), not driven to a live unprivileged uid=0. On a wifi-equipped host with a vap, this is a local-root candidate via slab grooming of the OOB write; on this guest it is a confirmed DoS-panic + info leak.

Fix (fix.diff)

Three changes to acl_getioctl MACCMD_LIST, all targeting the root cause:

  1. Move ACL_LOCK(as) BEFORE the as_nacls read (:313) so the size used for kmalloc and the list iterated by TAILQ_FOREACH cannot diverge. Add ACL_UNLOCK on the early-return and ENOMEM paths. ACL_LOCK is a sleepable lockmgr lock, so holding it across kmalloc(M_INTWAIT) is safe.
  2. Add M_ZERO to the kmalloc (:319) (M_INTWAIT | M_ZERO) β€” kills the uninit-leak even if a future divergence reappears.
  3. Bound the TAILQ_FOREACH write loop to space/IEEE80211_ADDR_LEN entries (if (i >= bound) break;) β€” defense-in-depth so the loop can never write past the buffer.

git apply --check passes. The FreeBSD branch (#else) is given IEEE80211_M_ZERO too for consistency.

Phase 8 β€” fix validation

Before (unpatched #0 baseline, prebuilt /boot/kernel/wlan_acl.ko): - grow race β†’ Fatal trap 12 ... acl_getioctl+0xd6: movw %si,0x4(%rcx) (panic, guest down). (panic.txt) - shrink race β†’ [shrink-leak] ret_len=49152 ... bytes: 00 00 43 6f 70 79 (kernel string residue leaked). (leak_sample.txt, trigger_run.log)

After (rebuilt wlan_acl.ko from patched source, sha256 c23e324c..., -Werror clean): - grow race β†’ no panic; 241489 LIST ioctls completed, guest stayed UP. (fix_trigger_run.log) - shrink race β†’ leakcheck: WEIRD_ADDR residue = 0, OTHER = 0 (no heap residue leaked). (fix_run.log) - Deterministic harness FIXED build β†’ both races NOT TRIGGERED. (run.log, fix_run.log)

fix_status: fixed. The fix closes both the OOB write and the info leak in the real kernel and in the deterministic transcription.

PoC changes from the seeded version

The folder arrived with a prior runner's harness_mod.c / trigger.c / Makefile (object-level harness) and their captured panic.txt / leak_sample.txt. This run: - Re-verified the real-kernel panic (acl_getioctl+0xd6) and shrink-race leak against the unpatched prebuilt module (refreshed panic.txt, leak_sample.txt, new trigger_run.log). - Added harness.c β€” a clean deterministic userspace transcription with a poisoned allocator and grow/shrink modes (the PRIMARY proof per the task framing). - Added leakcheck.c β€” a definitive residue classifier that distinguishes real WEIRD_ADDR (0xdeadc0de) heap residue from valid adder MACs, removing the false positives in the original trigger.c's is_known_mac() (which assumed adder byte3 always == 0xef). - Authored fix.diff (lock-before-read + M_ZERO + bounded foreach) and validated it end-to-end: git apply --check, rebuild wlan_acl.ko (-Werror), reload, re-run trigger/leakcheck β†’ no panic, zero residue. - Added build.sh / run.sh repro scripts, refreshed build.log / run.log, fix_build.log / fix_run.log / fix_trigger_run.log, env.txt.

Reproduce

# userspace deterministic harness (PRIMARY proof; no root, no wifi needed)
cd findings/poc/DF-0732 && sh build.sh && sh run.sh
# BUGGY: GROW RACE OOB WRITE CONFIRMED + SHRINK RACE UNINIT LEAK CONFIRMED
# FIXED: both NOT TRIGGERED

# real-kernel object-level harness (needs root to kldload the harness module)
# kldload wlan; kldload wlan_acl; kldload ./harness_mod.ko
# sysctl debug.use_malloc_pattern=1
# ./trigger 200000 4   # unpatched wlan_acl => panic (acl_getioctl+0xd6)
# ./leakcheck 100000 4 # fixed wlan_acl    => WEIRD_ADDR residue = 0

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED. The fix closes BOTH manifestations. Baseline (unpatched prebuilt wlan_acl.ko, #0 kernel): grow race -> Fatal trap 12 / acl_getioctl+0xd6 panic (guest down); shrink race -> kernel string residue ('..Copy') leaked via copyout. Patched (wlan_acl.ko rebuilt from fix.diff source): grow race -> NO panic (241489 LIST ioctls, guest stayed UP); shrink race -> leakcheck WEIRD_ADDR residue = 0 (no heap residue leaked). The deterministic harness FIXED build independently confirms both races eliminated (GROW: red zone intact; SHRINK: all tail slots zeroed). git apply --check passes; module rebuilds -Werror clean.

BEFORE (baseline): 'Stopped at acl_getioctl+0xd6: movw %si,0x4(%rcx)' (panic) + '[shrink-leak] bytes: 00 00 43 6f 70 79' (kernel residue leak). AFTER (fixed wlan_acl.ko): 'trigger: 241489 LIST ioctls ... no panic' (guest UP) + 'leakcheck: WEIRD_ADDR residue : 0 (none -- leak eliminated)'. Deterministic harness before/after: BUGGY 'GROW RACE OOB WRITE CONFIRMED'/'SHRINK RACE UNINIT LEAK CONFIRMED' -> FIXED 'GROW RACE: NOT TRIGGERED'/'SHRINK RACE: NOT TRIGGERED'.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 (same baseline kernel; wlan_acl is a loadable KLD not compiled into GENERIC, so the fix was validated by rebuilding+swapping /boot/kernel/wlan_acl.ko from patched source [sha256 c23e324c45c785a311a3088893d74931072c1118bb0e59aa9bee940317fed9d3, -Werror clean] rather than a full kernel rebuild)

Confirmed kernel references

Detail

Exploit chain

Memory-corruption primitive characterized, not driven to uid=0 due to a VALID hard blocker. Primitive: attacker-controlled 6-byte heap writes into the slab object(s) adjacent to the ap buffer (grow race; ap=kmalloc(N*6,M_TEMP) lands in kmalloc-128/256/512 for typical N; attacker controls N via list size and the MAC bytes via SIOCS80211 ADDMAC) plus 6-byte uninit heap reads per shrunk entry (shrink race, shipped by copyout). On default GENERIC the grow-race OOB trips INVARIANTS slab checks (WEIRD_ADDR 0xdeadc0de poisoning) and panics -- confirmed by the real acl_getioctl+0xd6 trap; on a noinv kernel this is a slab-groom -> corrupt-adjacent-victim -> escalate candidate (forge ucred/ops vector; no SMAP/SMEP/KASLR on this guest). VALID HARD BLOCKER: the vulnerable runtime path (SIOCG80211 MACCMD_LIST on a wlan vap) requires a wifi radio driver, which is ABSENT on this KVM guest (ifconfig -l = vtnet0 lo0; wlan_acl is a loadable KLD, not in X86_64_GENERIC). The primitive is therefore characterized at the harness/object level (real acl_getioctl panic + leak + deterministic transcription), not driven to a live unprivileged uid=0. On a wifi-equipped host with a vap, a local user racing ADDMAC vs MACCMD_LIST gets an OOB-write/leak primitive that is a local-root candidate via slab grooming. Files: harness.c (deterministic transcription, PRIMARY), harness_mod.c+trigger.c+leakcheck.c (real-kernel object-level harness). No live uid=0 is claimed -- the honest demonstrated impact on this guest is kernel panic (DoS) + info leak.

Evidence (decisive lines)

Deterministic harness (run.log): BUGGY grow -> 'GROW RACE OOB WRITE CONFIRMED' (foreach wrote 72 bytes into 48-byte buffer; 24 OOB bytes in red zone); BUGGY shrink -> 'SHRINK RACE UNINIT LEAK CONFIRMED' (4 tail slots uninitialized). Real-kernel (panic.txt): 'Fatal trap 12: page fault while in kernel mode / fault code = supervisor write data, page not present / Stopped at acl_getioctl+0xd6: movw %si,0x4(%rcx)'. Real-kernel shrink leak (leak_sample.txt): '[shrink-leak #1] ret_len=49152 entries=8192 first non-MAC at entry 2, bytes: 00 00 00 00 78 cb' and prior run captured kernel string residue '00 00 43 6f 70 79' ('..Copy'). FIXED harness: both races 'NOT TRIGGERED'. FIXED real-kernel module: 241489 LIST ioctls, no panic, guest UP; leakcheck WEIRD_ADDR residue=0.

PoC changes

Added harness.c (deterministic userspace transcription of acl_getioctl MACCMD_LIST with poisoned allocator + grow/shrink modes; PRIMARY proof per task framing). Added leakcheck.c (definitive WEIRD_ADDR 0xdeadc0de residue classifier that distinguishes real heap leaks from valid adder MACs, fixing the false-positive in the seeded trigger.c whose is_known_mac assumed adder byte3 always==0xef). Re-verified the prior harness_mod.c/trigger.c real-kernel harness against the unpatched prebuilt wlan_acl.ko: refreshed panic.txt (acl_getioctl+0xd6) and leak_sample.txt (kernel string residue). Authored fix.diff (ACL_LOCK before as_nacls read + M_ZERO + bounded foreach) and validated it end-to-end. Added build.sh/run.sh repro scripts, refreshed build.log/run.log, new fix_build.log/fix_run.log/fix_trigger_run.log/env.txt, VERDICT.md, README.md, manifest.json.

Verified recommended fix

In acl_getioctl IEEE80211_MACCMD_LIST (sys/netproto/802_11/wlan_acl/ieee80211_acl.c:312-340): (1) move ACL_LOCK(as) to BEFORE the as_nacls read at :313 so the kmalloc size and the TAILQ_FOREACH list cannot diverge (add ACL_UNLOCK on the i_len==0 early-return and the ENOMEM paths; ACL_LOCK is a sleepable lockmgr lock so holding it across kmalloc(M_INTWAIT) is safe); (2) add M_ZERO to the kmalloc at :319 (M_INTWAIT | M_ZERO) to kill the uninit-leak; (3) bound the TAILQ_FOREACH write loop to space/IEEE80211_ADDR_LEN entries (defense-in-depth). The FreeBSD #else branch gets IEEE80211_M_ZERO too. Supersedes the finding markdown proposal (which named the same three changes; this fix.diff is the verified, git-apply-able, line-accurate implementation).

Verdict

REPRODUCED. The TOCTOU in acl_getioctl MACCMD_LIST is real and confirmed at two levels. (1) Line-by-line trace of sys/netproto/802_11/wlan_acl/ieee80211_acl.c: as_nacls is read at :313 WITHOUT ACL_LOCK (lock not taken until :328); kmalloc(space,M_TEMP,M_INTWAIT) at :319 has NO M_ZERO; TAILQ_FOREACH ap[i++] at :329-332 writes every current list entry, unbounded; copyout at :335 ships ap to userland. ACL_LOCK/UNLOCK are real lockmgr LK_EXCLUSIVE (ieee80211_dragonfly.h:606). No guard was missed. (2) Deterministic userspace harness (harness.c) transcribes this verbatim with a controlled interleaving point and prints 'GROW RACE OOB WRITE CONFIRMED' (24 OOB bytes for N=8,K=4) and 'SHRINK RACE UNINIT LEAK CONFIRMED' (4 uninit tail slots). (3) Real-kernel object-level harness (harness_mod.ko exercising the ACTUAL acl_getioctl via a fake vap + /dev/df0732, driven by unprivileged trigger.c) reproduced a real kernel panic 'Stopped at acl_getioctl+0xd6: movw %si,0x4(%rcx)' (grow-race OOB write faulting the page) and live shrink-race heap-residue leaks ('..Copy' kernel string, varying bytes).