# DF-2323 — VPD cleanup NULL-sentinel walk after krealloc shrink — evidence pack

Finding: `sys/bus/pci/pci.c:1225-1315` — the VPD error-cleanup loops
scan `vpd_ros`/`vpd_w` for a NULL `.value` sentinel, but the
success-path `krealloc` at pci.c:1225/1280 shrinks the arrays to
exactly the live entry count, deleting the zero padding the sentinel
scan relies on; a later error path walks and `kfree()`s past the
allocation.

## Verdict (short)

REPRODUCED (production-config kernel) — heap corruption: the verbatim
`pci_read_vpd` walks the shrunk array past its end and `kfree()`s an
attacker-influenced pointer; with a controlled neighbor chunk this is
a free-of-LIVE-object (silent on production builds) followed by the
same chunk being handed to a second owner (overlapping live kernel
objects, demonstrated end-to-end); with a wild pointer it is a kernel
page-fault panic. On the STOCK INVARIANTS kernel the walk is never
reached: the shrink `krealloc(..., M_ZERO)` itself panics first
(`KKASSERT((flags & M_ZERO) == 0)`, kern_slaballoc.c:1187) — also
captured. The finding's exact claimed trigger (bad checksum in a
second VPD item) is REFUTED (fresh array + leak); the real co-trigger
is the state < -1 I/O-error path. Fix A/B validated on both configs.

## Rig

Established verbatim-interposition kernel-compiled harness (same class
as the pnpparse/firewire/CAM rigs): `rig/dfpci_pre.c` (rename map +
interposition) + sha256-gated VERBATIM extracts of the pci.c functions
(`rig/gen.sh`, blob sha256 in env.txt) + `rig/dfpci_post.c`
(fabricated pcicfg device: 256-byte config space, VPD addr/data
register protocol with completion-bit stall point, BAR sizing
readback; kmalloc/krealloc/kfree trackers that log and pass through to
the REAL allocator; sysctl `debug.dfpcih.cmd` interface; hard-IRQ
systimer watchdog used by the DF-2324 runs). The guest's real PCI
stack keeps running untouched (harness TU is fully renamed).

## Reproduce

1. `sh rig/build.sh DFPCI` — build+install the INVARIANTS rig kernel
   (then `vm.sh up`).
2. Baseline stock: `sh rig/run_2323_stock.sh`
   (`sysctl debug.dfpcih.cmd="vpd errfirst"` control stays alive;
   `"vpd stock"` panics at the krealloc M_ZERO assert — capture via
   `dfbsd-qemu/vm.sh log 60 > panic_stock.txt`).
3. Production config: rebuild with KERNCONF=DFPCIP (gen.sh creates it;
   INVARIANTS commented out), then `sh rig/run_2323_prod.sh`
   (natural x5 + badck2 + controls + groom).
4. Wild variant: `sysctl debug.dfpcih.cmd="vpd wild"` → page-fault
   panic (panic_wild.txt).
5. Fixed A/B: apply `rig/fix.diff` to the guest /usr/src (patch -p0),
   `sh rig/gen_fixed.sh` (re-extracts from the FIXED file, new sha256
   gate), rebuild DFPCIPF and DFPCIF, re-run all scenarios
   (`rig/fixed_runs.log`).

## Files

* VERDICT.md — full narrative + impact quantification
* panic_stock.txt — stock kernel: krealloc M_ZERO assert at pci.c:1225
* panic_wild.txt — production kernel: page fault in kfree(wild ptr)
* rig/groom.log — the corruption run (UNTRACKED kfree of live canary,
  kmalloc(64) == canary, TWO LIVE OWNERS SAME MEMORY)
* rig/natural_5runs.log — 5 natural runs + adjacency snapshots
* rig/badck2.log, rig/controls.log — claimed-trigger refutation + controls
* rig/fixed_runs.log — fixed-kernel A/B (0 untracked frees)
* rig/ — harness sources, generators, gates, run scripts, fix.diff
* env.txt — kernels, compiler, CFLAGS, verbatim hashes, reachability
* manifest.json, verdict.json
