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

Use-after-free in GDT_IOCTL_GENERAL: framework-owned ioctl buffer queued into persistent controller command list

Summary

iir_ioctl (iir_ctrl.c:245) casts framework-supplied ap->a_data buffer to gdt_ucmd_t* then TAILQ_INSERT_TAIL into gdt->sc_ucmd_queue at line 250 -- handing ownership of framework transient buffer (kmalloc at sys_generic.c:675 freed at sys_generic.c:735 unconditionally after driver returns) to controller persistent queue with NO lifetime management. tsleep with PCATCH at line 255 has return value explicitly discarded via (void) so signal-induced EINTR/ERESTART silently ignored falls through to break (256). On signal interrupt: mapped_ioctl frees buffer but sc_ucmd_queue and gccb->gc_ucmd (set at iir.c:1006) still hold dangling pointer. Controller interrupt completion handler (iir.c:1600-1627) writes up to sizeof(gdt_ucmd_t) bytes including bcopy up to GDT_SCRATCH_SZ(3072) bytes of controller-controlled data into freed heap memory at iir.c:1615/1619/1624/1626. Subsequent ioctl also hits dangling pointer via gdt_next TAILQ_FIRST/TAILQ_REMOVE at iir.c:704-706 (UAF read). Attacker: local user in operator group (/dev/iir default UID_ROOT GID_OPERATOR 0600 iir_ctrl.c:100-106; operator gid 5 routinely granted to backup/semi-trusted accounts). Trigger: fork child issuing slow ioctl, parent usleep then kill(child,SIGUSR1) to interrupt tsleep; child syscall returns mapped_ioctl frees buffer but controller still references it. Impact: kernel heap corruption in M_IOCTLOPS slab leading to panic (A:H) or with heap grooming (spray same-size objects with function pointers) local privilege escalation to uid 0 (C:H/I:H). Write offset/content steerable: param_size controls write length, controller returns disk-placed data for read subfuncs controls write content. AV:L/PR:L/AC:L, C:H/I:H/A:H.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2028 Β· 7 files
FileTypeDescriptionSize
README.md readme original PoC README 176 B ↓ raw
VERDICT.md verdict full source-trace verdict 1.8 KB ↓ raw
build.sh build-script build/verify instructions 438 B view raw
env.txt environment guest environment (no matching HW) 814 B view raw
fix.diff suggested-fix git-apply-able fix, verified to compile -Werror 870 B view raw
fix_build.log build-log Phase 8 module build evidence (-Werror rc=0) 1.4 KB view raw
run.sh run-script run instructions (HW-gated) 320 B view raw
README.md readme original PoC README
↓ download raw

DF-2028 PoC

See the parent finding markdown at findings/DF-2028-*.md for the full threat model and PoC steps. This directory is the evidence-pack slot for the PoC runner.

VERDICT.md verdict full source-trace verdict
↓ download raw

VERDICT -- DF-2028 (High)

Verdict: INCONCLUSIVE (HW-gated)

Impact: UAF confirmed at source (kernel heap write up to GDT_SCRATCH_SZ=3072 bytes); HW-gated (needs iir(4) RAID controller) -- not runtime-triggerable on this guest

Confidence: certain

Mechanism (source-traced)

gdt_ioctl (iir_ctrl.c:245) casts the framework-supplied ioctl buffer ap->a_data to gdt_ucmd_t* and TAILQ_INSERT_TAIL's it into the persistent sc_ucmd_queue (iir_ctrl.c:250), handing a transient buffer (kmalloc'd in sys_generic.c:675, freed at :735 after the driver returns) to the controller with no lifetime management. tsleep at :255 uses PCATCH but its return value is discarded via (void), so a signal interrupts the sleep and control falls through to break (:256); mapped_ioctl then frees the buffer while sc_ucmd_queue and gccb->gc_ucmd (set at iir.c:1006) still reference it. The controller interrupt completion handler (iir.c:1600-1627) later writes up to sizeof(gdt_ucmd_t) bytes into that freed memory (bcopy gccb->gc_scratch -> ucmd->data at iir.c:1615/1619/1624/1626).

Why not runtime-reproduced

The guest (DragonFlyBSD 6.5-DEVELOPMENT #0 master DEV, KVM) has NO matching hardware: pciconf shows no mfi/tws/iir RAID controller and no amdgpu/DRM GPU; the driver therefore cannot attach and the vulnerable path is not runtime- triggerable here. The defect was confirmed at the source level by tracing the cited path:line against sys/, and the proposed fix was applied and the affected module (iir) built clean with -Werror (see fix_build.log).

Fix

iir_ctrl.c GDT_IOCTL_GENERAL: loop tsleep until ucmd->complete_flag is set instead of discarding the signal return, so the framework buffer is never freed (by mapped_ioctl) before the controller completion handler is done writing into it.

The standalone, git-apply-able diff is fix.diff.

Confirmed kernel references

Detail

Exploit chain

none (HW-gated).

Evidence (decisive lines)

HW-GATED (no iir RAID). UAF CONFIRMED source. iir_ioctl casts framework buffer to gdt_ucmd_t, TAILQ_INSERT_TAIL into sc_ucmd_queue, tsleep PCATCH discarded. Signal frees buffer while completion handle

Verified recommended fix

Replace single tsleep with while(!complete_flag) tsleep loop.

Verdict

HW-GATED (no iir RAID). UAF CONFIRMED source. iir_ioctl casts framework buffer to gdt_ucmd_t, TAILQ_INSERT_TAIL into sc_ucmd_queue, tsleep PCATCH discarded. Signal frees buffer while completion handler still writes up to 3072B.