# DF-2427 VERDICT — OOB array access in amd_Reselect on garbage reselect ID (amd.c:1829-1844)

## Bottom line

**REPRODUCED — full kernel memory-corruption chain, mechanically
demonstrated through the REAL `amd_intr() -> amd_Reselect()` path of
the byte-verbatim driver, ending in a supervisor write at an
attacker-influenced wild address (`Fatal trap 12`) and, in the
contained variant, write-what-where into a victim object.**

`amd_Reselect()` decodes the reselecting target from the SCSI FIFO
(amd.c:1829-1831):

```c
amd->cur_target = amd_read8(amd, SCSIFIFOREG);   /* wire byte */
amd->cur_target ^= amd->HostID_Bit;
amd->cur_target = ffs(amd->cur_target) - 1;      /* no ffs()==0 check */
```

`cur_target` is a signed int (amd.h:204). A reselection byte that
XORs with HostID_Bit to **zero** (e.g. 0x80 with AdaptSCSIID 7 — the
malicious target asserts only the initiator's own bit) yields
`cur_target = -1`, and every subsequent per-target array index goes
out of bounds (amd.c:1833-1835, 1844-1845).

## Confirmed offsets (kernel-compiled offsetof dump, x86_64)

sizeof(amd_target_info)=12; `untagged_srbs`@softc+56 (64 entries × 8 B),
`tinfo`@softc+568 (8 × 12 B), `disc_count`@softc+664 (8×8 × 2 B):

* `tinfo[-1]` spans softc+556..567 = last 4 bytes of
  `untagged_srbs[7][6]` + all 8 bytes of `untagged_srbs[7][7]`
  (CtrlR1@557, CtrlR3@558, CtrlR4@559, sync_period_reg@560,
  sync_offset_reg@561).
* `disc_count[-1][lun]` @softc+648+2*lun — the tail of tinfo[]:
  lun=0 → tinfo[6].goal{period,offset}; lun=4 → tinfo[7].
  {sync_period_reg,sync_offset_reg}; lun=7 → tinfo[7].user. This is
  exactly the "overlap the tail of tinfo[]
  (sync_period_reg/offset/disc_tag)" aliasing claimed by the finding.
* `untagged_srbs[-1][lun]` @softc+(-8+8*lun): lun=0 → **8 bytes
  BEFORE the softc object** (production: heap header / adjacent
  chunk preceding the newbus softc allocation); lun=1..7 → the
  softc's own dev/tag/bsh/buffer_dmat/unit+last_phase/
  cur_target+cur_lun/active_srb fields.

## What was mechanically observed (run_battery1_clean.log)

Scenario A (byte 0x80, identify lun 0), through the real ISR:

1. `cur_target = -1`; the driver itself logs
   `amd0: Unexpected reselection for target -1` (its own printf of
   the negative ID).
2. **OOB READ** (amd.c:1834): `active_srb =
   untagged_srbs[-1][0]` = the 8 bytes at softc-8 — the harness
   pre-placed a victim pointer there (production: heap data) and the
   driver loaded it as the active SRB.
3. **OOB WRITE** (amd.c:1845): `untagged_srbs[-1][0] = NULL` zeroed
   softc-8..-1 — 8 bytes outside the object (canary ring smashed;
   bytes softc-64..-9 and the post ring untouched).
4. **OOB RMW** (amd.c:1844): `disc_count[-1][0]--` decremented 16
   bits of tinfo[6].goal 0x0000→0xFFFF (in-struct tail aliasing).
5. **OOB READ → chip config** (amd.c:1848-1853): DESTID=0xFF
   (cur_target -1 truncated) and SYNCPERIOD=0x77/SYNCOFF=0x66/
   C1=0xAA/C3=0x99/C4=0x88 — the exact pre-placed bytes of
   untagged_srbs[7][7]/[7][6] via tinfo[-1] — flowed into the chip
   registers.
6. **Write through the wild pointer**: a follow-on MSG IN interrupt
   (real amd_MsgInPhase0 → amdparsemsg, amd.c:1218) wrote
   `SRB_DISCONNECT` (2 bytes at +offsetof(SRBState)) through the
   wild active_srb into the victim object; amd.c:1219 RMW'd
   disc_count[-1][0] twice more (net attacker-steered +1 on
   tinfo[6].goal.period).

Scenario A2 (byte 0x80, lun 6): `untagged_srbs[-1][6]` aliases
`&cur_target/cur_lun` — the OOB read packed (cur_target=-1, lun=6)
into a **non-canonical wild pointer 0x00000006FFFFFFFF**, and the OOB
write zeroed both fields. Letting the real MSG IN path then
dereference it (battery mode 2):

```
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x70000016f     (= 0x6FFFFFFFF + 0x170 SRBState)
fault code = supervisor write data, page not present
Stopped at amd_MsgInPhase0+0xae: movw %si,0x170(%rax)
```

(panic_2427.txt) — a supervisor WRITE at wild-pointer+0x170 inside
the verbatim interrupt path.

Scenario B (multi-bit byte 0xA8 = initiator|bit5|bit3, device
physically at ID 5): ffs() decodes target **3** — the reselection
nexus (active_srb, disc_count, sync registers) bound to target 3's
SRB. Confused-deputy: an attacker at bus ID 5 can redirect its
reselection onto ANY other target's queued untagged SRB (data
diversion across nexuses; the abort path then also runs with the
wrong tinfo).

