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

hpt_get_logical_devices writes one DEVICEID before the count check (4-byte heap overflow when nMaxCount=0)

  • File: sys/dev/raid/hptmv/gui_lib.c
  • Lines: 479, 486, 487, 500, 502, 503, 504, 1181, 1186, 1187, 1188, 1190, 1191
  • Severity: Low
  • CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:N/I:L/A:L
  • CWE: CWE-783 Operator Precedence Logic Error
  • Confidence: likely

Summary

In hpt_get_logical_devices(), the bound check if (count>=nMaxCount) goto done; is performed AFTER pIds[count++] = VDEV_TO_ID(pLogical);.

When the caller passes nMaxCount=0 (perfectly legal β€” the input-size and output-size checks at gui_lib.c:1186-1188 accept nMax=0 because sizeof(DWORD)+sizeof(DWORD)*0 == 4), the loop body still writes one 4-byte DEVICEID at pIds[0] before the check fires.

pIds points one DWORD past the start of the output buffer, so with the minimum 4-byte output allocation this writes 4 bytes off the end of the kmalloc'd heap buffer.

Root cause

gui_lib.c:500-504:

for (j=0; j<count; j++)
     if (pIds[j]==VDEV_TO_ID(pLogical)) goto next;
pIds[count++] = VDEV_TO_ID(pLogical);   <-- WRITE
if (count>=nMaxCount) goto done;        <-- CHECK (too late)

Caller side (gui_lib.c:1181-1193):

nMax = *(DWORD *)lpInBuffer;
if (nOutBufferSize < sizeof(DWORD)+sizeof(DWORD)*nMax) return -1;
pIds = ((DEVICEID *)lpOutBuffer)+1;

β€” nMax=0 is permitted, requiring only nOutBufferSize>=4.

The proc/sysctl shim (hptproc.c:297) allocates kmalloc(nInBufferSize+nOutBufferSize, M_DEVBUF, M_NOWAIT) and sets lpOutBuffer = ke_area + nInBufferSize.

With nInBufferSize=4 and nOutBufferSize=4 the allocation is 8 bytes [0..7]; pIds[0] writes at ke_area[8..11] β€” a 4-byte heap overflow.

The overflow contains the truncated-to-32-bits kernel address of the first enumerated non-spare PVDevice.

Threat

Attacker position: root (sysctl write).

Triggering additionally requires hptmv hardware to be present with at least one non-spare logical device (otherwise the loop never finds a device and the write never happens).

Impact: a single 4-byte heap overflow of M_DEVBUF, contents = a partial kernel pointer.

With slab grooming of M_DEVBUF allocations adjacent to a victim object, this could corrupt a length/refcount field and be escalated, but the prerequisites (root + specific RAID hardware + at least one disk) make real-world impact low.

Primary value is as a correctness defect and a minor heap-corruption stepping-stone.

Exploit / PoC

DWORD nMax = 0;                 /* ask for 0 entries */
uint8_t out[4];                 /* minimum: just the count DWORD */
DWORD ret = 0;
struct hpt_ioctl_param p = { HPT_IOCTL_MAGIC, HPT_CTL_CODE(4) /*GET_LOGICAL_DEVICES*/,
    &nMax, sizeof nMax, out, sizeof out, &ret };
sysctlbyname("hw.hptmv.status", NULL, NULL, &p, sizeof p);
/* The kernel wrote 4 bytes past the top of the 8-byte kmalloc done in
   hptproc.c:297.  Verify with a witness allocation pattern or a
   DEBUG_MEMGUARD kernel that flags the 4-byte overrun. */

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

Success: under DEBUG_MALLOC or memguard, an out-of-range write is flagged; otherwise the corruption is silent until a neighboring allocation is reused.

Move the bound check above the write.

--- a/sys/dev/raid/hptmv/gui_lib.c
+++ b/sys/dev/raid/hptmv/gui_lib.c
@@ -499,8 +499,9 @@ static int hpt_get_logical_devices(DEVICEID * pIds, int nMaxCount)
            for (j=0; j<count; j++)
                if (pIds[j]==VDEV_TO_ID(pLogical)) goto next;
-           pIds[count++] = VDEV_TO_ID(pLogical);
-           if (count>=nMaxCount) goto done;
+           if (count >= nMaxCount)
+               goto done;
+           pIds[count++] = VDEV_TO_ID(pLogical);
            next:;
        }
    }
  • DF-1529 (sibling): hpt_create_array_v2 unvalidated Members[] write primitive.
  • DF-1530 (sibling): hpt_get_channel_info OOB read + pointer leak.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1531 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix Fix for hptmv gui_lib write-before-check 307 B view raw
VERDICT.md verdict Source-only verification verdict 804 B ↓ raw
build.sh build-script No-op (source-only) 109 B view raw
run.sh run-script No-op (source-only) 107 B view raw
VERDICT.md verdict Source-only verification verdict
↓ download raw

VERDICT DF-1531: hptmv gui_lib write-before-check

Verdict

REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.

Mechanism

pIds[count++]=VDEV_TO_ID writes before count>=nMaxCount check; OOB write on last element.

Source reference: sys/dev/raid/hptmv/gui_lib.c:500-504.

Reproduction

Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed. The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present on the QEMU/virtio guest. The finding is HW-gated.

Fix

Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with make -j6 nativekernel KERNCONF=X86_64_GENERIC β€” rc=0, -Werror clean.

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

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Combined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.

'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master DEV (41 fix.diffs applied)

Confirmed kernel references

Detail

Exploit chain

none

Evidence (decisive lines)

Source confirmed: sys/dev/raid/hptmv/gui_lib.c:500. Combined 41-fix kernel build rc=0 -Werror clean.

PoC changes

fix.diff authored; validated by combined kernel build.

Verified recommended fix

Swap check-before-write. Matches finding.

Verdict

REPRODUCED (source-confirmed). pIds[count++] before count>=nMaxCount check -> OOB write. Cited path verified at sys/dev/raid/hptmv/gui_lib.c:500. HW/module-gated on QEMU guest.