# DF-5360 — Poisoned num_iospace/num_memspace survive the CFTABLE error-break

## Verdict in one line
Statically certain and model-reproduced data poison; the OOB consumer is
**latent** today because the same path increments `card->error` and
`pccard_attach_card` bails at pccard.c:204-208 before any consumer iterates
the counts — reported as Low (defense-in-depth / landmine), not Medium.

## Root cause (path:line)
`pccard_parse_cis_tuple`, CISTPL_CFTABLE_ENTRY:

1. `cfe` is **allocated and STAILQ_INSERT_TAIL'ed into `state->pf->cfe_head`
   first** (pccard_cis.c:908-917), and becomes the new `default_cfe` when the
   DEFAULT bit is set (pccard_cis.c:926-927).
2. Only then is the count parsed and stored **before validation**:
   - `cfe->num_iospace = 1 + (reg & PCCARD_TPCE_IO_RANGE_COUNT)`
     (pccard_cis.c:1044) — attacker range byte admits 1..16 while
     `cfe->iospace[]` holds 4 entries (pccardvar.h:102).
   - `cfe->num_memspace = (reg & PCCARD_TPCE_MS_COUNT) + 1`
     (pccard_cis.c:1164) — admits 1..8 while `cfe->memspace[]` holds 2
     entries (pccardvar.h:109).
3. The "too many" branches (pccard_cis.c:1047-1055 and :1167-1175) print,
   `state->card->error++`, and `break` — **leaving the out-of-range count
   stored on the already-queued cfe** and on `default_cfe`.
4. Poison **propagates**: any later CFTABLE with a different index does
   `*cfe = *state->default_cfe` (pccard_cis.c:914), copying the poisoned
   counts into fresh queue entries (model S5 shows cfe#2 inheriting
   num_iospace=16 from cfe#1 and adding num_memspace=8).

## Consumers (why it matters at all)
- `pccard_function_init` iterates `i < cfe->num_iospace` reading
  `cfe->iospace[i].start/.length` (pccard.c:429-450) and
  `i < cfe->num_memspace` (pccard.c:452-474) — with counts 16/8 these read
  up to ~120/~128 bytes past the `kmalloc(sizeof(*cfe))` chunk
  (model S5, pointer-arith instrumented) and feed the OOB-read
  start/end/length into `bus_alloc_resource` (pccard.c:439-440).
- `pccard_print_cis` also iterates both counts (pccard_cis.c:550-556,
  561-571) but is only reached with bootverbose/pccard_debug.

## Honest reachability gate
Every poison path does `state->card->error++`, and `pccard_attach_card`
rejects error cards before scanning functions (pccard.c:204-208
`if (sc->card.error) { ...; return (1); }`), so **no in-tree consumer
currently iterates a poisoned count**. The defect is a stored out-of-range
index (CWE-129) that any refactor, quirk table, or future consumer trips
over; hence Low / latent.

## Model evidence (run.log, scenario `poison`)
```
scan r=0 iters=4 card.error=2 poison_io=16 poison_mem=8
live cfe number=1 num_iospace=16 (array 4) num_memspace=0 (array 2)
GATE: card.error=2 -> pccard_attach_card bails at pccard.c:204-208 TODAY ...
consumer pccard.c:429  i<num_iospace=16 -> 8 OOB element reads, deepest 120
  bytes past the 160-byte kmalloc chunk
live cfe number=2 num_iospace=16 (array 4) num_memspace=8 (array 2)
consumer pccard.c:429  ... 8 OOB element reads, deepest 120 bytes ...
consumer pccard.c:452  i<num_memspace=8 -> 6 OOB element reads, deepest 128
  bytes past the chunk
```
(model struct trims runtime-only fields, hence 160 B vs the kernel's ~176 B;
the overrun is identical in kind.)

## Fix
Zero the counts on the error path (fix.diff): `cfe->num_iospace = 0;` before
the :1054 break and `cfe->num_memspace = 0;` before the :1174 break, so no
queued or copied cfe can carry a count larger than its array. Alternative:
validate before storing. Both are one-line-per-site.

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