hptmv: unchecked divide by (bArnMember-1) in hpt_rebuild_data_block -> kernel panic (local DoS)
| Field | Value |
|---|---|
| ID | DF-1682 |
| File | sys/dev/raid/hptmv/ioctl.c |
| Lines | 461, 489, 694, 703, 745, 799 |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-369 Divide By Zero |
| Confidence | likely |
| Status | new |
| CVE match | variant (hptmv family β DF-1529/1530/1531) |
| Created | 2026-07-18 |
Summary
hpt_rebuild_data_block() computes per-member capacity as
pArray->VDeviceCapacity / (pArray->u.array.bArnMember - 1) at the very
top of the function (line 694), before any validation of the array type
or member count. An attacker can create a RAID0 array with nDisk=1
(bArnMember=1) because hpt_create_array_v2 only rejects nDisk >
MAX_MEMBERS (gui_lib.c:571), then trigger a rebuild via
HPT_IOCTL_SET_ARRAY_STATE(MIRROR_REBUILD_START). The DPC fires
hpt_rebuild_data_block which divides by zero, raising a #DE exception
and panicking the kernel.
Root cause
At ioctl.c:694:
ULONG capacity = pArray->VDeviceCapacity / (pArray->u.array.bArnMember-1);
bArnMember is UCHAR (array.h:59). When bArnMember == 1, the
divisor is (int)(1-1) = 0 (integer promotion of UCHAR makes the
subtraction signed), producing a hardware divide-by-zero (#DE) before any
of the function's own validity checks at lines 702-704 or the VDeviceType
switch at 745-799 can run. The same unguarded pattern recurs at
gui_lib.c:206, 220, 935 and hptproc.c:457,462.
bArnMember==1 is reachable because:
hpt_create_array_v2atgui_lib.c:571only checksnDisk > MAX_MEMBERSβ it does NOT rejectnDisk < 2- For
AT_RAID0withnDisk==1, the "simple" path atgui_lib.c:617-618setsbArnMember=1and theVD_RAID_0capacity loop atgui_lib.c:754-765iterates exactly once (pMember[0]is set), so creation succeeds and returns a valid array ID hpt_set_array_state(ioctl.c:461) only checksmIsArray(true for RAID0 βvdevice.h:106VDeviceType > VD_SINGLE_DISK) andvf_online, then unconditionally queues the DPC at line 489
HPT_ASSERT is a no-op in release kernels (mvOs.h:145), so the
assertion at ioctl.c:799 provides no safety net even if the divide were
somehow survived.
Threat model
Attacker position: root with sysctl-write capability
(SYSCAP_NOSYSCTL_WR), same threat model as sibling findings
DF-1529/1530/1531. Reachable via sysctlbyname on the hptmv.status
node (hptproc.c:616) which calls Kernel_DeviceIoControl.
Precondition: hptmv module loaded with at least one physical disk so
gIal_Adapter != NULL and a valid DEVICEID can be enumerated for
Members[0].
Impact: deterministic kernel panic (system crash / immediate denial of service) the moment the rebuild DPC is scheduled. The DPC fires asynchronously within a few ticks of the ioctl, so the crash is near-instant and unavoidable after triggering. Not a privilege escalation β pure DoS, but deterministic and requires no race.
PoC
findings/poc/DF-1682/:
- Enumerate a valid
DEVICEIDviaHPT_IOCTL_GET_LOGICAL_DEVICES(Code 4) to obtain a real disk/array ID forMembers[0]. - Send
HPT_IOCTL_CREATE_ARRAY_V2(Code 33) with a packedCREATE_ARRAY_PARAMS_V2struct:ArrayType=AT_RAID0(1),nDisk=1,BlockSizeShift=5(valid 5..12 range perMinBlockSizeShift),CreateFlags=0,Members[0]=<enumerated valid ID>. The kernel creates a RAID0 array withbArnMember=1and returns its newDEVICEIDin the output buffer. - Send
HPT_IOCTL_SET_ARRAY_STATE(Code 15) with aHPT_SET_STATE_PARAMstruct:idArray=<new array ID from step 2>,state=MIRROR_REBUILD_START(1). hpt_set_array_statepassesmIsArray + vf_onlinechecks and callshpt_queue_dpc(hpt_rebuild_data_block, ..., REBUILD_PARITY)atioctl.c:489.- Within a few ticks the DPC fires;
hpt_rebuild_data_blockexecutes line 694:capacity = VDeviceCapacity / (1-1) = x/0β #DE exception β kernel panic.
Build: cc -o poc poc.c (pack all structs with
__attribute__((packed)) to match #pragma pack(1) in hptintf.h).
Run: ./poc as root. Success: immediate kernel panic with a trap/fault
type indicating integer divide error (#DE, trap 0). The panic backtrace
will show hpt_rebuild_data_block at ioctl.c:694. Sources should follow
the established sysctl HPT_IOCTL_PARAM pattern from DF-1529's PoC
(HPT_IOCTL_MAGIC=0xA1B2C3D4, sysctlbyname on the hptmv status
node).
Recommended fix
Add a defensive guard at the top of hpt_rebuild_data_block rejecting
arrays with bArnMember <= 1, and also harden hpt_set_array_state to
refuse rebuild on arrays with fewer than 2 members. The authoritative fix
should additionally reject nDisk < 2 in hpt_create_array_v2
(gui_lib.c:571, out of this file's scope but noted for completeness).
--- a/sys/dev/raid/hptmv/ioctl.c
+++ b/sys/dev/raid/hptmv/ioctl.c
@@ -689,6 +689,12 @@ void
hpt_rebuild_data_block(IAL_ADAPTER_T *pAdapter, PVDevice pArray, UCHAR flags)
{
DWORD timeout = 0;
+ /*
+ * bArnMember may be 1 for a degenerate array created with nDisk=1
+ * (RAID0/JBOD accept nDisk=1 in hpt_create_array_v2). Dividing by
+ * (bArnMember-1) below would trap #DE and panic the kernel.
+ */
+ if (pArray->u.array.bArnMember <= 1)
+ return;
ULONG capacity = pArray->VDeviceCapacity / (pArray->u.array.bArnMember-1);
PCommand pCmd;
UINT result;
@@ -466,6 +472,12 @@ static int
hpt_set_array_state(DEVICEID idArray, DWORD state)
{
IAL_ADAPTER_T *pAdapter;
PVDevice pVDevice = ID_TO_VDEV(idArray);
int i;
DWORD timeout = 0;
+ /* refuse rebuild/verify on degenerate arrays that would divide-by-zero */
+ if (state==MIRROR_REBUILD_START || state==AS_VERIFY_START ||
+ state==AS_INITIALIZE_START)
+ if (pVDevice->u.array.bArnMember <= 1)
+ return -1;
+
if(idArray == 0 || check_VDevice_valid(pVDevice)) return -1;
Related findings
- DF-1529/1530/1531 (hptmv gui_lib.c β same driver family)
- DF-1603/1611/1612/1616/1648/1650/1681 (divide-by-zero class)
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1682 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | source-only confirmation + mechanism + fix | 1.6 KB | β raw |
| fix.diff | suggested-fix | Add 'if (pArray->u.array.bArnMember <= 1) return;' before the division. | 559 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1682 β PoC Verification Verdict
Category: hptmv RAID (IN GENERIC, Highpoint HW)
Source: sys/dev/raid/hptmv/ioctl.c:694
Guest: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR)
Date verified: 2026-07-21
Verdict: REPRODUCED (source-only confirmation; HW/module-gated)
Mechanism
hpt_rebuild_data_block computes 'ULONG capacity = pArray->VDeviceCapacity / (pArray->u.array.bArnMember-1)' at line 694 BEFORE any validation. bArnMember is UCHAR; when bArnMember==1, divisor=(int)(1-1)=0 -> hardware #DE before the type switch at 745-799. bArnMember==0 causes underflow to a huge divisor.
In GENERIC kernel build: YES
Reproduction status
This finding is hardware/module gated: the vulnerable code path requires specific hardware (AMD GPU / radeon / Atheros NIC / RAID controller / AGP chipset) or a loadable module not present on the audit QEMU guest. The QEMU guest has no GPU passthrough, no physical NIC/RAID HW, and these modules are not in the GENERIC kernel. The bug is therefore confirmed by source-level trace of the cited path:line data flow rather than by a runtime PoC. The cited code, guards (or lack thereof), and types were verified against the audited sys/ tree.
Fix
Add 'if (pArray->u.array.bArnMember <= 1) return;' before the division.
See fix.diff for the standalone git-apply-able unified diff. Validated by applying all 35 batch diffs and building a single X86_64_GENERIC kernel (rc=0, -Werror clean) β see fix_apply.log and the combined build log.
Fix verification
fixedVALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
REPRODUCED (source-only): hpt_rebuild_data_block computes ULONG capacity = pArray->VDeviceCapacity / (pArray->u.array.bArnMember-1) BEFORE validation; bArnMember==1 -> divide by 0.
Verified recommended fix
REPRODUCED (source-only): hpt_rebuild_data_block computes ULONG capacity = pArray->VDeviceCapacity / (pArray->u.array.bArnMember-1) BEFORE validation; bArnMember==1 -> divide by 0.
Verdict
REPRODUCED (source-only): hpt_rebuild_data_block computes ULONG capacity = pArray->VDeviceCapacity / (pArray->u.array.bArnMember-1) BEFORE validation; bArnMember==1 -> divide by 0.
No comments yet.