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)
PoC verification
Evidence pack
findings/poc/DF-2028 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 -- 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
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- i
- i
- r
- /
- i
- i
- r
- _
- c
- t
- r
- l
- .
- c
- :
- 2
- 4
- 5
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- i
- i
- r
- /
- i
- i
- r
- _
- c
- t
- r
- l
- .
- c
- :
- 2
- 5
- 0
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- i
- i
- r
- /
- i
- i
- r
- .
- c
- :
- 1
- 6
- 1
- 5
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.
No comments yet.