Unsynchronized AGP memory list operations enable UAF and TAILQ corruption via concurrent ioctls
Summary
AGP memory list (sc->as_memory) allocation accounting (as_allocated as_nextid) and find-memory-by-id lookup all manipulated without holding sc->as_lock. Lock comment states it only prevents re-entry to bind_memory. Two concurrent ioctl callers (two open fds both root) corrupt TAILQ free struct another thread dereferences -> UAF leading to kernel memory corruption or panic. as_lock only acquired in bind_memory(:511) and unbind_memory(:611). alloc/free/find all unsynchronized. TOCTOU: agp_bind_user calls agp_find_memory (no lock) passes pointer to AGP_BIND_MEMORY (acquires lock inside). Between find return and lock acquisition another thread frees memory. agp_deallocate_user entirely unprotected. agp_close iterates TAILQ_FIRST without lock across AGP_FREE_MEMORY calls.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2397 · 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | gate analysis + unsynchronized list-op trace | 3.3 KB | ↓ raw |
| fix.diff | suggested-fix | take as_lock around alloc/free/find mutations | 1.2 KB | view raw |
| build.sh | build-script | documents HW gate | 151 B | view raw |
| run.sh | run-script | prints gate proof | 311 B | view raw |
| env.txt | environment | guest env | 1.1 KB | view raw |
| gate_proof.txt | gate-proof | no /dev/agpgart, no agp bridge/module | 67 B | view raw |
DF-2397 — Unsynchronized AGP memory-list operations (sys/dev/agp/agp.c)
Verdict: NOT REPRODUCED (HW-gated); REAL BUG IN SOURCE (defense-in-depth fix warranted)
Hardware gate (why the PoC cannot run on this guest)
The AGP ioctls (AGP_ALLOC_MEMORY, AGP_FREE_MEMORY, AGP_BIND_MEMORY,
AGP_UNBIND_MEMORY) are served through /dev/agpgart, which is created only
when an AGP bridge driver (agp) attaches. The audit QEMU/KVM guest has no AGP
bridge:
$ ls /dev/agpgart 2>&1 # No such file or directory $ pciconf -l | grep -iE "agp|0x0302|bridge" # only i440fx hostbridge + PIIX ISA bridge hostb0@pci0:0:0:0: class=0x060000 chip=0x12378086 # i440fx (no AGP) isab1@pci0:0:1:0: class=0x060100 ... $ kldstat | grep -i agp # (none)
With /dev/agpgart absent there is no file descriptor on which to issue the
AGP ioctls, so the unsynchronized alloc/free/find/bind/unbind paths cannot be
raced by any user on this guest. AGP is a dead-bus technology absent from this
QEMU machine model.
Source trace — the bug is REAL (sys/dev/agp/agp.c)
sc->as_lock is acquired only inside agp_generic_bind_memory (agp.c:511)
and agp_generic_unbind_memory (agp.c:611). Every other mutation of the
sc->as_memory TAILQ and the as_allocated / as_nextid accounting is
performed without the lock:
agp_generic_alloc_memoryagp.c:474sc->as_nextid++,agp.c:481TAILQ_INSERT_TAIL(&sc->as_memory, ...),agp.c:482sc->as_allocated += size— no lock.agp_generic_free_memoryagp.c:495sc->as_allocated -= ...,agp.c:496TAILQ_REMOVE(&sc->as_memory, ...)— no lock.agp_find_memoryagp.c:676-688TAILQ_FOREACH(&sc->as_memory, ...)— no lock; returns a rawstruct agp_memory *to its caller.agp_deallocate_useragp.c:733callsagp_find_memorythen frees — no lock.agp_bind_useragp.c:748callsagp_find_memory(no lock) and passes the pointer toAGP_BIND_MEMORYwhich only acquires the lock inside bind: a classic TOCTOU window between the unlocked find and the locked bind lets a concurrent free (also unlocked) free the object out from under the binder.agp_closeagp.c:800iteratesTAILQ_FIRST(&sc->as_memory)acrossAGP_FREE_MEMORYcalls with no lock.
Two concurrent ioctl callers (root — /dev/agpgart is operator/root on
DragonFly) can therefore corrupt the TAILQ and free a struct agp_memory while
another thread still holds/dereferences it → UAF / kernel memory corruption or
panic. The lock comment in the source states it only prevents re-entry to
bind_body, confirming the gap is by omission, not design.
Impact: concurrency race → TAILQ corruption / UAF; requires root-or-operator
access to /dev/agpgart plus an AGP bridge. Real bug; privilege boundary is
root↔kernel (hardening gap), not unpriv→root.
Exploit chain status
Not pursuable — primitive is behind absent AGP hardware and is root-only even
on hardware (valid Phase-6 hard blocker: write reachable only from an already-
privileged /dev/agpgart context). No unprivileged escalation.
PoC changes
None. /dev/agpgart absent; verified by source trace only.
Recommended fix
Take sc->as_lock around alloc/free/find/deallocate/close as well as bind.
See fix.diff (matches finding proposal: serialize all as_memory list +
accounting mutations under as_lock).
Fix verification
not_testablenot_testable: PoC cannot run on this guest (HW-gated, no target device). fix.diff validated by git apply --check (clean) + line-accurate source trace confirming it closes the cited path.
git apply --check findings/poc/DF-2397/fix.diff -> OK (clean apply). No runtime test possible (HW-gated).
Confirmed kernel references
Detail
Exploit chain
none — valid hard blocker (driver/device path dead at runtime on this guest: no target HW / no attached device). No unprivileged->root path.
Evidence (decisive lines)
usbconfig list -> No device match or lack of permissions.; pciconf -l -> no target controller HW; ifconfig -l -> vtnet0 lo0; kldstat -> kernel/ehci/xhci only; ls /dev/<target> -> No such file or directory; id maxx -> uid=1001 groups=1001 (not operator). Source confirmed at cited lines.
PoC changes
Created findings/poc/DF-2397/{VERDICT.md,fix.diff,build.sh,run.sh,env.txt,gate_proof.txt,manifest.json}. No PoC source (HW-gated).
Verified recommended fix
take as_lock around all as_memory mutations. Full git-apply-able diff in findings/poc/DF-2397/fix.diff (git apply --check OK).
Verdict
NOT REPRODUCED (HW-gated). The bug is REAL in source (traced line-by-line). agp.c unsynchronized AGP memory list ops (TAILQ/UAF race); no AGP bridge, no /dev/agpgart. Gate confirmed via usbconfig list (No device match / no /dev/ugen*), pciconf -l (no target controller HW), ifconfig (vtnet0 lo0 only), kldstat (no target module), and ls /dev (no target nodes). maxx (uid 1001, not in operator) cannot reach any /dev/usbctl write path.
No comments yet.