# DF-4237 VERDICT — unchecked `sili_ata_get_xfer()` NULL dereference in sili_cam.c probe paths

**Status: not_reproduced (hardware-gated: guest has no sili(4) HBA).**
**Code-trace + guest-run model: proven.** Confidence: likely.
Impact if triggered: kernel NULL-pointer panic (local DoS).

## 1. Root cause (path:line)

`sili_ata_get_xfer()` returns NULL when the port ccb free list is empty:

* sys/dev/disk/sili/sili.c:2318-2327 — `ccb = sili_get_ccb(ap); if (ccb == NULL) { DPRINTF(... "NULL ccb"); return (NULL); }`
* sys/dev/disk/sili/sili.c:2003-2018 — `sili_get_ccb()` pops `ap_ccb_free`, returns NULL when empty. No blocking, no waiting.

`sili_cam.c` dereferences the result unconditionally at (at least) these sites:

* **sili_cam.c:289-294** (IDENTIFY step of `sili_cam_probe`):
  `xa = sili_ata_get_xfer(ap, atx); xa->complete = sili_ata_dummy_done; xa->data = ...`
  → NULL write at `NULL->complete`.
* **sili_cam.c:371-372** (NCQ renegotiation loop):
  `xa = sili_ata_get_xfer(ap, atx); if (xa->tag < at->at_ncqdepth)`
  → NULL read at `NULL->tag`.
* Defensive (same shape, currently shielded by devq accounting but exposed to
  the same renegotiation race window): sili_cam.c:533, 556, 583, 652
  (probe_disk/set_xfer), sili_cam.c:1026 (`sili_xpt_scsi_disk_io`), sili_cam.c:1420
  (`sili_xpt_scsi_atapi_io`).

## 2. Why the free list can be empty when the probe runs

Pool construction:

* sili.h:706 — `SILI_MAX_CMDS 31` (`sc_ncmds ≤ 31`).
* sili.c:265-268 — slot 1 is reserved as `ap_err_ccb` and never placed on the
  free list; the other `sc_ncmds-1` slots are. Free list capacity = 30.
* sili_cam.c:101-115 — the CAM devq opening is `sc_ncmds - 1` = 30 and
  `max_tags` starts at 1, raised to `at_ncqdepth - 1` (≤ 30) only after NCQ
  negotiation (sili_cam.c:377-380). So up to 30 CAM ccbs can concurrently
  hold an xa.

Probe entry points have **unequal protection**:

* `XPT_SCSI_IO` (sili_cam.c:939-963): the current ccb occupies a devq slot
  but has not yet taken an xa, so ≤ 29 others hold xas → ≥ 1 free slot for
  the IDENTIFY at :289. **But the renegotiation loop at :370-376 permanently
  holds every free slot whose tag ≥ `at_ncqdepth`** ("throw away any
  ata_xfer's beyond the negotiated number of slots"). With the last free slot
  held and no completion in the window, the next loop iteration's
  `sili_ata_get_xfer` returns NULL and `xa->tag` at :372 faults. With
  `at_ncqdepth = 2` (device identify word 75 reports qdepth=1, sili_cam.c:365)
  only tag 0 cycles, so the drain is near-certain.
* `XPT_PATH_INQ` wildcard (sili_cam.c:878-881), `XPT_ENG_EXEC`
  (sili_cam.c:846-850) and the port thread state machine (sili.c:511-512)
  run `sili_port_state_machine` → `sili_cam_probe` with **no devq credit**;
  with 30 in-flight ccbs the free list is empty and even the IDENTIFY at
  :289-290 NULL-derefs.

Path to NEED_IDENT without revoking in-flight ccbs:

* sili.c:845-849 — `sili_port_softreset()` success sets
  `ap_probe = ATA_PROBE_NEED_IDENT` while in-flight ccbs stay `ap_active`
  (they hold their slots until their own timeouts).
* Softreset is reached from `XPT_RESET_DEV` (sili_cam.c:903-913), from error
  recovery, and from the state machine (sili.c:509-510).

## 3. Model verification (run on the audit guest)

`sili_ncq_pool_model.c` encodes the pool rules exactly (31 slots, slot 1
reserved, TAILQ head-pop/tail-push per sili.c:2011/2028, devq opening 30,
renegotiation loop of sili_cam.c:370-376, `at_ncqdepth = (qdepth & 0x1F)+1`
of sili_cam.c:365-368). Guest run (DragonFly 6.5-DEVELOPMENT, cc 8.3):

* Scenario C (idle port, control): no NULL — the bug needs concurrent I/O.
* Scenario A (XPT_SCSI_IO reprobe, 29 in-flight, device re-reports
  qdepth=1): **`PANIC: sili_cam.c:372 xa->tag read through NULL xa after
  loop held 1 slots (iteration 2/31)`** — the loop holds the single free
  slot (tag 30 ≥ ncqdepth 2), the next iteration gets NULL, the kernel
  derefs.
* `guard` mode (fix.diff semantics): defers, no panic, both A and B.

See `run.log` (full untrimmed output) and `build.log`.

## 4. Honest classification

* The guest kernel contains sili(4) compiled in (`kldload sili` → "module
  already loaded or in kernel") but has **no SiI 3124/3132 PCI device**
  (`pciconf -l` shows only i440FX/virtio; `env.txt`). The panic therefore
  cannot be triggered live on this guest → `not_reproduced`, impact `none`
  (nothing demonstrated live), per the same discipline used for DF-4027,
  DF-2517, DF-2518.
* On real hardware this is a local-DoS panic requiring queue saturation
  concurrent with a reprobe (error recovery / reset / hot-plug). A crafted
  SATA device (the audit's DF-3690.. family threat model) triggers it
  deterministically by reporting qdepth=1 after forcing an error-recovery
  softreset with I/O in flight.
* Not memory corruption: the faulting access is to the NULL direct-map page;
  no attacker-controlled content is written or read (CWE-476).

## 5. Fix

`fix.diff` in this directory:

1. sili_cam.c:289 — on NULL, log + `return (EBUSY)` **before** the err:
   label (keeps `at_probe = NEED_IDENT` so the next XPT_SCSI_IO retries;
   at attach time the pool is full, so no behavior change at boot).
2. sili_cam.c:371 — `if (xa == NULL) break;` in the renegotiation loop
   (pool exhausted by in-flight I/O; the held slots still achieve the
   "reserve one slot for recovery" intent).
3. Defensive guards at sili_cam.c:1026/1420 completing the ccb with
   `CAM_RESRC_UNAVAIL`.

fix_status: not_testable (no sili HBA on the guest; guards are modeled in
`./sili_model guard` instead).

## 6. Relation to known findings

Distinct from the sili knowns: DF-1338/DF-2517 (SGE/PRDT overflow in
`sili_load_prb_callback`), DF-1339/DF-2518 (NCQ error tag OOB),
DF-4027 (deferred-timeout ccb double lifecycle — that finding is about
`put_xfer` of port-owned ccbs; this one is about the *absence* of a NULL
check on `get_xfer`), DF-1462/DF-2519 (ATA_PASS_16 copy-paste).

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