# DF-4810 — AHCI command-table stride (`ATA_AHCI_CT_SIZE`=256) ≠ `sizeof(struct ata_ahci_cmd_tab)`=1152 — latent OOB of the DMA work area

File: `sys/dev/disk/nata/chipsets/ata-ahci.c` + `sys/dev/disk/nata/ata-all.h` (pass 2)
Severity: Info (hardening / defense-in-depth — **not exploitable in the tree as-is**) ·
CWE-131 (incorrect calculation of buffer size) · confidence: certain

## Build / run

Not applicable — this is a latent-hazard finding, proven by arithmetic on the
type/constant definitions. No PoC can exercise it today because `tag` is
hardcoded to 0 on every code path.

## The mismatch

`struct ata_ahci_cmd_tab` (`sys/dev/disk/nata/ata-all.h:249-255`), `__packed`:

```
cfis[64] + acmd[32] + reserved[32] = 128  (== ATA_AHCI_CT_SG_OFFSET, ata-all.h:238 ✓)
prd_tab[64] × sizeof(struct ata_ahci_dma_prd = 8+4+4 = 16)  = 1024
total = 1152 bytes
```

but the stride used to place command tables inside the channel's DMA work area
is `ATA_AHCI_CT_SIZE = 256` (`sys/dev/disk/nata/ata-all.h:239`), and every
indexing site uses that stride:

- `ata-ahci.c:254-255` (`ctp = work + ATA_AHCI_CT_OFFSET + ATA_AHCI_CT_SIZE*tag`)
- `ata-ahci.c:277` (cmd-list placement uses CL_SIZE — consistent)
- `ata-ahci.c:286-287` (`cmd_table_phys = work_bus + CT_OFFSET + CT_SIZE*tag` —
  this is what the *hardware* is told)

## Why it is safe today (and why it is a landmine)

- `tag` is `int tag = 0` at both sites (`ata-ahci.c:250`, `ata-ahci.c:343`);
  this driver never issues more than one command per port (no NCQ).
- The work area is `MAXWSPCSZ = PAGE_SIZE*2 = 8192` bytes
  (`ata-dma.c:55-56`, allocated at `ata-dma.c:130-145`), so the single real
  table at `[1280, 1280+1152) = [1280, 2432)` fits.
- If any future change derives `tag` from the request (the natural NCQ
  evolution — FreeBSD's later `sys/dev/ahci` does exactly that):
  - tag ≥ 1: tables **overlap/alias** (stride 256 < struct 1152) — silent
    corruption of a live command's FIS/PRD by the next one;
  - tag ≥ 28: `1280 + 28×256 = 8448 > 8192` — `ctp` and `ctp->prd_tab`
    point **past the end of the 8192-byte bus_dmamem_alloc'd work area**
    (heap/DMA-memory OOB write with fully attacker-influenced content: FIS
    bytes and PRD addresses of the I/O), and `cmd_table_phys` hands that OOB
    address to the AHCI engine for real DMA.

Secondary hardening items in the same function family:

- `ata_ahci_dmasetprd()` (`ata-ahci.c:531-546`): `KASSERT(nsegs <=
  ATA_AHCI_DMA_ENTRIES)` sits **after** the `for (i = 0; i < nsegs; i++)
  prd[i] = ...` write loop, and KASSERT is compiled out without INVARIANTS.
  Today the bound holds *only* structurally: `max_iosize = 63×PAGE_SIZE`
  (`ata-ahci.c:558`) caps a transfer at 64 pages-spanned == exactly 64 PRD
  entries == `prd_tab[64]` (the dma tag itself would permit up to
  `ATA_DMA_ENTRIES = 256` segments, `ata-dma.c:99,112`). One careless bump of
  `max_iosize` or `max_iosize/PAGE_SIZE` arithmetic change turns this into a
  live OOB write with zero margin.

## Fix

Make the constant self-consistent and assert it (see fix.diff):

```c
-#define ATA_AHCI_CT_SIZE                256
+/* must equal sizeof(struct ata_ahci_cmd_tab) = 128 + 64*16 = 1152 */
+#define ATA_AHCI_CT_SIZE                1152
```
plus `_Static_assert(ATA_AHCI_CT_SIZE == sizeof(struct ata_ahci_cmd_tab), ...)`
in ata-ahci.c, and move the `KASSERT(nsegs <= ATA_AHCI_DMA_ENTRIES)` **before**
the write loop in `ata_ahci_dmasetprd`. With tag still 0 the address
arithmetic is unchanged (`CT_OFFSET + 0*CT_SIZE`), so the constant fix is a
no-op at runtime today and only removes the trap.
