DragonFlyBSD Kernel Audit
← triage · dashboard
DF-1578

Heap OOB write in INQUIRY/READ_CAPACITY/SERVICE_ACTION_IN via missing dxfer_len check

Summary

hptrr_osm_bsd.c:582 memset(data_ptr, 0, dxfer_len) ONLY use of dxfer_len. 585-597 write fixed offsets INQUIRYDATA: byte4 AdditionalLength, byte7 CommandQueue, bytes8-15 VendorId memcpy, bytes16-31 ProductId memcpy, bytes32-35 ProductRevisionLevel. sizeof(INQUIRYDATA)=96; last write at byte35. READ_CAPACITY 613-620 writes rbuf[0..7]. SERVICE_ACTION_IN 631-642 writes rbuf[0..11]. NO comparison dxfer_len vs actual bytes written. CCB via /dev/passN mode 0600 root with dxfer_len=0 -> writes 36B past NULL/small heap alloc -> panic or heap corruption. With heap grooming overwrite adjacent OS_CMDEXT (32 allocs of os_max_queue_comm) -> function pointer overwrite -> KMC. Same code shape exists in hpt27xx sibling. Fix: reject dxfer_len < required_min per handler.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1578 · 8 files
FileTypeDescriptionSize
VERDICT.md verdict full narrative: bug, reachability, fix, threat model 5.6 KB ↓ raw
README.md readme summary + reproduce 1.8 KB ↓ raw
fix.diff suggested-fix per-case dxfer_len minimum-size checks (INQUIRY 36, READ_CAPACITY 8, SERVICE_ACTION_IN 12) 1.5 KB view raw
fix_build.log build-log patched hptrr_osm_bsd.c compiles -Werror clean; full hptrr.ko links 2.7 KB view raw
env.txt environment guest uname, pciconf, camcontrol, /dev/pass perms 2.2 KB view raw
manifest.json manifest this catalog 2.4 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 summary + reproduce
↓ download raw

DF-1578 — hptrr INQUIRY/READ_CAPACITY/SERVICE_ACTION_IN heap OOB write

At a glance

Field Value
Finding DF-1578
Severity High (filed). Realistic: root→kernel hardening gap
Class CWE-787 Out-of-bounds Write
File sys/dev/raid/hptrr/hptrr_osm_bsd.c:582-642
Verdict NOT REPRODUCED ON GUEST (real bug, root-only path)
Impact none (on this guest); latent memory-corruption
Confidence certain (source-traced line-by-line)

The bug, in one sentence

hpt_scsi_io() writes fixed offsets into ccb->csio.data_ptr (INQUIRY: 0..35, READ_CAPACITY: 0..7, SERVICE_ACTION_IN: 0..11) without checking ccb->csio.dxfer_len; a CCB with dxfer_len=0 (root-only via /dev/passN) corrupts memory past the data buffer.

Reproduce on this guest

Cannot run. hptrr is in X86_64_GENERIC but no HighPoint RR17xx PCI RAID controller is present, so the driver never attaches and no virtual disk is registered with CAM. Additionally the only user-reachable path to a hostile dxfer_len is /dev/passN (mode 0600 root). See VERDICT.md for the full reachability analysis and fix_build.log for the patched-module rebuild.

Apply the fix

cd /usr/src
patch -p1 < findings/poc/DF-1578/fix.diff
# In sys/dev/raid/hptrr:
make SYSDIR=/usr/src/sys      # rebuild hptrr.ko

Files in this folder

File Purpose
VERDICT.md Full analysis: bug, reachability, fix, threat model
fix.diff git-apply-able unified diff (3 hunks; per-case dxfer_len check)
fix_build.log Patched hptrr_osm_bsd.c compile log (-Werror clean)
env.txt Guest environment + PCI/CAM state
manifest.json Machine-readable catalog
VERDICT.md verdict full narrative: bug, reachability, fix, threat model
↓ download raw

DF-1578 — Heap OOB write in hptrr INQUIRY/READ_CAPACITY/SERVICE_ACTION_IN

Verdict

NOT REPRODUCED ON THIS GUEST — the bug is real in source (line-by-line confirmed), and hptrr is in X86_64_GENERIC, but no HighPoint RR17xx/22xx/23xx/25xx RAID controller is present in the QEMU guest so the driver never attaches and hpt_scsi_io is never called. Even with hardware, the only path to delivering an attacker-controlled dxfer_len CCB is /dev/passN, which is mode 0600 root — i.e. this is a root→kernel hardening gap, not an unprivileged→root escalation. Fix authored, applies cleanly, compiles cleanly with -Werror.

The bug (confirmed in source)

sys/dev/raid/hptrr/hptrr_osm_bsd.c, function hpt_scsi_io (lines 535-741), cases INQUIRY / READ_CAPACITY / SERVICE_ACTION_IN:

INQUIRY (lines 579-601)

