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

Read-only GUI ioctls walk live array state with no driver lock while DELETE_ARRAY frees it - use-after-free race (panic or kernel-pointer copyout)

Summary

gui_lib.c is self-declared unsynchronized; its caller honors that only for the mutating ioctls - the whole read-only family is forwarded 'directly' with NO lock, and DELETE_ARRAY also runs unlocked. Two concurrent root sysctl writes race a GET_DEVICE_INFO walk (member derefs, VDEV_TO_ID copies) against array-table teardown -> UAF reads -> panic, or stale-slab bytes plus truncated kernel pointers copyout'd. Root-only race on HighPoint hardware. Serialize-the-read-family fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4332 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 3.5 KB ↓ raw
verdict.json β€” 2.3 KB view raw
manifest.json β€” 834 B view raw
env.txt β€” 363 B view raw

DF-4332 β€” hptmv read-only GUI ioctls walk array state with no driver lock (UAF race)

Files (walk sites under audit): sys/dev/raid/hptmv/gui_lib.c * get_array_flag :163-197 (pMember[i] derefs) * calc_rebuild_progress :208-224 * get_array_info/get_array_info_v2 :243-245, 265-267 * hpt_get_logical_devices :496 (while (pLogical->pParent) walk) * dispatcher: hpt_default_ioctl :1134-1447

Race counterpart (out-of-file, root cause of the missing serialization): sys/dev/raid/hptmv/ioctl.c:312-329 routes HPT_IOCTL_GET_ "directly" with no lock_driver(), and HPT_IOCTL_DELETE_ARRAY (ioctl.c:219-263 β†’ hpt_delete_array β†’ blob fDeleteArray) also never takes the driver lock, while the mutating ioctls (CREATE_ARRAY / SET_ / ADD_) do* run under lock_driver_idle (:357-371). gui_lib.c's own header states the contract plainly (gui_lib.c:36-37): "This implementation doesn't use any synchronization; the caller must assure the proper context when calling these functions" β€” the caller does not, for the whole read-only family and for DELETE_ARRAY.

Trigger (requires HighPoint RocketRAID 18xx hardware)

Two concurrent root sysctl writes to hw.hptmv.status (hptproc.c:614-617, gated by SYSCAP_NOSYSCTL_WR, kern_sysctl.c:1446):

  • thread A: HPT_IOCTL_GET_DEVICE_INFO {id = array X} β†’ get_array_info walks X->u.array.pMember[0..bArnMember-1] and copies VDEV_TO_ID(pMember) / ArrayName / flags to userspace;
  • thread B: HPT_IOCTL_DELETE_ARRAY {id = X} β†’ fDeleteArray frees X's member array tables (mArFreeArrayTable, cf. the mirrored free in gui_lib.c:802-809) and stamps.

Thread A then dereferences freed PVDevices: UAF read β†’ panic, or β€” if the freed slab is untouched β€” copies kernel heap pointers (VDEV_TO_ID = truncated PVDevice) and stale slab contents out via hptproc.c:315.

Guest status β€” NOT REPRODUCIBLE HERE

The audit guest has no HighPoint/Marvell PCI device:

# pciconf -l | grep -icE "11ab|hpt"
0
# sysctl -n hw.hptmv.status
sysctl: unknown oid 'hw.hptmv.status'     # hpt_status OID registered from attach

With gIal_Adapter == NULL the ioctl path NULL-derefs before reaching gui_lib.c (already known: DF-1853). Verdict: inconclusive β€” hardware absent; the race is established by code inspection only.

Fix direction (not validated on this guest)

Serialize the read-only dispatch group and DELETE_ARRAY under the same lock_driver()/lock_driver_idle(pAdapter) regime as the mutating ioctls (one-line summary; a reviewed patch needs care: GET_* currently run lock-free by design comment ioctl.c:316).

VERDICT.md
↓ download raw

DF-4332 β€” VERDICT

Status: inconclusive (hardware absent). Confidence in the code defect: likely.

Why inconclusive

