# DF-2522 VERDICT — REPRODUCED (production handler, stock module)

**Status: reproduced** — the production `trm_SRBdone()` auto-sense bcopy
(sys/dev/disk/trm/trm.c:2529-2530) copies `csio->sense_len` (a `u_int8_t`
carried verbatim from the CAM CCB) bytes out of a **32-byte** DMA sense
slot into the **32-byte** `csio->sense_data` field. With `sense_len=255`
the running kernel's own handler code read **223 bytes past the 32-byte
slot — past the entire 256-slot `sense_buffers` array into adjacent
kernel heap — and wrote them over the live tail of `ccb_scsiio`**.
Guest survived; both overflow directions proven byte-exact by pattern
provenance. The one-line clamp fix eliminates every marker on a rebuilt
module.

## Rig (production code, not a re-implementation)

No Tekram DC395 HBA exists on the guest, but the driver ships as the
production module `/boot/kernel/trm.ko` (identical sources:
`md5 /usr/src/sys/dev/disk/trm/trm.c` = `bf84dbcb…` matches the audit
tree). `kldload trm` puts its real static handlers into the running
kernel — `trm_SRBdone`, `trm_Disconnect`, `trm_Reselect` are all inlined
into `trm_Interrupt` (file offset 0x1d40 in `nm`). The KLD rig
(`df252x_trm_rig.c`) contains zero driver logic; it fabricates the
post-attach ACB/DCB/SRB/CCB state exactly as the driver builds it, maps
the TRM-S1040 register file as a scratch page behind the kernel's own
`bus_space` accessors (`X86_64_BUS_SPACE_MEM` → plain volatile
loads/stores, what the driver sees on its memory BAR), and **dispatches
the module's real `trm_Interrupt()`** at `kldstat base + nm offset`
(0xffffffff826001d40, stock). The interrupt choreography (`SCSI_STATUS`
has `SCSIINTERRUPT`, `SCSI_INTSTATUS` = `INT_DISCONNECT`) drives the
production path `trm_Interrupt` (trm.c:1323) → `trm_Disconnect`
(trm.c:2402-2404, SRB_COMPLETED) → `trm_SRBdone` (trm.c:2511
AUTO_REQSENSE branch) → the bcopy at trm.c:2529.

The CCB is presented as `kmalloc(sizeof(union ccb) + 256)` with
sentinels — a real pass(4) CCB is exactly `kmalloc(sizeof(union ccb))`
(`xpt_alloc_ccb`, cam_xpt.c:4933).

## Stock run (run.log)

* Control `sense_len=32` (SSD_FULL_SIZE): clean completion, sense bytes
  land in `sense_data`, nothing else moves — validates the fabricated
  state and the dispatch (all production side-effects present:
  `pDCB->pActiveSRB` cleared at trm.c:2402, SRB freed onto
  `pACB->pFreeSRB`, `SgSenseTemp`/`Segment0/1` restores,
  `ccb_h.status = CAM_SCSI_STATUS_ERROR|CAM_AUTOSNS_VALID`).
* `sense_len=255`:
  * `dest bytes [32..255) = 0xA7 … CONFIRMED (223/223 bytes)` — the 0xA7
    pattern exists **only past the 256-slot sense_buffers array** (slot
    255 = last of 256; its 32 bytes are the 0x51 pattern which lands in
    `sense_data[0..31]`). 223 adjacent-kernel-heap bytes were copied
    into the CCB. With `CAM_AUTOSNS_VALID` set (trm.c:2531-2532) these
    bytes go back to the CAM client — with a pass(4) CCB that is
    **userland-visible kernel heap disclosure** on every command.
  * `clobbered LIVE ccb fields: sense_len=0xa7 cdb_len=0xa7
    sglist_cnt=0xa7a7 scsi_status=0xa7 (just set to 0x02 by
    trm.c:2528, overwritten!) … cdb_io.cdb_ptr=0xa7a7…a7
    msg_ptr=0xa7a7…a7 …` — the OOB write side.
* Layout facts printed at runtime: `sizeof(struct scsi_sense_data)=32`,
  `sizeof(struct ccb_scsiio)=320`, `sizeof(union ccb)=720`,
  `offsetof(csio,sense_data)=236` → the 255-byte write reaches offset
  491, i.e. **stays inside the 720-byte CCB allocation** on this
  platform (intra-object overflow of live fields, including the
  `msg_ptr` and `cdb_io.cdb_ptr` pointers), and does **not** cross the
  heap chunk. The read side crosses the sense_buffers array end
  (TagNumber=255 → reads [8160..8414] of the 8192-byte array).

## Reachability / threat notes

`sense_len` is fully attacker-chosen in the CAM CCB (33..255); no
clamp exists in pass(4) or xpt (`grep sense_len` in scsi_pass.c /
cam_xpt.c finds no validation). In practice the trigger needs CAM
command submission on a machine with the Tekram HBA — `/dev/passN`
(root/operator) or in-kernel CAM clients. The DMA that fills the slot
is SG-bounded to 32 bytes (trm.c:2837-2838), so the device cannot
influence the length — only the CCB originator can. Filing severity
(Medium) is consistent: privileged-local trigger, heap-disclosure
primitive (223 bytes/heap layout per command) plus ccb field/pointer
corruption.

## Fix validation (fix.diff, run.patched.log, fix_build.log)

`imin(pcsio->sense_len, sizeof(pcsio->sense_data))` — the copy clamps
to 32. Validated by rebuilding the driver module from the patched
source (same tree as `fix.all.diff`, which also carries the independent
DF-2523/DF-2524 hunks; this scenario exercises only the bcopy hunk):
`make nativekernel KERNCONF=X86_64_GENERIC` (full log fix_build.log)
produced `/usr/obj/…/dev/disk/trm/trm.ko` (md5 ea902f81…, vs stock
7211861d…), loaded in place of the stock module, and the SAME rig
re-dispatched against the patched module's own `trm_Interrupt`:

* `dest bytes [32..255) = 0xA7 … ABSENT - COPY WAS CLAMPED (0/223
  bytes)`;
* live fields intact: `scsi_status=0x02` (CHECK_COND preserved),
  `cdb_len/sglist_cnt/resid/cdb_ptr/msg_ptr/tag_*` all zero (bzero
  state), `sense_len=0xff` (the user's own value, untouched);
* the 32 legal bytes still copied correctly (`sense_data[0..31]` all
  0x51).

Baseline bad behavior gone; control semantics preserved.

## Things checked and killed

* `xpt_done()` on the fabricated CCB: neutralized by
  `func_code = XPT_NOOP` (no `XPT_FC_QUEUED` bit → xpt_done returns
  immediately, cam_xpt.c:4896; CAMDEBUG not compiled into this kernel —
  no `cam_dflags` symbol — so the CAM_DEBUG macro is a no-op). This is
  rig hygiene, not a driver finding.
* Write crossing the CCB allocation: false on amd64 layout (491 < 720);
  documented as intra-object overflow — the finding text ("writes past
  sense_data into rest of csio corrupting cdb_io msg_ptr tag_action
  tag_id init_id") matches exactly.
* REQUEST_SENSE CDB allocation-length write at trm.c:2844
  (`CmdBlock[4..5] = sense_len`): noted; the DMA remains SG-bounded to
  32 bytes (trm.c:2838), so it is not itself an overflow — clamping the
  bcopy is sufficient for this bug.

## Bottom line

Real bug, reproduced through the running kernel's own driver code, both
overflow directions byte-proven; one-line clamp fixes it, verified on a
rebuilt module. Hardware gate: machines with Tekram DC395/TRM-S1040
HBAs.

## 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.
