DragonFlyBSD Kernel Audit
← triage · dashboard
DF-1851

32-bit integer overflow in hptmv HPT_IOCTL_PARAM size check enables kernel heap overflow

Field Value
ID DF-1851
Status new
Severity High
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
CWE CWE-190 Integer Overflow or Wraparound; CWE-787 Out-of-bounds Write
File sys/dev/raid/hptmv/hptproc.c
Lines 292, 297, 304
Area dev/raid (HighPoint hptmv sysctl)
Confidence certain
Discovered 2026-07-20
Reported pending
Known CVE FreeBSD-SA-09:11.hptmv (same pattern)
CVE match variant

Summary

The size check piop->nInBufferSize+piop->nOutBufferSize > PAGE_SIZE is evaluated in 32-bit unsigned arithmetic because both fields are DWORD (= unsigned int, hptintf.h:748,750). An attacker can choose sizes whose true sum exceeds 2^32 but whose 32-bit-wrapped sum is <= 4096, bypassing the check. The subsequent kmalloc allocates the wrapped (small) size, but copyin uses the original (huge) sizes, producing an unbounded kernel heap overflow.

Root cause

hptproc.c:292:

if (piop->nInBufferSize+piop->nOutBufferSize > PAGE_SIZE) {
    KdPrintE(("User buffer too large\n"));
    return -EINVAL;
}

Both nInBufferSize and nOutBufferSize are DWORD (hptintf.h:748,750 = unsigned int). C performs the addition in unsigned int width, so e.g. nInBufferSize=0x00000FFF (4095) + nOutBufferSize=0xFFFFF001 wraps modulo 2^32 to 0x00000001, which is not > PAGE_SIZE (4096).

hptproc.c:297 then calls:

ke_area = kmalloc(piop->nInBufferSize+piop->nOutBufferSize, M_DEVBUF, M_NOWAIT);

with the same wrapped value 1, returning a minimum-size slab object.

hptproc.c:304:

copyin((void*)(ULONG_PTR)piop->lpInBuffer, ke_area, piop->nInBufferSize);

is then invoked with the unwrapped nInBufferSize=4095, copying 4095 user bytes into the ~16-byte slab allocation — a 4079-byte kernel heap overflow.

hptproc.c:310 Kernel_DeviceIoControl(... ke_area + piop->nInBufferSize, piop->nOutBufferSize ...) passes a destination pointer far past the allocation and the unwrapped 0xFFFFF001 length, giving every ioctl handler in ioctl.c an OOB write window into adjacent kernel heap.

hptproc.c:315 copyout(ke_area + piop->nInBufferSize, lpOutBuffer, piop->nOutBufferSize) reads OOB from kernel heap into userspace (info leak) if Kernel_DeviceIoControl returns 0.

This bug pattern matches FreeBSD-SA-09:11.hptmv; the DragonFlyBSD copy still ships the buggy unbounded addition.

Threat model & preconditions

  • Attacker position: local root (sysctl writes require SYSCAP_NOSYSCTL_WR per kern_sysctl.c:1446 since the OID lacks CTLFLAG_ANYBODY).
  • Privileges gained or impact: controlled kernel heap corruption (slab-object overwrite), kernel OOB read into userspace (info leak), and/or panic. In a jail, capability mode, or any model where root is treated as a trust boundary below the kernel, this is root→kernel arbitrary write. Even in single-tenant use it is a trivial DoS.
  • Required config or capabilities: device hptmv loaded (the sysctl is always registered via hptregister_node whether or not hardware is present).
  • Reachability: sysctl hptmv.status write with a crafted 48-byte HPT_IOCTL_PARAM.

Proof of concept

PoC source: findings/poc/DF-1851/poc_hptmv_heapoverflow.c

Build & run

cc -o poc_hptmv_heapoverflow poc_hptmv_heapoverflow.c
# As root:
./poc_hptmv_heapoverflow

Expected output

kernel: page fault trap, code=0
panic: vm_fault: ... (corrupted slab freelist metadata)

Immediate kernel panic from corrupted slab metadata on the copyin destination or a kfree corruption assert, proving the heap overflow.

Impact

High-severity root→kernel heap overflow. The integer overflow in the size check is trivially exploitable: choose nInBufferSize and nOutBufferSize so their 32-bit sum wraps into [0, 4096]. The kmalloc allocates the wrapped size, but copyin/copyout/Kernel_DeviceIoControl use the original sizes, producing unbounded heap corruption.

Bound each size individually before the sum, so the sum cannot wrap. Cast to size_t to force 64-bit arithmetic.

--- a/sys/dev/raid/hptmv/hptproc.c
+++ b/sys/dev/raid/hptmv/hptproc.c
@@ -289,7 +289,10 @@ hpt_set_info(int length)
            /*
             * map buffer to kernel.
             */
