# DF-2425 VERDICT — full narrative

## Bottom line

**REPRODUCED.** The unchecked `cdb_len` → `bcopy` into `CmdBlock[12]` at
`sys/dev/disk/amd/amd.c:400` is a real, deterministic kernel heap overflow
inside the SRB, and it yields a fully attacker-chosen 64-bit pointer
(`struct amd_srb.pccb`) that the driver dereferences immediately
(`amd.c:287-288`) and writes through (`amd.c:355`). Demonstrated on the
production driver source compiled into a KLD rig (no modification of the
audited code path), including a kernel panic whose faulting address contains
the attacker's bytes verbatim. The clamp fix (`fix.diff`) eliminates both the
overflow and the forged-pointer deref and was validated on a rebuilt
GENERIC kernel. **However**, the trigger is reachable in practice only from
a context that can already issue arbitrary SCSI commands (`/dev/passN` is
`root:operator 0600`) *and* only on systems whose PCI bus carries an AMD
53C974 / Tekram DC-390(T) HBA. Severity is therefore materially lower than
filed (High → realistic Low/Medium); see "Honest reachability".

## The bug, precisely

`struct amd_srb` (`sys/dev/disk/amd/amd.h:157-160`):

```
offset  0: TAILQ_ENTRY(amd_srb) links;   /* 16 bytes */
offset 16: u_int8_t CmdBlock[12];        /* sink                       */
offset 24: (4 bytes padding)
offset 32: union ccb *pccb;              /* CmdBlock[16..24) lands HERE */
offset 40: bus_dmamap_t dmamap;          /* CmdBlock[24..32) */
offset 48: struct amd_sg *pSGlist;       /* CmdBlock[32..40) */
```

`amd_action(XPT_SCSI_IO)` (`amd.c:399-400`):

```c
pSRB->ScsiCmdLen = pcsio->cdb_len;                                       /* :399 */
bcopy(pcsio->cdb_io.cdb_bytes, pSRB->CmdBlock, pcsio->cdb_len);          /* :400 */
```

`cdb_len` is `u_int8_t` (`sys/bus/cam/cam_ccb.h:603`) — 0..255, and
`cdb_io.cdb_bytes` is 16 bytes (`IOCDBLEN = CAM_MAX_CDBLEN = 16`,
`cam_ccb.h:44,587`). Nothing between the CAMIOCOMMAND ioctl and this bcopy
validates it:

- `scsi_pass.c:462` CAMIOCOMMAND hands the user's ccb to `passsendccb`;
- `xpt_merge_ccb` (`cam_xpt.c:3888-3897`) copies the user's *entire* CCB
  payload after `ccb_hdr` into the kernel ccb — `cdb_len`, `msg_ptr`,
  `tag_action`, … all verbatim;
- `cam_periph_runccb` → `xpt_action` dispatches to the SIM with no further
  checks (`grep cdb_len` over `cam_xpt.c`/`cam_periph.c`: no hits).

The over-read source bytes `cdb_bytes[16..24)` are `ccb_scsiio.msg_ptr`
(next field after `cdb_io`, `cam_ccb.h:625-633`) — an 8-byte,
fully-user-controlled value copied verbatim by `xpt_merge_ccb`. They land
exactly on `pccb` (verified at runtime: `offsetof(CmdBlock)=16`,
`offsetof(pccb)=32`, `sizeof(amd_srb)=384`).

Then, with `CAM_DIR_NONE` (no DMA mapping), `amdexecutesrb` runs on the same
call:

```c
ccb = srb->pccb;                        /* amd.c:287 — forged            */
amd = ccb->ccb_h.ccb_amd_ptr;           /* amd.c:288 — DEREF of it       */
...
if (ccb->ccb_h.status != CAM_REQ_INPROG) /* amd.c:347 — read through it  */
...
ccb->ccb_h.status |= CAM_SIM_QUEUED;    /* amd.c:355 — WRITE through it  */
```

Deeper overflow (`cdb_len` up to 255) also overwrites `dmamap`, `pSGlist`,
`TotalXferredLen`, `SGPhysAddr`, `SGToBeXferLen`, `Segment0/1` and walks
141 bytes into `SGsegment[]` — all *within* the softc's `SRB_array`, so the
bcopy itself stays inside the softc allocation; the dangerous primitives are
the three smashed pointer fields (`pccb` used at :240/:244/:287/:919;
`dmamap` later `bus_dmamap_unload`ed at :349 on error paths; `pSGlist`
consumed by the S/G engine). Additionally `amdsetupcommand`
(`amd.c:252-255`) later writes `ScsiCmdLen` (=255) bytes from `CmdBlock` to
the SCSI FIFO — an info leak of the overflowed SRB bytes to a (hostile)
SCSI target.

