Use-after-free race in aac_getnext_aif: AIF context list traversed and dereferenced without aac_aifq_lock
Summary
aac_getnext_aif() at aac.c:3527-3531 walks sc->fibctx for(ctx;ctx;ctx=ctx->next) WITHOUT aac_aifq_lock. aac_close_aif at :3495-3510 takes lock, unlinks, kfrees. Concurrent close -> ctx freed while getnext walks/reads ctx->next -> UAF. Also tsleep at :3536 without lock: close can kfree(ctx) during sleep, aac_return_aif on wakeup derefs freed ctx->ctx_idx/ctx_wrap at :3562-3575. Operator group (0640). Race widened by growing list with many OPEN_GET_ADAPTER_FIB. Fix: take aac_aifq_lock across traversal, re-validate ctx after tsleep wakeup.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1231 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | full path:line trace, threat model, fix rationale | 5.0 KB | β raw |
| README.md | readme | claim, verdict, runnable-PoC instructions | 2.4 KB | β raw |
| aac_getnext_aif_race.c | trigger-source | pthread-race PoC (operator-group reachable) | 4.3 KB | view raw |
| build.sh | build-script | cc -O -Wall -pthread -o aac_getnext_aif_race aac_getnext_aif_race.c | 231 B | view raw |
| run.sh | run-script | ./aac_getnext_aif_race | 112 B | view raw |
| build.log | build-log | PoC build, full output | 149 B | view raw |
| run.log | run-log | PoC run on this guest (open /dev/aacN ENOENT) | 401 B | view raw |
| fix.diff | suggested-fix | take aac_aifq_lock across getnext; tsleep -> lksleep | 1.5 KB | view raw |
| fix_build.log | build-log | kernel build rc=0 with all 5 fixes applied; aac.c compiled clean under -Werror | 2.1 KB | view raw |
| env.txt | environment | guest uname, cc version, PCI topology | 2.0 KB | 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 |
DF-1231 β README
Finding
aac_getnext_aif() at sys/dev/raid/aac/aac.c:3527-3546 walks
sc->fibctx and tsleep()s WITHOUT aac_aifq_lock. Concurrent
aac_close_aif() (aac.c:3489-3512) takes the lock, unlinks, and
kfree()s the same context β two UAF races:
- traversal race β close runs between two iterations of getnext's
lookup loop; getnext then reads
ctx->nextof freed memory. - sleep race β getnext
tsleep()s without the lock; close freesctx; on wakeup, getnext callsaac_return_aif()which derefsctx->ctx_idx/ctx->ctx_wrap(aac.c:3562, 3574-3575) on freed memory.
Verdict
NOT REPRODUCED on this guest (latent): the audit guest has no
Adaptec FSA RAID controller in pciconf -lv, so no /dev/aacN exists
and the ioctls are not reachable. The driver is statically linked
into X86_64_GENERIC (kldstat -v shows pci/aac), so the bug path is
in the running kernel and fires on real DragonFly installs with an
aac(4) adapter.
Confidence (bug is real): certain β traced line-by-line in sys/.
Impact ceiling: UAF β kernel heap corruption / DoS / potential
control-flow hijack. Reachable by any local user in operator group
once an aac controller is present.
How to reproduce
./build.sh && ./run.sh
Expected on this guest: PoC builds clean, prints "No /dev/aacN found: No
such file or directory" and reachability analysis. On a host with an
aac controller, two processes racing
FSACTL_GET_NEXT_ADAPTER_FIB (Wait=1) vs
FSACTL_CLOSE_FIB_ADAPTER on the same fib context trigger the UAF.
Files
| Path | Purpose |
|---|---|
aac_getnext_aif_race.c |
PoC: pthread-race demonstrator (operator-group reachable) |
build.sh / run.sh |
exact build/run commands |
fix.diff |
take aac_aifq_lock across traversal; switch tsleep->lksleep |
VERDICT.md |
full path:line trace, threat model, fix rationale |
build.log / run.log |
PoC build + run outputs |
fix_build.log |
kernel-build compile validation of fix.diff |
env.txt |
guest uname / cc / device topology |
DF-1231 β VERDICT
Finding: aac_getnext_aif() walks the per-controller sc->fibctx list
and tsleep()s without holding sc->aac_aifq_lock, while concurrent
aac_close_aif() (which DOES hold the lock, unlinks and kfree()s the
context) creates a use-after-free.
Status: NOT REPRODUCED (latent β no Adaptec FSA RAID controller on this guest).
Confidence (bug is real): certain (traced line-by-line in sys/).
Impact ceiling: use-after-free; kernel memory corruption (slab poisoning
or attacker-reclaimed slab), panic/DoS or control-flow hijack on
INVARIANTS-OFF kernels. Reachable by any local user in the operator group
once an aac(4) controller is present.
Mechanism (confirmed line-by-line in sys/)
-
aac_getnext_aifatsys/dev/raid/aac/aac.c:3519:c if ((error = copyin(arg, &agf, sizeof(agf))) == 0) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { /* :3528 */ if (agf.AdapterFibContext == ctx->unique) break; } if (!ctx) return (EFAULT); error = aac_return_aif(sc, ctx, agf.AifFib); if (error == EAGAIN && agf.Wait) { ... while (error == EAGAIN) { error = tsleep(sc->aac_aifq, PCATCH, "aacaif", 0); /* :3540 */ if (error == 0) error = aac_return_aif(sc, ctx, agf.AifFib); /* :3543 */ }The lookup loop at :3528 and the tsleep at :3540 execute WITHOUTaac_aifq_lockheld. -
aac_close_aifatsys/dev/raid/aac/aac.c:3489-3512:c lockmgr(&sc->aac_aifq_lock, LK_EXCLUSIVE); /* :3495 */ for (ctx = sc->fibctx; ctx; ctx = ctx->next) { ... unlink ... } lockmgr(&sc->aac_aifq_lock, LK_RELEASE); /* :3508 */ if (ctx) kfree(ctx, M_AACBUF); /* :3510 */ -
Race A (traversal): a concurrent
closeruns between two iterations of thegetnextlookup loop.closetakes the lock, unlinksctx, releases the lock, andkfree()sctx. The next loop iteration ingetnextthen readsctx->nextof freed memory β UAF read. -
Race B (sleep):
getnextenterstsleepat :3540 without the lock.closeruns end-to-end (lock, unlink, release,kfree(ctx)). Whengetnextwakes, it callsaac_return_aif(sc, ctx, agf.AifFib)at :3543, which dereferencesctx->ctx_idx(aac.c:3562) and writesctx->ctx_wrap/ctx->ctx_idx(aac.c:3574-3575) β UAF read+write on freed memory. -
aac_softc->aac_aifq_lockis a recursivelockmgrlock (aac.c:264lockinit(..., LK_CANRECURSE)), andaac_return_aifitself takes it (aac.c:3561), so holding it across the lookup AND across anlksleepis safe. -
lksleep(ident, &lock, ...)is the standard DragonFly primitive that atomically releases the lock during sleep and reacquires before returning; it is already used elsewhere in this driver (aac.c:672, 1014, 1352, 3003, 3094). With the lock held,aac_close_aifcannot unlink or freectxwhilegetnextis sleeping β the race is closed.
Why it is NOT REPRODUCED on this guest
pciconf -lvshows only QEMU i440BX/PIIX3/virtio devices. No Adaptec AAC RAID controller is present.kldstat -vconfirmspci/aac,aac/aacd,aac/aacp,pci/aacchare statically linked intoX86_64_GENERIC(the driver code IS in the running kernel), butaac_attachnever runs without matching HW, so no/dev/aacNdevice node is created.ls /dev/aac*returns no device nodes. PoCaac_getnext_aif_race.cconfirms this at runtime.
Threat model & privilege boundary
/dev/aacN is created with default devfs perms (0640 root:operator). Any
local user in the operator group (commonly granted to administrative
staff for tape/CD-ROM access) can open the device and issue
FSACTL_OPEN_GET_ADAPTER_FIB / FSACTL_GET_NEXT_ADAPTER_FIB /
FSACTL_CLOSE_FIB_ADAPTER ioctls. Racing two of these is therefore a
real local-user DoS / kernel-corruption vector on any host with an
aac(4) RAID adapter β not requiring root. On the audit guest there is no
such adapter, so the bug is dormant.
Fix (authored in fix.diff, applied + compile-validated)
Take sc->aac_aifq_lock across the entire aac_getnext_aif body: the
list traversal, the aac_return_aif call (recursive take is fine β the
lock is LK_CANRECURSE), and the sleep. The tsleep is replaced by
lksleep(sc->aac_aifq, &sc->aac_aifq_lock, PCATCH, "aacaif", 0) so the
lock is dropped during sleep but aac_close_aif cannot proceed (it
needs the same lock to unlink+free), eliminating both races.
This mirrors how the driver already uses lksleep(..., &sc->aac_io_lock,
...) at lines 672, 1014, 1352.
Validation
fix.diffapplies cleanly withpatch -p1 --forward(verified).- All 5 audit fixes applied together;
make -j6 nativekernel KERNCONF=X86_64_GENERICreturned rc=0 with no errors / warnings under-Werror.aac.cwas compiled cleanly into both the kernel and theaac.komodule. - Fix is not_testable at runtime on this guest (no AAC controller).
Fix verification
not_testablecompile validated
nativekernel rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. aac_getnext_aif UAF race vs aac_close_aif. aac in GENERIC, no Adaptec HW.
No comments yet.