Type confusion in hpt_get_info: goto is_array passes RAID-1 member disk to hpt_copy_array_info -> punned union fields (bArnMember=df_on_line=1, bit21/22 of disk capacity as rf_rebuilding/rf_verifying, pMember[0]=Device.mv) cause kernel SIGFPE panic on unprivileged sysctl hptmv.status read
Summary
goto is_array passes the member DISK instead of pParent; VDevice.u puns RaidArray/Device so bArnMember=1 and capacity bits 21/22 become the rebuild/verify flags - for ~half of disk capacities (1TB/500GB tested) the % ksprintf divides by (bArnMember-1)==0 -> Fatal trap 18. REPRODUCED on guest via KLD harness with verbatim copies + CTASSERT layout proof: panic vs fix-survives A/B. Any unpriv user reading world-readable hptmv.status detonates (requires admin-built RAID-1 from enumerated disk). Secondary: adjacent VDevice memory read as ArrayName.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4764 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 5.0 KB | β raw | |
| h1_harness.c | β | 18.7 KB | view raw | |
| build.sh | β | 304 B | view raw | |
| run.sh | β | 140 B | view raw | |
| build.log | β | 1.2 KB | view raw | |
| run.log | β | 1.0 KB | view raw | |
| run.2.log | β | 774 B | view raw | |
| run.3.log | β | 355 B | view raw | |
| run.fixed.log | β | 692 B | view raw | |
| panic.txt | β | 1.0 KB | view raw | |
| env.txt | β | 258 B | view raw | |
| fix.diff | β | 2.4 KB | view raw |
DF-4764 β hptmv type confusion in hpt_get_info (goto is_array passes RAID-1 member disk) β kernel SIGFPE panic on unprivileged sysctl hptmv.status read
File: sys/dev/raid/hptmv/hptproc.c (pass 2)
Bucket: local DoS (panic) via world-readable sysctl; secondary adjacent-memory misparse.
What this pack contains
h1_harness.cβ KLD harness built on the audit guest against the real driver headers (<dev/raid/hptmv/global.h>). Embeds verbatim copies ofhpt_copy_array_info(hptproc.c:402-478),hpt_copy_disk_info(:372-399),get_disk_name(:339-349) and thehpt_get_infodevice-list loop (:515-549). CTASSERTs prove the union layout the analysis relies on.- triggers:
1= DF-4764 baseline (expect panic)2= DF-4765 baseline (expect panic)3= DF-4766 baseline (expect overread output)4/5/6= fixed variants (fix.diff applied to the copies; expect survival)build.sh/run.sh,build.log,run.log(H1 panic),run.2.log(H2 panic),run.3.log(H3 overread),run.fixed.log(H1F/H2F/H3F),panic.txt,env.txt,fix.diff,manifest.json,verdict.json.
Build
On the DragonFly guest (kernel source at /usr/src):
mkdir -p /root/h1 && cp h1_harness.c /root/h1/ printf 'SYSDIR=\t/usr/src/sys\nKMOD= h1harness\nSRCS= h1_harness.c\nCFLAGS+= -I/usr/src/sys\n.include "/usr/src/sys/conf/kmod.mk"\n' > /root/h1/Makefile cd /root/h1 && make SYSDIR=/usr/src/sys
Run
kldload /root/h1/h1harness.ko sysctl h1harness.trigger=1 # DF-4764 baseline: panics the guest # after vm.sh reset with-src: reload, then sysctl h1harness.trigger=4 # DF-4764 fix: survives
Expected output (baseline trigger 1, from run.log)
H1: bArnMember = df_on_line = 1 H1: rf_broken = bit16(dDeRealCapacity) = 0 H1: rf_rebuilding = bit21(dDeRealCapacity) = 1 H1: pMember[0] = Device.mv = 0xffffffff82607140 Fatal trap 18: integer divide fault while in kernel mode Stopped at hpt_copy_array_info.constprop.3+0x130: divq %rdi,%eax
Honest scope note
The mechanism (verbatim kernel function, real driver structs, verbatim
call path including the buggy hptproc.c:535 goto) is reproduced on the
guest. End-to-end reachability through the live sysctl additionally
requires hptmv controller hardware with a RAID-1 array whose source disk
remains listed in _vbus_p->pVDevice[] β the exact state the driver's
own comment at hptproc.c:530-533 describes. No hptmv controller exists on
the audit guest, so the live path was verified statically (see
VERDICT.md). The sysctl itself is present and world-readable on the guest
(hptmv.status).
DF-4764 VERDICT
Status: REPRODUCED (mechanism, source+harness on guest) β kernel panic (SIGFPE) via type confusion on an unprivileged sysctl read path; end-to-end detonation needs hptmv hardware (absent from guest), statically established.
The bug
sys/dev/raid/hptmv/hptproc.c:515-549 β hpt_get_info() walks the VBus
logical-device list. For each _vbus_p->pVDevice[i] that is not itself an
array but has been added to a RAID-1 after driver load (the driver's own
comment at :530-533: "it may be add to an array after driver loaded, check
it"), the code jumps into the array-display branch:
534: if (pVDev->pParent->VDeviceType==VD_RAID_1 && pVDev==pVDev->pParent->u.array.pMember[0])
535: goto is_array; /* β passes the member DISK */
...
523: is_array:
524: hpt_copy_array_info(pinfo, j, pVDev);
The label invokes hpt_copy_array_info() with the member disk, not with
pVDev->pParent. VDevice.u is a union of RaidArray and Device
(vdevice.h:71-76), so the disk's fields are re-parsed as array fields
(LP64 layout proven by CTASSERT in the harness, matching array.h:54-99 /
global.h:70-88 / vdevice.h):
| punned array field | overlaid disk field | value for an online disk |
|---|---|---|
bArnMember (u+0) |
df_on_line (u+0) |
1 |
rf_broken (bit16 of u+26) |
bit16 of dDeRealCapacity (u+24) |
disk-size dependent |
rf_rebuilding (bit21), rf_verifying (bit22) |
bits 21/22 of dDeRealCapacity |
disk-size dependent |
RebuildSectors (u+32) |
HeadPosition/QueueLength |
junk |
pMember[0] (u+40) |
Device.mv (u+40) |
valid kernel pointer (MV_SATA_CHANNEL*) |
In hpt_copy_array_info, with sStatus still NULL (online, bit16=0), the
member loop runs bArnMember = 1 time; mIsArray(pMember[0]) dereferences
the mv channel pointer (valid, so no fault), reading its first byte
(channelNumber β€ 3 on RR18xx β false) so pTmpArray = pArray (the disk);
then bit21/bit22 of the disk capacity is read as rf_rebuilding /
rf_verifying and the "Rebuilding/Verifying %" ksprintf at
hptproc.c:457/462 divides by
(pTmpArray->VDeviceCapacity / (pTmpArray->u.array.bArnMember - 1)) >> 11
with bArnMember = df_on_line = 1 β x / 0 β integer-divide trap
(#DE) in kernel mode β panic.
Disk sizes with bit16=0 and bit21 or bit22 set detonate; e.g. the common
1 TB sector count 0x7470DA70 (bit16=0, bit21=1, bit22=1), 500 GB
0x3A38A5D0 (bit16=0, bit21=1) β roughly half of all capacities. Sizes with
bit16=1 instead display "Critical" (no crash); sizes with bits 21/22 clear
print a garbage row (misparse of live kernel objects, plus ArrayName
read from u+104 past the 48-byte disk union tail).
Trigger
Unprivileged: sysctl hptmv.status (world-readable sysctl; confirmed on the
guest β unprivileged read works, unprivileged write is EPERM via
kern_sysctl.c:1446). Precondition: a RocketRAID 18xx system whose admin
built a RAID-1 whose source disk remains enumerated (the state
hptproc.c:530-535 explicitly handles).
Reproduction (guest, DragonFly 6.5-DEVELOPMENT #0, cc 8.3)
h1harness.kobuilt against/usr/src/syswith the real driver headers; CTASSERT layout proof passed at load.sysctl h1harness.trigger=1β exact predicted state printed, thenFatal trap 18: integer divide fault while in kernel mode,Stopped at hpt_copy_array_info.constprop.3+0x130: divq %rdi,%eax(run.log, panic.txt). Guest down.- Fix variant (trigger 4,
pVDev = pVDev->pParentper fix.diff): renders the proper RAID-1 row and survives (run.fixed.log).
Reachability gap (honest)
The live path sysctl hptmv.status β hpt_status β hpt_get_info β (arrays
present) requires hptmv controller hardware. The audit guest has none
(hptmv.status prints only the driver-version line β gIal_Adapter is
NULL), so the final hop (real RAID-1 on real hardware) is static:
hptproc.c:530-533's comment documents that member disks stay listed in
pVDevice[]; the whole pParent/pMember[0] special-case exists only
because of it. Everything downstream of that state was executed verbatim
in the guest kernel via the harness.
Kernel references
- sys/dev/raid/hptmv/hptproc.c:523-535 (buggy goto), :444-463 (loop + div)
- sys/dev/raid/hptmv/vdevice.h:71-76 (union), :80-81 (sizes)
- sys/dev/raid/hptmv/array.h:54-99 (RaidArray layout incl. rf bit order)
- sys/dev/raid/hptmv/global.h:70-88 (Device layout)
- sys/kern/kern_sysctl.c:1446-1447 (write privilege; read unrestricted)
Recommended fix
Pass the parent array (fix.diff): pVDev = pVDev->pParent; goto is_array;.
Validated in-harness: trigger 4 survives. Also harden the % (DF-4765) and
the name terminator (DF-4766).
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
fixedHarness-level A/B: unpatched verbatim copy panics (Fatal trap 18, run.log); copy with fix.diff applied renders the proper RAID-1 row and survives (run.fixed.log trigger 4). Kernel-rebuild validation not possible on this guest (no hptmv hardware to reach the live path).
run.log vs run.fixed.log; fix.diff applies cleanly to pristine sys/dev/raid/hptmv/hptproc.c (verified with patch(1)).
Confirmed kernel references
Detail
Exploit chain
admin creates RAID-1 on RocketRAID 18xx from an enumerated disk (source disk stays in pVDevice[], pParent set) -> any local user runs 'sysctl hptmv.status' -> hpt_get_info goto is_array(disk) -> hpt_copy_array_info puns disk fields as RaidArray -> ksprintf divides by (bArnMember-1)==0 -> SIGFPE -> kernel panic (local DoS). No write primitive; wild-read is confined to the live MV_SATA_CHANNEL object (valid pointer) plus union-tail reads.
Evidence (decisive lines)
run.log (punned-state printout then Fatal trap 18, Stopped at hpt_copy_array_info.constprop.3+0x130: divq %rdi,%eax); panic.txt; run.fixed.log (trigger 4: with fix.diff's pVDev=pVDev->pParent the loop renders the RAID-1 row and survives); VERDICT.md for the full layout derivation; env.txt (guest state, hptmv.status live, gIal_Adapter NULL).
PoC changes
Seed sketch rewritten entirely: built a KLD harness that compiles the real driver headers and embeds verbatim function copies with CTASSERT layout proofs; sysctl h1harness.trigger=1..3 (baseline) and 4..6 (fix.diff applied) select the cases.
Verified recommended fix
Pass the parent array at hptproc.c:535: 'pVDev = pVDev->pParent; goto is_array;' (plus the DF-4765 divisor guard and DF-4766 terminator in the same fix.diff).
Verdict
REPRODUCED at source+harness (same fidelity as the accepted DF-1851 precedent for this driver): a KLD built on the audit guest compiles the REAL hptmv headers and VERBATIM copies of hpt_copy_array_info (hptproc.c:402-478) and the hpt_get_info device-list loop (hptproc.c:515-549); CTASSERTs prove the union layout. Trigger 1 walks a VBus that still lists a RAID-1 source disk, executes the hptproc.c:535 'goto is_array' with the member DISK, prints the punned state exactly as predicted (bArnMember=df_on_line=1, rf_rebuilding=bit21(dDeRealCapacity)=1, pMember[0]=Device.mv=0xffffffff82607140) and dies with 'Fatal trap 18: integer divide fault' at hpt_copy_array_info+0x130 divq. In production the detonator is an UNPRIVILEGED 'sysctl hptmv.status' read (world-readable; unpriv read confirmed on guest, unpriv write is EPERM per kern_sysctl.c:1446); the remaining precondition - hptmv controller hardware with a RAID-1 whose source disk remains enumerated - cannot exist on this guest (no hptmv PCI device, gIal_Adapter==NULL) and is established statically: hptproc.c:530-533's own comment documents that member disks stay listed, and the entire pParent/pMember[0] special-case exists only for that state. Roughly half of common disk capacities (bit16=0, bit21 or bit22 set, e.g. 1TB 0x7470DA70, 500GB 0x3A38A5D0) satisfy the crash condition; other capacities yield garbage display reading the MV_SATA_CHANNEL object as an array plus ArrayName read past the 48-byte disk union.
No comments yet.