## Why a rig (and what the rig is)

The amd(4) driver attaches only to PCI 0x1022:0x2020 (amd.c:82,2455). It IS
compiled into GENERIC (`sys/config/X86_64_GENERIC:88`; `/usr/obj/.../amd.o`
present in the guest) but no physical card exists in the QEMU guest.

QEMU 7.2 *does* emulate this chip (`-device am53c974`, and `dc390`), with the
same PCI ID. Attaching it proved the driver binds for real:

```
attach-am53c974-boot.log:103:
amd0: <Tekram DC390(T)/AMD53c974 SCSI Host Adapter> port 0xc000-0xc07f irq 11 at device 3.0 on pci0
attach-am53c974-boot.log:133: CAM: Configuring 3 busses
```

but boot then wedges forever at `Waiting 5 seconds for SCSI devices to
settle` — the XPT scan of the emulated bus never completes (the driver has
no timeout handler at all, `amd.c:356-360` is `#if 0`), so multiuser is
never reached and the userland pass(4) path cannot be driven end-to-end on
this emulation. That is an availability bug of its own (any command that
does not complete hangs the queue forever), not a bypass of the finding.

The rig (`amdrig.c`) therefore compiles the **production**
`/usr/src/sys/dev/disk/amd/amd.c` verbatim into a KLD (`#include`), sets up
exactly what `amd_init`/`amd_attach` set up (softc, dma tag, `amd_linkSRB`,
`cam_sim_alloc(amd_action, amd_poll, ...)`), and calls the real
`amd_action` with a crafted `XPT_SCSI_IO` ccb. Documented deviations, none
in the audited path:

- `#define xpt_done rig_xpt_done` shim — forwards to the real `xpt_done`
  unless `ccb_h.path == NULL` (rig-fabricated ccbs cannot travel the real
  CAM completion machinery without a registered device; the shim records
  the completion instead). The baseline panic/writes never reach
  `xpt_done`; only the fixed build's reject path does.
- `DRIVER_MODULE` stubbed (newbus registration boilerplate) — the GENERIC
  kernel already owns module `pci/amd`; without the stub kldload refuses.
- `#define amd amdrig_chip` — renames module-scope identifiers only; every
  driver function/struct token is untouched.

## Baseline results (unmodified /usr/src, kernel #0 Jul 2 2026)

Run 1 — controlled write, guest survives (`panic.txt`,
`panic.txt` header):

```
RIGW0: victim=0xfffff8008db3b800 (pre status=0x0), forging pccb=0xfffff8008db3b800 via csio.msg_ptr, cdb_len=255
RIGW1: post victim status=0x200 (CAM_SIM_QUEUED=0x200) -> WRITE-THROUGH-FORGED-PTR-CONFIRMED
RIGW2: srb=... CmdBlock[0..11]=4c4c4c4c4c4c4c4c4c4c4c4c pccb=0xfffff8008db3b800
       dmamap=0xc0ffee005a1337 pSGlist=0x1234abcd ScsiCmdLen=255
```

The write `status |= CAM_SIM_QUEUED` (amd.c:355) landed at the forged
address — a write-what-where-class primitive (OR of a known constant at a
chosen kernel address). `dmamap`/`pSGlist` values match the predicted
packing of `msg_len/tag_action/tag_id/init_id` — the smash is byte-exact
and deterministic.

Run 2 — sanity: `cdb_len=6` queues normally (`RIGS1`), i.e. the rig drives
the driver's legitimate path.

Run 3 — panic proof:

```
RIGP0: forging pccb=0x414141414141 (canonical, unmapped) via csio.msg_ptr, cdb_len=255
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x4141414141c9
fault code = supervisor read data, page not present
Stopped at amdexecutesrb+0x12: movq 0x88(%r13),%r12
```

`0x4141414141c9 = 0x414141414141 + 0x88` (0x88 = offsetof(struct ccb_hdr,
ccb_amd_ptr)) and the faulting instruction is exactly amd.c:288 — the
deref uses the attacker's six bytes verbatim. That is byte-for-byte pointer
control at the sink, proven.

## Honest reachability / severity

