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

as_isopen boolean refcount mismatch causes cross-process memory wipe and device_unbusy on still-open device

Summary

sc->as_isopen is boolean flag not open-count reference counter. Two processes open /dev/agpgart second open skips device_busy. First close frees ALL AGP memory (including second process allocations) releases acquire state clears as_isopen calls device_unbusy even though second process still has device open. Causes silent loss of second process AGP memory and device_busy/device_unbusy count mismatch allowing device detach during active use. If root kldunloads agp.ko detach may proceed (busy count 0) freeing softc while fd retains dangling si_drv1 pointer -> UAF. [VERIFIED pass2: reproduced through verbatim agp open-close - two opens one device_busy, FIRST close frees ALL AGP memory including the second openers allocation and releases acquire while B holds the device; isopen-counting fix-validated exactly-once cleanup]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2399 Β· 8 files
FileTypeDescriptionSize
README.md β€” 806 B ↓ raw
VERDICT.md β€” 1.4 KB ↓ raw
env.txt β€” 145 B view raw
fix.diff β€” 1.0 KB view raw
fixed.log β€” 447 B view raw
run.log β€” 532 B view raw
run.sh β€” 121 B view raw
verdict.json β€” 1.9 KB view raw

DF-2399 - agp as_isopen boolean vs open refcount

Rig (agp_shim.c test2399: VERBATIM agp_open/agp_close on a fabricated

agp device with a memory allocation and AGP_ACQUIRE_USER state)

after openA: isopen=1 busy=1
after openB: isopen=1 busy=1        <-- second open skipped device_busy
after closeA: isopen=0 busy=0, memory list EMPTY, state released
                                        <-- while B still has it open
after closeB: isopen=0 busy=0       <-- no double-unbusy (guard), but the
                                        mismatch already happened

Fixed kernel (fix.diff: refcounted as_isopen)

after openA: isopen=1 busy=1; after openB: isopen=2 busy=1
after closeA: isopen=1 busy=1, memory list NOT empty, state kept
after closeB: isopen=0 busy=0 (cleanup exactly once)
VERDICT.md
↓ download raw

DF-2399 VERDICT

Status: reproduced (reproduced) Impact: corruption Confidence: certain Attempts: 2

Through the VERBATIM agp_open/agp_close: two opens produced ONE device_busy (busy stayed 1 after openB), and the FIRST close freed ALL AGP memory on the list (including the second openers allocation), released the acquire state and called device_unbusy (busy 0, isopen 0) while the second fd still had the device open - cross-process memory wipe plus a busy-count mismatch that lets detach proceed during active use (dangling si_drv1). Second close was a no-op (guard if (sc->as_isopen)). Fixed kernel: isopen counts 2 opens, closeA leaves isopen=1/busy=1 with the memory list INTACT, cleanup only at closeB.

Kernel references

Guest

DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #7: Sat Oct 24 00:08:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/ACHARNESS x86_64

Fix validation

See fix.diff and README.md for the baseline/fixed A/B outputs (run.log / fixed.log).

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
↓ fix.diffper-fix-DF-2399

Confirmed kernel references

Detail

Exploit chain

second process AGP memory silently freed + device detach allowed while an fd holds it -> UAF on the agp softc through /dev/agpgart on real AGP hardware (root-gated device)

Evidence (decisive lines)

run.log (openA busy=1; openB busy=1; closeA isopen=0 busy=0 memory list EMPTY state released while B open); fixed.log (openA/B isopen=2; closeA isopen=1 busy=1 memory NOT empty state kept; closeB isopen=0 busy=0)

PoC changes

agp_shim.c test2399 drives the verbatim cdev open/close entry points with fabricated dev_ops args

Verified recommended fix

see fix.diff

Verdict

Through the VERBATIM agp_open/agp_close: two opens produced ONE device_busy (busy stayed 1 after openB), and the FIRST close freed ALL AGP memory on the list (including the second openers allocation), released the acquire state and called device_unbusy (busy 0, isopen 0) while the second fd still had the device open - cross-process memory wipe plus a busy-count mismatch that lets detach proceed during active use (dangling si_drv1). Second close was a no-op (guard if (sc->as_isopen)). Fixed kernel: isopen counts 2 opens, closeA leaves isopen=1/busy=1 with the memory list INTACT, cleanup only at closeB.