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)
PoC verification
Evidence pack
findings/poc/DF-1578 · 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 |
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 */
PINQUIRYDATAisstruct _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_lenis 0 (or smaller than the highest offset written), every write pastdata_ptr + dxfer_lencorrupts adjacent kernel memory.
READ_CAPACITY (lines 603-624)
- Writes
rbuf[0]..rbuf[7](8 bytes), nodxfer_lencheck.
SERVICE_ACTION_IN (lines 626-646)
- Writes
rbuf[0]..rbuf[11](12 bytes), nodxfer_lencheck.
Reachability on this guest
hptrris inX86_64_GENERIC— statically compiled into the default kernel.- But
pciconf -lshows no RAID controller matching hptrr's PCI IDs;dmesgshows onlyhpt27xx: no controller detected.(the sibling driver; hptrr itself never printed an attach message). - Therefore
hpt_scsi_iois never called at runtime — noVDEVwas 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_lenis via the CAM pass-through layer:/dev/passN. On this guest/dev/pass0iscrw------- 1 root operator— mode 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 withdxfer_len=0against the hptrr SIM, causing the OOB write. This is a root→kernel hardening gap — root can alreadykldload/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-controlleddxfer_lenvalues down to the SIM; only the pass-through (pass) peripheral does, and that requires root. - Same code shape exists in the sibling
hpt27xxdriver — 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_testablecompile 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.
No comments yet.