Poisoned num_iospace=16/num_memspace=8 persist on the queued cfe after the CFTABLE error-break and propagate via the default-copy
Summary
Counts stored before validation, error-breaks leave them: num_iospace=16 vs iospace[4], num_memspace=8 vs memspace[2], and *cfe=*default_cfe propagates both into later entries. The only count-iterating consumers (pccard_function_init loops, print_cis) would read ~120-128B past the ~176B chunk - honestly gated today by card->error at pccard.c:204-208, so latent landmine one refactor from a heap OOB read. Model-verified (poison scenario: counts on live cfes + consumer loops reading 8/6 OOB elements). Zero-counts-on-error fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5360 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.2 KB | β raw | |
| VERDICT.md | β | 3.8 KB | β raw | |
| poc_pccard_cis2.c | β | 37.0 KB | view raw | |
| build.sh | β | 104 B | view raw | |
| run.sh | β | 105 B | view raw | |
| build.log | β | 495 B | view raw | |
| run.log | β | 1.2 KB | view raw | |
| env.txt | β | 918 B | view raw | |
| fix.diff | β | 652 B | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 4.1 KB | view raw |
DF-5360 β Poisoned num_iospace=16/num_memspace=8 persist on queued cfe after CFTABLE error-break (latent heap-OOB consumer)
File: sys/bus/pccard/pccard_cis.c (pass 2, GLM 5.3). Full narrative: VERDICT.md.
Reproduce (deterministic userland model of the kernel parse/consume chains)
./build.sh # cc -O2 -Wall -o poc_pccard_cis2 poc_pccard_cis2.c ./run.sh # ./poc_pccard_cis2 poison
Expected decisive output: the RESULT: DF-5360 ... line (see run.log; exit 0).
Built and executed on the DragonFly guest toolchain (DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT),
cc 8.3. The QEMU guest has no PCMCIA bridge, so the kernel path itself is not
enterable; the model mirrors pccard_cis.c:100-444 (walk), :612-1260 (handlers),
decode_funce :1262-1294 and the pccard.c:429-474 consumer loops statement-by-
statement. Run ./poc_pccard_cis2 all for every scenario incl. negatives.
What it proves: card.error gate (pccard.c:204) blocks today's consumers; counts > array persist and propagate via the :914 default copy. Recommended fix: Zero cfe->num_iospace / cfe->num_memspace on the 'too many spaces' error paths (:1054/:1174) (fix.diff).
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:
cfeis allocated and STAILQ_INSERT_TAIL'ed intostate->pf->cfe_headfirst (pccard_cis.c:908-917), and becomes the newdefault_cfewhen the DEFAULT bit is set (pccard_cis.c:926-927).- 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 whilecfe->iospace[]holds 4 entries (pccardvar.h:102). -cfe->num_memspace = (reg & PCCARD_TPCE_MS_COUNT) + 1(pccard_cis.c:1164) β admits 1..8 whilecfe->memspace[]holds 2 entries (pccardvar.h:109). - The "too many" branches (pccard_cis.c:1047-1055 and :1167-1175) print,
state->card->error++, andbreakβ leaving the out-of-range count stored on the already-queued cfe and ondefault_cfe. - 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_inititeratesi < cfe->num_iospacereadingcfe->iospace[i].start/.length(pccard.c:429-450) andi < cfe->num_memspace(pccard.c:452-474) β with counts 16/8 these read up to ~120/~128 bytes past thekmalloc(sizeof(*cfe))chunk (model S5, pointer-arith instrumented) and feed the OOB-read start/end/length intobus_alloc_resource(pccard.c:439-440).pccard_print_cisalso 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.
Fix verification
not_testablefix.diff authored post-verification; cannot be exercised against real PCMCIA hardware in this environment. Reviewed for correctness: zeroing the count before break leaves the cfe on the queue with num_iospace=0, which every consumer loop (pccard.c:429/:452, pccard_cis.c:550/:561) treats as 'no spaces'; card->error++ still rejects the card at pccard.c:204.
findings/poc/DF-5360/fix.diff
Confirmed kernel references
- sys/bus/pccard/pccard_cis.c:908
- sys/bus/pccard/pccard_cis.c:914
- sys/bus/pccard/pccard_cis.c:916
- sys/bus/pccard/pccard_cis.c:1044
- sys/bus/pccard/pccard_cis.c:1047
- sys/bus/pccard/pccard_cis.c:1054
- sys/bus/pccard/pccard_cis.c:1164
- sys/bus/pccard/pccard_cis.c:1167
- sys/bus/pccard/pccard_cis.c:1174
- sys/bus/pccard/pccardvar.h:102
- sys/bus/pccard/pccardvar.h:109
- sys/bus/pccard/pccard.c:204
- sys/bus/pccard/pccard.c:429
- sys/bus/pccard/pccard.c:452
Detail
Evidence (decisive lines)
run.log (scenario 'poison': card.error=2, live cfe num_iospace=16 (array 4), second cfe num_iospace=16/num_memspace=8 via the :914 copy, consumer loops -> 8/6 OOB element reads deepest 120/128 bytes past the chunk, plus the GATE line documenting pccard.c:204-208); VERDICT.md (line-accurate trace); fix.diff
PoC changes
Wrote the full model (poc_pccard_cis2.c) mirroring the walk, all pass-2-relevant handlers and the pccard.c consumer loops; fixed the model's CIS-image encoding after the first guest run (attribute-space tuple bytes are 2 bus-bytes apart per pccardvar.h:255-259).
Verified recommended fix
Zero cfe->num_iospace / cfe->num_memspace on the 'too many spaces' error paths before the break so no queued or copied cfe carries a count larger than its array.
Verdict
Statically certain and model-reproduced data poison, latent today; kernel path not enterable in QEMU (no PCMCIA bridge). The CISTPL_CFTABLE_ENTRY handler queues the cfe (pccard_cis.c:908-917) and stores the card-supplied counts BEFORE validating them: cfe->num_iospace = 1+(reg&0x0F) at pccard_cis.c:1044 admits 16 against iospace[4] (pccardvar.h:102), cfe->num_memspace = (reg&0x07)+1 at pccard_cis.c:1164 admits 8 against memspace[2] (pccardvar.h:109); the 'too many' error-breaks at pccard_cis.c:1047-1055 and :1167-1175 leave the out-of-range count on the live cfe, and the default-copy cfe = state->default_cfe at :914 propagates it to later queue entries (model S5: cfe#2 inherits num_iospace=16 and adds num_memspace=8). The only count-iterating consumers, pccard_function_init (pccard.c:429-450 io, :452-474 mem) and pccard_print_cis (pccard_cis.c:550-571), would read up to ~128 bytes past the ~176-byte kmalloc chunk and feed OOB-read start/length into bus_alloc_resource (pccard.c:439-440) - model-instrumented as 8 and 6 OOB element reads. HONEST GATE: every poison path also does state->card->error++ and pccard_attach_card rejects error cards at pccard.c:204-208 before any consumer runs, so today's impact is none; reported Low as a stored-out-of-range-index landmine (CWE-129) one refactor away from a heap OOB read.
No comments yet.