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

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:

  1. hpt_create_array_v2 at gui_lib.c:571 only checks nDisk > MAX_MEMBERS β€” it does NOT reject nDisk < 2
  2. For AT_RAID0 with nDisk==1, the "simple" path at gui_lib.c:617-618 sets bArnMember=1 and the VD_RAID_0 capacity loop at gui_lib.c:754-765 iterates exactly once (pMember[0] is set), so creation succeeds and returns a valid array ID
  3. hpt_set_array_state (ioctl.c:461) only checks mIsArray (true for RAID0 β€” vdevice.h:106 VDeviceType > VD_SINGLE_DISK) and vf_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/:

  1. Enumerate a valid DEVICEID via HPT_IOCTL_GET_LOGICAL_DEVICES (Code 4) to obtain a real disk/array ID for Members[0].
  2. Send HPT_IOCTL_CREATE_ARRAY_V2 (Code 33) with a packed CREATE_ARRAY_PARAMS_V2 struct: ArrayType=AT_RAID0(1), nDisk=1, BlockSizeShift=5 (valid 5..12 range per MinBlockSizeShift), CreateFlags=0, Members[0]=<enumerated valid ID>. The kernel creates a RAID0 array with bArnMember=1 and returns its new DEVICEID in the output buffer.
  3. Send HPT_IOCTL_SET_ARRAY_STATE (Code 15) with a HPT_SET_STATE_PARAM struct: idArray=<new array ID from step 2>, state=MIRROR_REBUILD_START(1).
  4. hpt_set_array_state passes mIsArray + vf_online checks and calls hpt_queue_dpc(hpt_rebuild_data_block, ..., REBUILD_PARITY) at ioctl.c:489.
  5. Within a few ticks the DPC fires; hpt_rebuild_data_block executes 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).

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;
  • 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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1682 Β· 4 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-only confirmation + mechanism + fix
↓ download 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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: 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.
↓ fix.diffcombined build rc=0

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.