- **Non-root users cannot trigger it.** `/dev/pass0` and `/dev/xpt0` are
  `crw------- root:operator` on this guest; opening as an unprivileged user
  returns EACCES (tested). `cdb_len > 16` exists nowhere in kernel-issued
  CCBs: periph drivers build ≤16-byte CDBs, and the only >12 case reachable
  from ordinary disk I/O is da(4) `READ_16`/`WRITE_16` (cdb_len=16) on
  >2TB devices — which writes 4 bytes into the *padding* between
  `CmdBlock[12]` and `pccb` (offsets 28..31): technically UB, no live field
  corrupted.
- **The driver must be attached**, i.e. the machine must carry an AMD
  53C974/Tekram DC-390 PCI HBA (1990s hardware; QEMU `-device am53c974`
  re-creates it, and the driver attaches — proven above).
- Therefore the practical attacker is *root* (or an operator with a
  permissive pass node / a jail with pass exposed) on a machine with this
  HBA, turning "send raw SCSI CDB" into a kernel write primitive. On the
  audit rubric ("Low: … requires specific hardware or already-privileged
  user") the realistic severity is **Low–Medium**, not the filed High
  (whose `PR:L` assumption does not hold). The code defect itself is
  certain and the primitive is strong; the fix is still warranted as
  hardening and correctness.

- uid0 escalation was **not** pursued because the trigger is already
  root-gated on the only reachable configuration; "root → uid0" adds no
  privilege.

## Fix validation

`fix.diff` inserts, before the bcopy:

```c
if (pcsio->cdb_len > sizeof(pSRB->CmdBlock)) {
        TAILQ_INSERT_HEAD(&amd->free_srbs, pSRB, links);
        pccb->ccb_h.status = CAM_REQ_INVALID;
        xpt_done(pccb);
        return;
}
```

Applied to guest `/usr/src` (`fix-apply.log`: `Hunk #1 succeeded at 393`),
then:

1. **Module-level A/B** — rig rebuilt against the patched source, same
   inputs (`fix-run-serial.log`, `fix-run2-serial-full.log`):

   ```
   RIGW0: ... forging pccb=0xfffff8008db3b800 ... cdb_len=255
   RIGDONE: shim saw func=2305 status=0x6            <- XPT_SCSI_IO / CAM_REQ_INVALID
   RIGW1: post victim status=0x0 -> no-write          <- forged ptr never used
   RIGW2: ... CmdBlock[0..11]=000000000000000000000000 pccb=0 dmamap=<valid map> ScsiCmdLen=0
   ```

   `pccb=0`, `CmdBlock` zeroed, `dmamap` = the legitimate map from
   `amd_linkSRB` — the bcopy never executed.

2. **Full kernel A/B** — `make -j6 nativekernel KERNCONF=X86_64_GENERIC` +
   install + reboot into kernel `#1 Mon Oct 12 08:57:10 UTC 2026`
   (sha256 `1aecdc66…ee63b`), the config that includes `device amd`:

   - the exact input that panicked the baseline
     (`dev.amdrig_fire=1`, forged `0x414141414141`) now completes with
     `RIGDONE: shim saw func=2305 status=0x6` and **no panic** — guest
     stayed up (`RIGP1: UNEXPECTED survival`, uptime verified);
   - `dev.amdrig_fire=2` (cdb_len=6) still queues the srb normally —
     no functional regression for legitimate commands.

Known rig wart, documented for honesty: `dev.amdrig_fire=3`
(`rig_reject_case`) memsets an SRB that is still linked on `free_srbs`,
corrupting the TAILQ; on the fixed kernel its reject path then touches the
corrupted queue and panics at `amd_action+0x335` (`fix-run-serial.log`).
This is a bug in my test harness, not in the production fix — the
load-time write-demo (same cdb_len=255 input, no memset) rejects cleanly.
Mode 3 is not needed for the verdict; modes 0/1/2 carry the full A/B.

## Conclusion

| | baseline | patched |
|---|---|---|
| write through forged `pccb` (amd.c:355) | YES (status 0x200 at victim) | no (status stays 0) |
| SRB smash (`CmdBlock`/`pccb`/`dmamap`/`ScsiCmdLen`) | YES, byte-exact | none |
| forged deref amd.c:288 → Fatal trap 12 | YES @ `0x4141414141c9` | no — `CAM_REQ_INVALID` |
| valid `cdb_len=6` path | works | works |

The finding is **certain** at the code level, **reproduced** with both a
controlled kernel-address write and a kernel panic in the production
routine, the **fix is validated** at module and full-kernel level, and the
**practical severity is gated** by hardware presence + root-level CAM
access (non-root is locked out by `/dev/passN` permissions — tested).

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