-           if (piop->nInBufferSize+piop->nOutBufferSize > PAGE_SIZE) {
+           if (piop->nInBufferSize > PAGE_SIZE ||
+               piop->nOutBufferSize > PAGE_SIZE ||
+               (size_t)piop->nInBufferSize + (size_t)piop->nOutBufferSize
+                   > PAGE_SIZE) {
                KdPrintE(("User buffer too large\n"));
                return -EINVAL;
            }

Also pass M_ZERO to the kmalloc (see DF-1852) and check the copyin return value (see DF-1852).

References

  • FreeBSD-SA-09:11.hptmv — same integer-overflow pattern in the FreeBSD hptmv driver.
  • DWORD type: mvOs.h:62 (typedef unsigned int DWORD).
  • HPT_IOCTL_PARAM layout: hptintf.h:748-750.

Timeline

  • 2026-07-20 Discovered during automated audit.
  • 2026-07-20 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1851 · 11 files
FileTypeDescriptionSize
harness.c trigger-source userspace logic harness reproducing the buggy arithmetic/control-flow 2.3 KB view raw
VERDICT.md verdict full verification narrative 3.1 KB ↓ raw
build.sh build-script exact build command 88 B view raw
run.sh run-script exact run invocation 41 B view raw
harness_run.log run-log harness output on guest 452 B view raw
fix.diff suggested-fix git-apply-able unified diff 1.3 KB view raw
env.txt environment guest uname, cc version, kernel config 768 B view raw
README.md readme human-facing PoC README 912 B ↓ raw
poc_hptmv_heapoverflow.c trigger-source original PoC skeleton (pre-existing) 2.0 KB 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
README.md readme human-facing PoC README
↓ download raw

DF-1851 PoC

Trigger: write a crafted HPT_IOCTL_PARAM to sysctl hptmv.status where nInBufferSize + nOutBufferSize wraps in 32-bit unsigned to a value <= PAGE_SIZE, bypassing the size check. kmalloc allocates the wrapped (small) size; copyin copies the original (huge) nInBufferSize bytes, overflowing the slab.

Preconditions

  • device hptmv loaded (sysctl is always registered whether or not hardware is present).
  • Root (SYSCAP_NOSYSCTL_WR).

Build

cc -o poc_hptmv_heapoverflow poc_hptmv_heapoverflow.c

Run

sudo ./poc_hptmv_heapoverflow

Expected output

kernel: page fault trap, code=0
panic: vm_fault: ... (corrupted slab freelist metadata)

Fix

See the finding markdown: bound each size individually before the sum and cast to size_t to prevent 32-bit wraparound. Also add M_ZERO to kmalloc (DF-1852) and check copyin return (DF-1852).

VERDICT.md verdict full verification narrative
↓ download raw

DF-1851 — Verification Verdict

Verdict: REPRODUCED (source-confirmed + arithmetic-harness)

The 32-bit integer overflow in the size-sum check is confirmed at sys/dev/raid/hptmv/hptproc.c:292. The harness reproduces the wraparound and shows the resulting 4095-byte heap overflow.

Mechanism

// hptproc.c:292 (under #ifdef SUPPORT_IOCTL, which IS defined — global.h:59)
if (piop->nInBufferSize + piop->nOutBufferSize > PAGE_SIZE)  // 32-bit add
    return -EINVAL;
// :297
ke_area = kmalloc(piop->nInBufferSize + piop->nOutBufferSize, M_DEVBUF, M_NOWAIT);
// :304
copyin(piop->lpInBuffer, ke_area, piop->nInBufferSize);  // 4095-byte copyin

Both sizes are DWORD/unsigned int (hptintf.h:748,750) — the addition wraps mod 2³². With nInBufferSize=4095, nOutBufferSize=0xFFFFF001, the sum wraps to 0x100000001 mod 2³² = 1, which is <= PAGE_SIZE, so the check is bypassed. kmalloc(1) returns a tiny slab; copyin then copies 4095 bytes into it — a 4079-byte heap overflow. The subsequent Kernel_DeviceIoControl(ke_area+nInBufferSize=ke_area+4095, ...) writes OOB, and copyout on success leaks kernel heap.

This matches FreeBSD-SA-09:11.hptmv.

Harness evidence

DF-1851: hptproc.c:292 size check (32-bit unsigned add)
  nInBufferSize = 0x00000fff (4095)
  nOutBufferSize= 0xfffff001 (4294963201)
  32-bit sum    = 0x00000000 (0)
  sum > PAGE_SIZE (4096)? NO (passes — check bypassed)
  -> kmalloc(0) returns a 0-byte slab
  -> copyin(lpInBuffer, ke_area, 4095) overflows it by 4095 bytes
  This is the FreeBSD-SA-09:11.hptmv class: integer-overflow in size sum.

Why no live trigger on this guest (as unprivileged)

The hptmv.status sysctl IS registered on this guest (device hptmv is in X86_64_GENERIC, and the sysctl handler runs at module init regardless of HW). However, writing to sysctls requires SYSCAP_NOSYSCTL_WR (kern_sysctl.c:1446) — maxx gets Operation not permitted. The bug is therefore root-only on this guest: a root write to hptmv.status with the crafted HPT_IOCTL_PARAM would trigger the overflow (and then panic in Kernel_DeviceIoControl since gIal_Adapter is NULL without HW).

This is a valid Phase-6 hard blocker for uid=0: the write is reachable only from an already-root context, so there is no privilege boundary to cross. (Root→kernel is game-over by definition.)

Exploit chain

Not applicable (root-only trigger — no unpriv→root boundary). The bug is a real root→kernel heap-overflow primitive; on a host with actual HPT RAID HW, gIal_Adapter is non-NULL so Kernel_DeviceIoControl runs and the corruption is silently weaponizable rather than panicking.

PoC changes

  • Added harness.c: reproduces the 32-bit wrap arithmetic.
  • Added fix.diff: per-field bounds + size_t cast + M_ZERO + copyin return check.

Fix

fix.diff bounds each size individually before the sum, casts to size_t before adding to prevent 32-bit wrap, adds M_ZERO to the kmalloc, and checks the copyin return.

  • BEFORE: harness shows sum wraps to 0, bypassing the check.
  • AFTER: per-field bounds reject nOutBufferSize > PAGE_SIZE before any addition.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED at compile+boot level: all 13 fixes applied cleanly to /usr/src, built into a single X86_64_GENERIC kernel (make -j6 nativekernel rc=0, kernel linked), installed as /boot/kernel/kernel, and the patched kernel booted cleanly (kern.version #1 vs baseline #0). The live PoC cannot run on this guest (HW/config-gated per the verdict), so before/after is at source+harness level: baseline harness: '32-bit sum = 0x00000000 ... passes — check bypassed' | patched: per-field bounds reject nOutBufferSize>PAGE_SIZE before addition

baseline (#0 unpatched): baseline harness: '32-bit sum = 0x00000000 ... passes — check bypassed'
patched (#1 kernel, all 13 fixes, booted clean): patched: per-field bounds reject nOutBufferSize>PAGE_SIZE before addition
kernel sha256 c3fff85f... (patched, booted) vs 5dc83dac... (baseline #0)
↓ fix.diffDragonFly 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 19:12:20 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

Root-only trigger (sysctl write requires SYSCAP_NOSYSCTL_WR; maxx gets EPERM). Valid Phase-6 hard blocker: write reachable only from already-root context, so no privilege boundary to cross. No uid=0 escalation claimed. Primitive characterized in harness.c (32-bit wrap arithmetic). Live ceiling: root->kernel heap overflow (game-over by definition); on host with real HPT HW, gIal_Adapter is non-NULL so Kernel_DeviceIoControl runs and corruption is silently weaponizable.

Evidence (decisive lines)

DF-1851: hptproc.c:292 size check (32-bit unsigned add)
  nInBufferSize = 0x00000fff (4095)
  nOutBufferSize= 0xfffff001 (4294963201)
  32-bit sum    = 0x00000000 (0)
  sum > PAGE_SIZE (4096)? NO (passes — check bypassed)
  -> kmalloc(0) returns a 0-byte slab

PoC changes

Added harness.c (32-bit wrap arithmetic) and fix.diff (per-field bounds + size_t cast + M_ZERO + copyin return check).

Verified recommended fix

fix.diff bounds each size individually before the sum, casts to size_t, adds M_ZERO, checks copyin return. supersedes finding proposal by also adding per-field bounds and copyin check.

Verdict

REPRODUCED at source+harness. hptproc.c:292 (under #ifdef SUPPORT_IOCTL which IS defined in global.h:59) does 'if (nInBufferSize+nOutBufferSize > PAGE_SIZE)' with both sizes DWORD (32-bit unsigned) — addition wraps mod 2^32. Harness with nIn=4095 nOut=0xFFFFF001 shows sum wraps to 0, bypasses check; kmalloc(0) then copyin(4095) -> 4095-byte heap overflow. FreeBSD-SA-09:11.hptmv class. The hptmv.status sysctl IS registered on this guest (device hptmv in GENERIC) but writing requires root (SYSCAP_NOSYSCTL_WR — maxx gets EPERM). Valid Phase-6 hard blocker for uid0: root-only trigger.