# DF-5462 VERDICT

## Status: not_reproduced (race proven in code; window never hit in budgeted attempts)

## The defect is real and provable by inspection

Unsynchronized shared-memory access between CAM periph attach/detach and
every periph entry point, on the `cam_extend` array `scsi_pt.c` uses for
unit lookup:

* readers, NO lock held:
  - `ptopen`  sys/bus/cam/scsi/scsi_pt.c:153  `cam_extend_get(ptperiphs, unit)`
  - `ptclose` :189
  - `ptstrategy` :219
  - `ptioctl` :632
* writer, sim lock deliberately dropped around it:
  - `ptctor` :311-312 `cam_periph_unlock(); cam_extend_set(...)`
* the racing free:
  - `cam_extend_set` sys/bus/cam/cam_extend.c:54-78 — on every growth
    (`index >= nelem`, i.e. at every 8th unit): `kmalloc(new)` →
    `bcopy` → **`kfree(ea->ps)`** → `ea->ps = new`, with no lock
    anywhere (`struct extend_array` cam_extend.c:26-29 has no lock;
    `cam_extend_release` :90-97 stores NULL the same way).

Interleaving that corrupts: a reader in `cam_extend_get`
(cam_extend.c:81-88) loads `ea->ps` (old pointer), the growth frees the
old array, the reader's subsequent `ea->ps[index]` load reads **freed
kernel memory** and the value is used as `struct cam_periph *`:
`cam_periph_acquire()` (cam_periph.c:319-329 `periph->refcount++` — UAF
WRITE if the chunk was reused), then `periph->softc` deref and
`cam_periph_lock()` in ptopen:157-159.

All pt entry points are root-gated (ptopen:149
`caps_priv_check_self(SYSCAP_RESTRICTEDROOT)`; node 0600 root:operator,
ptctor:320-322). The same `cam_extend` defect also backs the lookup
paths of da/sa/cd/ch/ses/pass (cam_extend users), where attach churn is
ordinary hot-plug — but those drivers are equally permission-gated on
their raw device nodes.

## Verification attempts (all on the QEMU guest, root)

Rig: `ptsim.ko` fake CAM SIM, 512 LUN-0 targets answering INQUIRY as
T_PROCESSOR so `pt` attaches; growth driven in batches of 8 units (each
batch = exactly one `cam_extend_set` growth/kfree); `race5462` runs 8
processes doing open/ioctl/close on /dev/pt0../dev/pt7 (~130k
opens/s/child, ~780k/s aggregate); optional kernel stamper threads
kmalloc/memset/kfree chunks of every freed size class so that a raced
reader would see 0x00 (NULL periph → visible ENXIO anomaly) or 0x41
(non-canonical periph → GP fault in cam_periph_acquire) instead of the
still-valid old pointers.

| boot | config | windows | opens | result |
|------|--------|---------|-------|--------|
| run 2 | byte 0x00, batches→256 | ~31 | ~78M | no ENXIO anomaly; 3 of 8 racer children (units 0,1,2) never completed and the system lost exec capability — consistent with a hit whose garbage periph blocked in `cam_periph_lock`, but NOT provable (ssh session lost, no stack) |
| run 3 | byte 0x41, batches→256 (stopped ~111 nodes) | ~13 | ~150M total w/ run4 | clean |
| run 4 | byte 0x41, batches→512 (stopped ~231 nodes) | ~29 | ↑ | clean |
| cycle 5 | byte 0x41, batches→512 (stopped ~161 nodes) | ~19 | ~120M | guest hit the UNRELATED camq_remove NULL-deref panic (below), not the extend race |
| cycle 6,7 | aborted after cycle 5 triage | — | — | — |

Aggregate: ~5 boots, ~90+ growth windows, >150M racing open/ioctl/close
executions, zero race-specific manifestations (no 0x4141... fault, no
spurious ENXIO burst). Estimated hit probability per growth window with
the 1-2 instruction `ea->ps`→`ps[index]` gap and ~780k opens/s is
~0.1-0.3%; the observed zero hits over ~90 windows is consistent with
that estimate, i.e. the experiment simply needs more cycles than the
budget allowed.

## Independent lead discovered by the rig (not this finding)

Mass attach churn (~100-160 targets, batched or single scan)
deterministically triggers a NULL-deref write panic in
`camq_remove+0x35` (`removed_entry->index = -1` on a NULL queue slot;
fault VA 0x8; cambio SWI context) — a queue-array hole / double-remove
class bug in the xpt scheduler queue path. Captured in
`panic.camq-lead.txt`. Filed as a lead for the cam_xpt/cam_queue owner;
it capped this experiment's per-boot window count.

## Fix validation

`fix.diff` serializes `cam_extend_{set,get,release}` behind a leaf
lockmgr lock (`cam_extend_lk`, SI_SUB_DRIVERS SYSINIT; shared for
readers, exclusive for writers; internal `cam_extend_get_locked` helper
avoids the release→get recursion). Lock order is strictly
sim/buses → camext (leaf), taken nowhere else — no inversion.

Because the baseline race never reproduced, the fix could not be
behaviorally A/B'd; it was build- and load-path validated on the guest:

* applied to /usr/src, `make nativekernel KERNCONF=EVILPT` clean build
  (`fix_build.log`), installed, booted (kernel #1 22:30:30);
* rig attaches 8 pt devices under the lock, batched growth 8→64 with
  stampers active: 23,137,834 racing opens, zero anomalies, zero
  panics, guest healthy (`run.patched` section of run.log summary).

fix_status: not_testable (baseline unreproduced) with a passing
build/boot/growth smoke test.

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