The race requires (a) a HighPoint RocketRAID 18xx controller attached (gIal_Adapter != NULL), (b) at least one configured array, and (c) two concurrent privileged sysctl writes. The audit guest (QEMU, stock X86_64_GENERIC) has no such device β€” pciconf -l shows zero 11AB devices and the hw.hptmv.status OID is not even registered (it is created from the attach path via xhptregister_node, hptproc.c:612-619 + entry.c:98). Without an adapter the ioctl entry NULL-derefs long before gui_lib.c is reached β€” that crash is the already-known DF-1853 and is deliberately NOT reproduced here.

Code trace establishing the defect

  1. hptproc.c:614-617 registers hw.hptmv.status RW; writes require SYSCAP_NOSYSCTL_WR (sys/kern/kern_sysctl.c:1446-1447) β†’ root-only surface, consistent with DF-1529/1530/1531's threat model.
  2. hptproc.c:309-311 calls Kernel_DeviceIoControl per write, with no serialization between two concurrent writers.
  3. ioctl.c:312-329: the read-only family (GET_CHANNEL_INFO / GET_LOGICAL_DEVICES / GET_DEVICE_INFO[_V2] / …) is forwarded to hpt_default_ioctl directly β€” the comment at :316 says "read-only ioctl functions can be called directly" β€” i.e. without the lock_driver_idle() that the mutating group gets at :357-371.
  4. ioctl.c:219-263 (HPT_IOCTL_DELETE_ARRAY) β†’ hpt_delete_array (:156-186) β†’ blob fDeleteArray β€” also no lock held.
  5. gui_lib.c:36-37 documents that gui_lib itself has zero synchronization. Its read paths dereference live structures freely: hpt_get_device_info:512-534 (mIsArray β†’ get_array_info:230-249), get_array_flag:163-197 (pMember[i] derefs guarded only by NULL check), calc_rebuild_progress:208-224, hpt_get_logical_devices:496 (while (pLogical->pParent) unbounded parent walk).
  6. fDeleteArray (blob, ioctl.c:183) tears down array tables (mArFreeArrayTable, the free pattern visible at gui_lib.c:802-809) β€” concurrent step 5 walks are then UAF reads.

Impact ceiling: kernel UAF read β†’ panic (DoS), or limited kernel-heap info leak β€” get_array_info copies VDEV_TO_ID(pMember) (kernel addresses) and ArrayName bytes from freed/reused slab to the caller (hptproc.c:315 copyout). Root-triggerable from both sides; race window is a single ioctl. Severity Low.

What would falsify or confirm it

Confirm: two threads looping GET_DEVICE_INFO vs DELETE_ARRAY on a real 182x with one mirror array; expect panic in get_array_flag/get_array_info or pIds[] values leaking heap pointers after free. Falsify: if the blob's fDeleteArray defers all frees via DPC-to-completion under the caller's lock (it takes none, so unlikely), the race would be only against rebuild state, not lifetime.

Fix direction

Hold the driver lock across the read-only dispatch group and DELETE_ARRAY (mirroring ioctl.c:357-371), or refcount PVDevice lifetime for the GET_* walks. Not validated on this guest (no hardware); no fix.diff shipped.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean
per-fix-DF-4332

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md full path:line trace; env.txt: pciconf grep count 0 and 'sysctl: unknown oid hw.hptmv.status'

PoC changes

no runnable PoC possible without the HBA; evidence is the code trace plus guest hardware-absence capture

Verified recommended fix

Serialize the read-only ioctl group and DELETE_ARRAY under lock_driver()/lock_driver_idle like the mutating ioctls (ioctl.c:357-371)

Verdict

INCONCLUSIVE - hardware absent. The unlocked-walk defect is established by inspection: ioctl.c:316-329 dispatches the entire read-only GUI ioctl family to hpt_default_ioctl with no driver lock and ioctl.c:219-263 (DELETE_ARRAY) frees array/member tables equally unlocked, while gui_lib.c (self-declared unsynchronized, header lines 36-37) dereferences pMember[]/pParent chains in get_array_flag/calc_rebuild_progress/get_array_info/hpt_get_logical_devices. Concurrent root sysctl writes then race a GET_DEVICE_INFO walk against array teardown -> UAF read -> panic or kernel-pointer copyout. The guest has no HighPoint controller (pciconf: 0 devices; hw.hptmv.status OID absent - registered from attach), and without one the path NULL-derefs before gui_lib.c (known DF-1853). Root-only both sides, narrow window: Low.