# DF-1529 — hptmv hpt_create_array_v2 unvalidated Members[1..nDisk]

## Verdict
**REPRODUCED (source-level harness).** The bug is real; impact ceiling is a
near-arbitrary kernel-address deref + write (kernel function-pointer
overwrite via `pfnDeviceFailed`) reachable from a `root` write to the
`hw.hptmv.status` sysctl with `HPT_IOCTL_CREATE_ARRAY[_V2]` and an
attacker-chosen `Members[1..nDisk-1]` value. The guest has no HighPoint
RocketRAID 1820 (`pci 1103:1820` Marvell-based HBA) so the kernel path
cannot be exercised here. Harness demonstrates the validation gap using
the genuine control-flow from `gui_lib.c`. fix.diff applies cleanly and
`nativekernel` succeeds (rc=0).

## Mechanism (`sys/dev/raid/hptmv/gui_lib.c`)
1. Lines 571-572: `if (pParam->nDisk > MAX_MEMBERS) return INVALID_DEVICEID;`
   bounds `nDisk` to `MAX_MEMBERS = 8` (`global.h:52`).
2. **Lines 573-585: the entire per-member validation block is COMMENTED OUT**
   — the comment says "check in verify_vd" but no such validation runs
   before the deref sites below.
3. Line 586: `_vbus_p = (ID_TO_VDEV(pParam->Members[0]))->u.disk.pVBus;` —
   `Members[0]` is the only entry validated (by the caller
   `hptmv:` `ioctl.c:337-357` runs `check_VDevice_valid` only on
   `Members[0]`).
4. Lines 644-654: `for(i=0..nDisk-1) pArray->u.array.pMember[i] =
   ID_TO_VDEV(pParam->Members[i]); pMember[i]->bSerialNumber = i;
   pMember[i]->pParent = pArray; UnregisterVDevice(pMember[i]);` — derefs
   **Members[1..nDisk-1]** without any check.
5. Line 682 RAID1+0 path: `pChild->u.array.pMember[j] =
   ID_TO_VDEV(pParam->Members[i*2+j])` — same defect.
6. Line 685: `pChild->u.array.pMember[j]->pfnDeviceFailed =
   pfnDeviceFailed[pChild->VDeviceType];` — writes a kernel function
   pointer to the attacker-chosen address.
7. Line 742: `pDisk = ID_TO_VDEV(pParam->Members[i]); fDeReadWrite(...)`
   — disk-I/O on a forged PDevice.
8. `ID_TO_VDEV(id)` (`osbsd.h:246`) is `(PVDevice)((gIal_Adapter &
   0xffffffff00000000) | id)` — user controls the low 32 bits, so the
   deref target is `(kernel_heap_base & 0xffffffff00000000) | user_id`.
9. V1 wrapper `hpt_create_array` lines 813-827 has the same defect.

## Harness proof (`harness.c`)
Replicates the genuine ID_TO_VDEV decode and the line-644-654 loop:

```
nDisk=3
Members[0] = 0xffdfd870 (validated by caller)
Members[1] = 0x41414141 (NOT validated)
Members[2] = 0x42424242 (NOT validated)
Per-member validation: DISABLED (commented-out block at line 573-585)

Walking the buggy loop at gui_lib.c:644-654:
  i=0: pMember[i] = 0x7ffffdfd870  (== &legit, safe)
  i=1: pMember[i] = 0x7fff41414141  -> UNCHECKED DEREF
  i=2: pMember[i] = 0x7fff42424242  -> UNCHECKED DEREF
```

## Exploit-chain note
Trigger requires `root` to write `hw.hptmv.status` sysctl AND an `hptmv`
adapter present. This is **root → kernel** code-exec, which is a hardening
gap (root could equally `kldload` its own module); the unpriv→root chain
is not present. That said, the primitive is a near-arbitrary kernel
function-pointer write at a user-chosen address, which on a host that does
have `hptmv` and grants the RAID-management GUI to a less-privileged
operator, is a credible privilege-boundary escalation. Documented as
primitive characterization.

## PoC changes
- Original folder was README only.
- Added harness.c, build/run scripts, env, logs, fix.diff, VERDICT.md,
  manifest.json.

## Fix
`fix.diff` uncomments and restores the per-member validation block, running
`check_VDevice_valid`, `mIsArray`, `vf_online`, and same-VBus checks for
**every** `Members[i]` before any deref. Matches the finding markdown
proposal ("validate each Members[i] before deref").

## Fix-validation
`patch -p1 --forward` succeeds (hunks at 570 + 590). `nativekernel`
completes with rc=0 (`fix_build.log`). No run-time exercise possible
because no `hptmv` HBA on the guest → `fix_status: "not_testable"`. Diff
applies and compiles; restored block runs every check before the deref
sites.
