# DF-4174 VERDICT — unbounded physical SG-list copy in gdtexecuteccb (pass(4)-reachable)

**Status: untested on this guest (hardware-gated: requires an iir controller to
own the CAM bus). PoC built and executed to the hardware gate; bug existence
certain from code trace.**

## Reproduced?

No. The PoC (`sg_overflow.c`) compiled cleanly on the audit guest
(`cc -O -Wall … -lcam`, build.log: BUILD_RC=0) and ran correctly: it opened
`/dev/pass0`, performed XPT_PATH_INQ, found the SIM is `"ata"` (the QEMU
DVD), refused to fire, and exited 4 (`run.log`). With no iir controller the
vulnerable code is unreachable — the gate is documented in `gate_check.log`
(no `/dev/iir`, no 0x1119/Intel-0600 PCI device, `iir.ko` not loaded).

## Code trace (why the bug is certain)

1. `sc_buffer_dmat` is created with `nsegments = GDT_MAXSG` (=32)
   (iir.c:202-208, iir.h:71). This bounds **only** the `bus_dmamap_load()`
   paths.
2. `gdt_next()` (iir.c:697-780) dispatches XPT_SCSI_IO CCBs to
   `gdt_raw_cmd()` (iir.c:782-885) / `gdt_cache_cmd()` (iir.c:887-989).
   For a CCB with `CAM_SCATTER_VALID` set and `CAM_DATA_PHYS` clear, the
   guards at iir.c:867-873 panic on other flag combos but the
   `CAM_SG_LIST_PHYS` combination falls through to:

       segs = (struct bus_dma_segment *)ccb->csio.data_ptr;
       gdtexecuteccb(gccb, segs, ccb->csio.sglist_cnt, 0);   /* iir.c:876-877 */

   (identically in the cache path, iir.c:982-984).
3. `gdtexecuteccb()` (iir.c:1234-1304) then loops

       for (i = 0; i < nseg; ++i)
           gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SG_LST +
                     i * GDT_SG_SZ + GDT_SG_PTR, dm_segs->ds_addr);
           ... GDT_SG_LEN, dm_segs++ ...

   (iir.c:1271-1284; cache twin iir.c:1255-1269) — **no bound on `nseg`**,
   writing 8 bytes per entry into the SG region that `GDT_CMD_SZ` /
   `GDT_RAW_SG_LST` size for exactly `GDT_MAXSG`=32 entries
   (iir.h:234-243: `GDT_CMD_SZ = 0x0a + 0x3a + 32*8 = 324`).
4. `sglist_cnt` and `data_ptr` come verbatim from the CCB. `/dev/passN`
   (mode 0600, root only — `gate_check.log` shows the permission bits on the
   guest's pass0) lets a process submit arbitrary XPT_SCSI_IO CCBs with
   user-chosen flags, so `sglist_cnt = 64` writes 256 bytes past the end of
   the 32-entry array — overflowing `gc_cmd[324]` into `gc_cmd_len`, `sle`
   link, and the subsequent `struct gdt_ccb`s of the `kmalloc`'d
   `sc_gccbs[]` block (iir.c:250). Content is the attacker's segment
   addresses/lengths (64-bit values truncated to 32-bit wire fields).

## Reachability / privilege

root + iir hardware (pass device on the iir bus). Not reachable by an
unprivileged user. Severity Low per rubric; bucket: memcorrupt.

## PoC changes vs seed

The orchestrator's seed sketch used FreeBSD-isms that do not exist on
DragonFly: no `cam_fill_csio` (fields set manually), `cam_open_btl()` takes a
`path_id_t` not a bus-name string (PoC opens the pass device and verifies the
SIM via XPT_PATH_INQ `cpi.dev_name`), `MSG_SIMPLE_Q_TAG`/`SSD_FULL_LEN`
removed, and userland `struct bus_dma_segment` replaced with a local 16-byte
`{addr,len}` struct matching the kernel's x86_64 layout.

## Fix validation

`fix.diff` (reject `sglist_cnt > GDT_MAXSG` in `gdt_next`, mirroring the
existing `cdb_len > 12` rejection) passes `git apply --check`. Runtime
fix validation needs an iir bus; fix_status = not_testable.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