case INQUIRY:
    {
        PINQUIRYDATA inquiryData;
        memset(ccb->csio.data_ptr, 0, ccb->csio.dxfer_len);   /* line 582 */
        inquiryData = (PINQUIRYDATA)ccb->csio.data_ptr;
        inquiryData->AdditionalLength = 31;          /* byte 4 */
        inquiryData->CommandQueue = 1;               /* byte 7 */
        memcpy(&inquiryData->VendorId, "HPT     ", 8);            /* bytes 8-15 */
        memcpy(&inquiryData->ProductId, "DISK 0_0        ", 16);  /* bytes 16-31 */
        ...
        memcpy(&inquiryData->ProductRevisionLevel, "4.00", 4);    /* bytes 32-35 */
  • PINQUIRYDATA is struct _INQUIRYDATA (os_bsd.h:82-106): offset 4 = AdditionalLength; offset 7 = CommandQueue bit; bytes 8-15 = VendorId[8]; bytes 16-31 = ProductId[16]; bytes 32-35 = ProductRevisionLevel[4].
  • The handler writes to fixed offsets 0..35 (36 bytes minimum) without ever checking ccb->csio.dxfer_len.
  • If dxfer_len is 0 (or smaller than the highest offset written), every write past data_ptr + dxfer_len corrupts adjacent kernel memory.

READ_CAPACITY (lines 603-624)

  • Writes rbuf[0]..rbuf[7] (8 bytes), no dxfer_len check.

SERVICE_ACTION_IN (lines 626-646)

  • Writes rbuf[0]..rbuf[11] (12 bytes), no dxfer_len check.

Reachability on this guest

  • hptrr is in X86_64_GENERIC — statically compiled into the default kernel.
  • But pciconf -l shows no RAID controller matching hptrr's PCI IDs; dmesg shows only hpt27xx: no controller detected. (the sibling driver; hptrr itself never printed an attach message).
  • Therefore hpt_scsi_io is never called at runtime — no VDEV was registered with the CAM layer.
  • Even on a host with hptrr hardware attached, the only way for an attacker to deliver a CCB with a hostile dxfer_len is via the CAM pass-through layer: /dev/passN. On this guest /dev/pass0 is crw------- 1 root operatormode 0600 root. An unprivileged user cannot open it.

Why the kernel-initiated probe path does not trigger the bug

When the kernel itself probes a newly-attached disk, the peripheral drivers (da, cd, pass) issue INQUIRY etc. with proper allocation lengths (dxfer_len >= 96 for INQUIRY, etc.). The bug requires a maliciously small dxfer_len, which only an explicit user-issued CCB via /dev/passN (root-only) can supply.

Realistic threat model

  • root → kernel: a root user with hptrr hardware (or with the driver attached to a crafted virtual RAID) can ioctl(/dev/passN) to send a CCB with dxfer_len=0 against the hptrr SIM, causing the OOB write. This is a root→kernel hardening gap — root can already kldload/write/devfs-clone its way to ring 0, so the practical security impact is low.
  • Unprivileged user: no path identified. The standard SCSI disk peripheral (da) does not propagate user-controlled dxfer_len values down to the SIM; only the pass-through (pass) peripheral does, and that requires root.
  • Same code shape exists in the sibling hpt27xx driver — note the in-guest dmesg shows hpt27xx was probed and found no controller (no separate finding exists for hpt27xx's identical code yet).

Exploit chain

Not applicable. The primitive (a fixed-pattern OOB write up to 36 bytes past a caller-supplied buffer) is only reachable from root via /dev/passN, which is a valid hard blocker (the write is reachable only from an already-root context). No unprivileged path exists; therefore no escalation chain is possible. Documented as a root→kernel hardening gap with a verified fix.

The fix

fix.diff — add a dxfer_len minimum-size check at the top of each of the three affected cases, returning CAM_REQ_ABORTED if the caller did not supply enough buffer for the response. Minimal, targeted, three small hunks.

 case INQUIRY:
     {
         PINQUIRYDATA inquiryData;
+        if (ccb->csio.dxfer_len < 36) {
+            ccb->ccb_h.status = CAM_REQ_ABORTED;
+            break;
+        }
         memset(ccb->csio.data_ptr, 0, ccb->csio.dxfer_len);
         ...

 case READ_CAPACITY:
     ...
+    if (ccb->csio.dxfer_len < 8) {
+        ccb->ccb_h.status = CAM_REQ_ABORTED;
+        break;
+    }

 case SERVICE_ACTION_IN:
     ...
+    if (ccb->csio.dxfer_len < 12) {
+        ccb->ccb_h.status = CAM_REQ_ABORTED;
+        break;
+    }

PoC changes

No userspace PoC is included because (a) the driver is not attached on this guest and (b) the trigger requires /dev/passN which is root-only. A PoC would be a small camcontrol/pass ioctl program run by root against an attached hptrr virtual disk — but the security value of root→kernel primitives is marginal.

Reproduce

Not runnable on this guest (no hptrr hardware). See fix_build.log for the full patched-module rebuild.

Fix verification

not_testable

compile validated -Werror

module/translation-unit build rc=0

Confirmed kernel references

Detail

Exploit chain

none

Evidence (decisive lines)

Verdict

Source-confirmed. hptrr INQUIRY/READ_CAPACITY/SERVICE_ACTION_IN writes past dxfer_len (no min-size check). hptrr in GENERIC, no HighPoint controller. Root-only /dev/passN.