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:
- DoS / panic: send
bus = 0x80000000(or any value whose computed kernel address is unmapped) β the read ofmvChannel[bus].onlinetouches a wild address and the kernel page-faults. - Info leak: send small positive values
bus = 9, 10, ...β reads adjacent fields ofIAL_ADAPTER_TpastmvChannel[8]; if the byte at theonline-field offset ofmvChannel[bus]happens to equal 1 (MV_TRUE), the low 32 bits of&pAdapTemp->VDevices[bus]are returned, revealing (a) the low 32 bits of theIAL_ADAPTER_Tbase address (heap layout / KASLR partial bypass) and (b) the in-struct stride, useful for forgingDEVICEIDs to use in thecreate_arrayprimitive 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.
Recommended fix
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)
Related findings
- DF-1529 (sibling):
hpt_create_array_v2unvalidated Members[] write primitive. - DF-1531 (sibling):
hpt_get_logical_deviceswrite-before-check heap overflow.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1530 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 β 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 infix_apply.log). - Compiled with
-Werroras part of the combinedmake -j6 nativekernel KERNCONF=X86_64_GENERICbuild (kernel build rc=0; seemanifest.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
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- h
- p
- t
- m
- v
- /
- g
- u
- i
- _
- l
- i
- b
- .
- c
- :
- 1
- 1
- 6
- 4
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- h
- p
- t
- m
- v
- /
- g
- u
- i
- _
- l
- i
- b
- .
- c
- :
- 1
- 1
- 7
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- h
- p
- t
- m
- v
- /
- g
- u
- i
- _
- l
- i
- b
- .
- c
- :
- 4
- 6
- 9
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- h
- p
- t
- m
- v
- /
- o
- s
- b
- s
- d
- .
- h
- :
- 1
- 6
- 1
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.
No comments yet.