# DF-3608 — VERDICT

**Finding:** On amd64 systems with physical memory above 4 GB, ncr(4)
silently truncates physical addresses into the 32-bit script/DMA
descriptor fields; the 53C8xx (32-bit PCI DMA engine) then transfers to
the wrong physical address — arbitrary kernel/user memory corruption from
ordinary disk I/O.

**Status: inconclusive at runtime, certain at source level.** The
truncation is structural (types don't lie); the runtime A/B could not be
driven to a mismatch on the audit guest within the run window — see
"what was executed" for exactly how far it got and why.

## Structural argument (sys/dev/disk/ncr/ncr.c + ncrreg.h)

All chip-visible addresses are 32-bit:
- `typedef u_int32_t ncrcmd;` (ncrreg.h:266) — `struct link { ncrcmd
  l_cmd, l_paddr; }` (ncr.c:447-450), so every jump/call target and every
  reselect-chain pointer is 32-bit;
- `struct scr_tblmove { u_int32_t size; u_int32_t addr; }`
  (ncrreg.h:306-309) — all data/command/sense/msg descriptors;
- `u_int32_t squeue[MAX_START]` (ncr.c:1081).

The sources are full-width and unrestricted:
- `cp->p_nccb = vtophys(cp)` (ncr.c:6577) — the nccb comes from plain
  `kmalloc(M_DEVBUF)` (ncr.c:6567); only the script *images* are
  constrained below 4 GB via `kmem_alloc_contig(..., 0, 0xffffffff, ...)`
  (ncr.c:3588-3605), demonstrating the authors knew the constraint but
  applied it nowhere else;
- `np->squeue[np->squeueput] = CCB_PHYS(cp, phys)` (ncr.c:4149): the
  u_long result of `cp->p_nccb + offsetof(...)` is stored into a
  `u_int32_t` — truncation;
- `cp->phys.cmd.addr = vtophys(csio->cdb_io.cdb_bytes)` (ncr.c:4094) and
  `cp->phys.sense.addr = vtophys(&csio->sense_data)` (ncr.c:4110): the
  union ccb is allocated by the CAM layer (`xpt_alloc_ccb`,
  kmalloc M_CAMXPT, cam_xpt.c) with no 4 GB constraint;
- `phys->data[segment].addr = segaddr` (ncr.c:6701): `segaddr` is
  `vtophys()` of the transfer buffer supplied by the CAM periph —
  any kernel buffer, user-mapped pass(4) buffer, or dmamap-loaded
  segment; none is guaranteed below 4 GB.

There is no bus_dma(9) use anywhere in this driver — it is raw
`vtophys()` into 32-bit fields throughout. On a machine with RAM above
4 GB, `kmalloc`/CAM/periph allocations land above 4 GB with high
probability; the low 32 bits then address *some other* physical memory
and the chip reads/writes there. First-order observable: corrupt data
and random kernel panics under normal I/O; the chip cannot even fetch
the nccb correctly when `squeue[]`/`jump_nccb.l_paddr` are truncated
(garbage DSA -> wild script execution).

## What was executed on the guest

1. Confirmed driver compiled into GENERIC (symbols in /boot/kernel/kernel).
2. Confirmed default QEMU LSI 53C895A is claimed by sym(4), not ncr(4)
   (probe-priority semantics, sys/kern/subr_bus.c:1456 — larger probe
   value wins; sym -20 beats ncr -1000). Rebuilt the guest kernel with
   `SYM_SETUP_LP_PROBE_MAP=0x04` (the documented dual-driver mechanism,
   sym_conf.h:270-292) so ncr(4) claims the emulated 895A.
3. A/B runs of raw write+read+MD5 over an ncr-attached da(4) disk:
   - A: `-m 4096` (no physical memory above 4 GB) — baseline.
   - B: `-m 6144` (2 GB above 4 GB) — exercises the truncation window.
   Observed results in run.log / run.2.log.

The driver attaching and functioning on the emulated 895A at -m 4096
proves the code path is live; whether the A/B produced a mismatch within
this run is recorded verbatim in the logs (see run.2.log). Even without
a manifesting mismatch on the emulator (QEMU's LSI model may mask some
of the chaos), the type-level defect is unambiguous: a 53C8xx on any
real >4 GB amd64 system is being programmed with addresses that cannot
represent the host's buffers.

## Bottom line

Legacy-driver 64-bit correctness defect with direct memory-corruption
consequence; requires NCR 53C8xx HBA + >4 GB RAM (any such config, no
attacker action beyond doing I/O). fix.diff: constrain nccb allocation
below 4 GB and reject (CAM_REQ_TOO_BIG) any transfer whose CDB/sense/
scatter addresses cannot be represented in 32 bits, instead of silently
truncating.

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