# DF-5585 — pccard_cis_quirks fast path trusts stale `pf`/absent `pf_last` state: write-after-free / NULL-page write landmines (latent; unreachable with the shipped table)

File: `sys/bus/pccard/pccard_cis_quirks.c` (pass 2, GLM 5.3).  Full narrative: VERDICT.md.

## Reproduce (deterministic userland rig of the verbatim kernel loop)
    ./build.sh                          # cc -O2 -Wall -o poc_quirkfastpath poc_quirkfastpath.c
    ./run.sh                            # ./poc_quirkfastpath all

Expected decisive output (see run.log; exit 1 because findings>0):
    FREED-WRITE[repeat]: block ... (kmalloc #9) modified: first diff at +41 ...
    RESULT: DF-5585 repeat: quirk#3 fast path (pf_last==q->pf) STAILQ_INSERT_TAIL'd a cfe into the pf kfree'd at pccard_cis_quirks.c:282 -> write-after-free
    child died on SIGSEGV writing near address 0x28/0x30 ...
    RESULT: DF-5585 nulltemplate: NULL pf template + pf_last(NULL)==q->pf fast path -> &pf->cfe_head deref at offset 40 of NULL
    RESULT: DF-5585 matchmatrix: shipped table fast-path reachable by ANY card identity: NO
Built and executed on the DragonFly guest (DragonFly 6.5-DEVELOPMENT x86_64, cc 8.3).
The guest has no PCMCIA bridge (see env.txt), so the kernel path itself is not
enterable; the rig embeds byte-identical copies of pccard_cis_quirk_match()
(:201-216) and pccard_check_cis_quirks() (:218-292) — fidelity.diff shows the
only differences are the 3 documented shim substitutions — plus a failpoint
allocator that snapshots blocks at kfree time (kernel kfree does not scrub).

Scenarios: `negative` (clean install), `shippedfail` (shipped-table shape +
cfe-alloc failure -> CLEAN: today's kernel is safe), `repeat` (write-after-free),
`fixed_repeat` (fix.diff guard -> CLEAN), `nulltemplate` (NULL-page write,
SIGSEGV child), `fixed_nulltemplate` (guard -> CLEAN), `matchmatrix`
(machine-checked proof that NO card identity the shipped 8-entry table can
match ever takes the fast path).

What it proves: the fast path `if (pf_last == q->pf)` at :263 dereferences
`pf` under an invariant ("pf is the live copy built for template pf_last")
that the cfe-allocation error path at :280-285 breaks by kfree'ing `pf`
without invalidating it, and that a NULL `q->pf` entry breaks by matching
`pf_last == NULL == q->pf` right after the wipe loop (which always exits with
`pf == NULL`, :248-257).  Unreachable with the shipped quirk table (every
card's match set is <= 2 entries with distinct templates — proven by the
matchmatrix); one table edit (a repeated/alternating template, exactly how
:179-186 already shares templates across product ids) plus one M_NOWAIT
failure during card insertion turns it into kernel heap corruption.

Recommended fix (fix.diff, validated in-rig by the fixed_* scenarios):
NULL-guard table entries + reset `pf_last` on the :281-285 error path.
