β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5360

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5360 Β· 11 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-5360

Confirmed kernel references

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.