Scenario C (valid byte 0xA0 → target 5): correct in-bounds binding,
both canary rings intact — the machinery only corrupts on garbage
input.

## Threat model and privesc ceiling (honest)

Trigger class: a **malicious (or firmware-buggy / signal-garbled)
SCSI target device** on the bus of an AMD53c974 (Tekram DC-390(T))
HBA. This is a hardware/peripheral attacker (or a flaky cable) —
there is NO software-only path: the poison byte arrives via the chip
FIFO from the wire (no HBA exists in the QEMU guest; the mechanical
proof uses the verbatim register path against a fabricated chip).

Escalation ceiling on hosts that do have this HBA:

* The OOB read at softc-8 converts 8 bytes of heap metadata/adjacent
  memory into the kernel's `active_srb` pointer; the MESSAGE IN /
  STATUS phase handlers then perform fixed-offset 1- and 2-byte
  stores through it (demonstrated: SRBState write; StatusPhase0
  writes TargetStatus/EndMessage from FIFO bytes), plus TAILQ list
  operations on the forged object (SRBdone). That is a genuine
  write-what(chosen SCSI bytes)-where(forged pointer) primitive with
  heap grooming of the softc's preceding chunk — plausibly
  convertible to arbitrary kernel write and host uid=0 by a patient
  peripheral attacker.
* Even uncontrolled, the result is a guaranteed kernel fault
  (demonstrated Fatal trap 12) or silent heap corruption.
* Scenario B additionally gives a malicious peripheral
  cross-target SRB redirection (data integrity break) on the same
  HBA.

Local unprivileged user: **no route** (needs the HBA + a hostile
device on its bus). The realistic rating is kernel memory corruption
driven by a malicious peripheral — High within the "hardware attacker
present" scope, untriggerable otherwise.

## A/B fix validation

fix.diff adds the missing validation after the XOR (reject any
remainder that is not exactly one bit — killing both the -1 decode
and the multi-bit wrong-target decode) and resets the bus:

* Baseline: battery 0 checks failed with full corruption chain;
  battery=2 → Fatal trap 12 supervisor write.
* Fixed kernel: every scenario prints
  `amd0: Invalid reselection ID, resetting bus`, active_srb stays
  NULL, canary rings + tinfo tail + victim object untouched, valid
  reselect (C) still binds correctly, **0 checks failed, no fault,
  guest stays up** (run_fixed_dmesg.log, run_fixed_battery.log).

## Rig

Same kernel-compiled verbatim harness as DF-2426 (shared TU;
amd.c sha256-gated). Register access runs the real inline
bus_space code against a fabricated memory-mapped AMD53c974 register
block; only the SCSI FIFO register (SCSIFIFOREG) is shimmed to
pop-on-read (the real chip's FIFO semantics) so the verbatim
`amd_read8(amd, SCSIFIFOREG)` calls at amd.c:1829/1832 return the
poisoned reselection byte and identify byte. `xpt_done` interposed
(pass-through when unarmed). The softc under test sits inside a
canary-ringed wrapper (`pre[64] amd post[64]`) so any access outside
[softc, softc+sizeof) is visible.

## Artifacts

* run_battery1_clean.log — contained baseline battery, 0 checks
  failed (layout dump, A, A2, B, C with all PASS lines).
* panic_2427.txt — serial console: battery=2 with the Fatal trap 12
  supervisor write at 0x70000016f, `amd_MsgInPhase0+0xae`.
* run_battery1_dmesg.log (and .iter1) — raw dmesg captures.
* run_fixed_dmesg.log / run_fixed_battery.log — fixed kernel: all
  scenarios rejected, 0 checks failed, no fault.
* layout table: `sysctl debug.df2427.layout` output is the first
  block of run_battery1_clean.log.

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