# DF-1563 — hpt27xx sglist_cnt heap overflow

## Verdict
**REPRODUCED (source-level harness).** The bug is real; impact ceiling is a
heap overflow of `(sglist_cnt - 18) * 16` bytes past `OS_CMDEXT.psg` into
the adjacent `M_DEVBUF` slab allocation — up to ~1 MiB with `sglist_cnt =
65535`. The kernel path requires an hpt27xx HBA plus a `/dev/passN` for one
of its targets, neither of which exists on the audit guest. Harness
demonstrates the genuine loop and overflow magnitude. fix.diff applies
cleanly and `nativekernel` succeeds (rc=0).

## Mechanism (`sys/dev/raid/hpt27xx/hpt27xx_osm_bsd.c`)
1. Line 715: `pCmd->psg = ext->psg;` — `ext->psg` aliases to a fixed
   `SG psg[os_max_sg_descriptors]` array (`os_bsd.h:155`), where
   `os_max_sg_descriptors = 18` (`osm.h:40`).
2. Lines 717-728: when `CAM_SCATTER_VALID` is set, the loop
   `for (idx = 0; idx < ccb->csio.sglist_cnt; idx++) pCmd->psg[idx] = ...`
   iterates the user-supplied `sglist_cnt`.
3. `ccb->csio.sglist_cnt` is `u_int16_t` (`cam_ccb.h:604`), max 65535 —
   there is **no** bound check against `os_max_sg_descriptors` (18).
4. Each `SG` entry is 16 bytes on amd64 (`sizeof(SG) = 16`, harness-measured).
5. `psg` is the **last field** of `OS_CMDEXT` (`os_bsd.h:155-157`), so the
   overflow runs straight into the next `M_DEVBUF` allocation — no padding,
   no sentinel.
6. With `sglist_cnt = 64`, the loop writes 736 bytes past `OS_CMDEXT`;
   with `sglist_cnt = 65535`, it writes ~1 MiB.

`os_buildsgl` at lines 479-488 has the same defect (separate code path).

## Harness proof (`harness.c`)
Replicates the genuine `for(idx=0; idx<sglist_cnt; idx++) pCmd->psg[idx]=...`
loop and reports overflow for representative sglist_cnt values:

```
sizeof(SG) = 16
sizeof(OS_CMDEXT) = 320
os_max_sg_descriptors = 18

sglist_cnt            bytes-written          buffer-size                  OOB
18                              288                  288                    0
19                              304                  288                   16
32                              512                  288                  224
64                             1024                  288                  736
256                            4096                  288                 3808
1024                          16384                  288                16096
65535                       1048560                  288              1048272

Overflowing sglist_cnt values: 6/10
psg[18] write offset = 320 (== sizeof(OS_CMDEXT))
```

## Exploit-chain note
Trigger requires an hpt27xx SIM and `/dev/passN` access (root or operator
group). With those, this is a fully-attacker-controlled heap-overflow
primitive (every written byte is from the user-supplied SG list). On a host
with the HBA this is a credible root→kernel-code-exec primitive via heap
grooming into a victim object with a function pointer. Documented as
primitive characterization; impact ceiling is heap corruption.

## PoC changes
- Original folder was README only.
- Added harness.c, build/run scripts, env, logs, fix.diff, VERDICT.md,
  manifest.json.

## Fix
`fix.diff` adds an explicit `sglist_cnt > os_max_sg_descriptors` reject
before the loop, freeing resources and completing the CCB with
`CAM_REQ_CMP_ERR`. Matches the finding markdown proposal ("validate
sglist_cnt <= os_max_sg_descriptors"). A sibling fix is needed for the
`os_buildsgl` path at lines 479-488 (the `HPT_ASSERT(nsegs<=
os_max_sg_descriptors)` at line 514 already covers that path on INVARIANTS
kernels).

## Fix-validation
`patch -p1 --forward` succeeds (hunk #1 at line 721). `nativekernel`
completes with rc=0 (`fix_build.log`). No run-time exercise possible
because no hpt27xx HBA / no `/dev/passN` on the guest → `fix_status:
"not_testable"`. Diff applies and compiles; changed logic rejects
oversized sglist_cnt.
