Unbounded out-of-bounds read loops in CISTPL_CFTABLE_ENTRY power and misc-extension parsing
| Field | Value |
|---|---|
| ID | DF-1041 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-125 Out-of-bounds Read; CWE-835 Loop with Unreachable Exit Condition on attacker data |
| File | sys/bus/pccard/pccard_cis.c |
| Lines | 984-1007, 1241-1247 (esp. 995-1003, 1244-1246) |
| Area | bus/pccard (PCMCIA/CardBus CIS parser) |
| Confidence | likely |
| Discovered | 2026-07-14 |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
In pccard_parse_cis_tuple's CISTPL_CFTABLE_ENTRY handler, the power-parameter skip loop
(do-while at pccard_cis.c:995-1003) and the misc-extension skip loop (while at
pccard_cis.c:1243-1246) advance the parse index idx without ever comparing it against
tuple->length. Each iteration reads a card-supplied byte and continues while its high bit
is set; a malicious card that keeps the continuation bit set forces idx to grow unboundedly,
driving pccard_tuple_read_1() (which itself performs no length check,
sys/bus/pccard/pccardvar.h:258-259) past the declared tuple body, then past the 4096-byte
CIS resource mapping, eventually page-faulting the kernel or reading arbitrary bridge/card
memory.
Root cause
The power section (pccard_cis.c:984-1007) iterates i over up to 3 parameter-selection
bytes; for each set bit among 7 it enters:
do {
reg2 = pccard_tuple_read_1(tuple, idx);
idx++;
} while (reg2 & 0x80); /* pccard_cis.c:995-1003 */
The continuation predicate is the card-supplied byte's bit 7 β there is no
idx < tuple->length guard. pccard_tuple_read_1 (pccardvar.h:258-259) reads at
mult*(tuple->ptr + 2 + idx) with no length validation.
The misc section repeats the same pattern:
while (reg & PCCARD_TPCE_MI_EXT) {
reg = pccard_tuple_read_1(tuple, idx);
idx++; /* pccard_cis.c:1243-1246 */
}
Continuation is solely on the card-supplied PCCARD_TPCE_MI_EXT (0x80) bit. Unlike the
iospace/irq/memspace sections that DO check tuple->length <= idx before proceeding (lines
1024, 1102, 1134, 1225), the power and misc loops have no such guard at all. Because
tuple->length is a uint8_t (read at line 198), a card can declare length=2 but keep
the parser looping with extension bytes far beyond the tuple body.
Threat model & preconditions
- Attacker position: Malicious PC Card / CardBus CIS data (physical hot-plug, same surface as DF-1040).
- Privileges gained or impact: Denial of service via panic. Even within the bridge window the parser reads card/bridge bytes (which the card may shape), but those values are discarded (power/misc are "skip, don't save" β see comments at lines 985, 1009), so the observable impact is DoS, not info exfiltration.
- Required config or capabilities: Default kernel with
pccardconfigured; physical access to the PCMCIA slot. - Reachability:
pccard_parse_cis_tupleis the callback registered at line 95 and invoked for every tuple inpccard_scan_cis's default switch case (lines 347/349). ACISTPL_CFTABLE_ENTRY (0x1B)tuple whose feature byte setsPCCARD_TPCE_FS_POWER(lines 977, 984) followed by a parameter-selection byte with several bits set, then a run of bytes all with bit 7 set, drivesidxinto the thousands.
Proof of concept
PoC source: findings/poc/DF-1041/cis_image.bin and findings/poc/DF-1041/README.md
Build a CIS image containing a CISTPL_CFTABLE_ENTRY:
| Offset | Value | Meaning |
|---|---|---|
| 0x00 | 0x1B | CISTPL_CFTABLE_ENTRY code |
| 0x02 | 0x08 | declared length, small |
| 0x04 | 0x80 | interface present |
| 0x06 | 0x03 | feature byte: PCCARD_TPCE_FS_POWER_VCCVPP1VPP2 |
| 0x08 | 0x7F | parameter selection byte β all 7 parameter bits set |
| 0x0A⦠| 0xFF⦠| long run of 0xFF bytes (each keeps reg2 & 0x80 true) |
Pad the image so ~65000 bytes of 0xFF sit after the tuple. On insertion, idx walks
through all those 0xFF bytes; once idx*mult exceeds the bridge window,
bus_space_read_1 faults β panic.
Build & run
# Flash cis_image.bin into a PCMCIA attribute-memory EEPROM and insert the # card, OR boot DragonFlyBSD under QEMU with a PCMCIA/PCIC bridge. # Alternate: a unit test that feeds a crafted pccard_tuple through # pccard_parse_cis_tuple with a bus_space shim that faults past the mapping.
Expected output
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x<offset past bridge window> pccard_parse_cis_tuple(...) at pccard_cis.c:996 (or :1244) pccard_scan_cis(...) at pccard_cis.c:349
In practice the bridge window is 64 KB and full of 0xFF, so the loop may run ~32 000
iterations before faulting; the bug is the absence of any length-based termination, not the
exact panic offset.
Impact
Local/physical DoS from a malicious PCMCIA card. Lower confidence than DF-1040 because the exact panic offset depends on bridge window sizing (64 KB common vs. 4096-byte CIS mapping), but the missing-bounds-check is real and demonstrable.
Recommended fix
Guard each unbounded read against the declared tuple length, aborting the CFTABLE_ENTRY
parse (via the existing abort_cfe label at line 1251) when the card tries to read past
its own tuple:
--- a/sys/bus/pccard/pccard_cis.c
+++ b/sys/bus/pccard/pccard_cis.c
@@ -993,6 +993,8 @@
if ((reg >> j) & 0x01) {
/* skip over bytes */
do {
+ if (idx >= tuple->length)
+ goto abort_cfe;
reg2 = pccard_tuple_read_1(tuple, idx);
idx++;
/*
@@ -1242,6 +1244,8 @@
cfe->maxtwins = reg & PCCARD_TPCE_MI_MAXTWINS;
while (reg & PCCARD_TPCE_MI_EXT) {
+ if (idx >= tuple->length)
+ goto abort_cfe;
reg = pccard_tuple_read_1(tuple, idx);
idx++;
}
Defense-in-depth: the same idx < tuple->length guard should ideally precede every
pccard_tuple_read_1 in this function (the timing section at lines 1010-1021 also reads
idx without a check, though it is bounded to a few fixed increments and caught by the
subsequent iospace check at 1024); a comprehensive fix would introduce a small
checked-reader helper, but the two patches above close the genuinely unbounded loops.
References
- PC Card Standard, Configuration Format, CISTPL_CFTABLE_ENTRY tuple
sys/bus/pccard/pccardvar.h:258-259βpccard_tuple_read_1(no length check)sys/bus/pccard/pccard_cis.c:1024, 1102, 1134, 1225β existing length checks the power/misc loops should mirror- FreeBSD
sys/dev/pccard/pccard_cis.cβ shared heritage, same parser structure
Timeline
- 2026-07-14 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1041 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace verbatim replication of pccard_parse_cis_tuple's CFTABLE_ENTRY handler, demonstrates unbounded idx growth on cis_image.bin | 9.4 KB | view raw |
| harness_valid.c | trigger-source | well-formed tuple sanity test, proves fix.diff is benign on legitimate input | 2.8 KB | view raw |
| cis_image.bin | trigger-data | 65536-byte crafted CIS image: CISTPL_CFTABLE_ENTRY with all-0xFF continuation bytes | 64.0 KB | β download |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 178 B | view raw |
| run.sh | run-script | ./harness cis_image.bin | 138 B | view raw |
| build.log | build-log | harness build output (cc, exit 0) | 66 B | view raw |
| run.log | run-log | decisive harness run on patched kernel (#1): UNPATCHED idx=50001, PATCHED idx=8, harness_valid PASS | 1.5 KB | view raw |
| baseline_run.log | run-log | harness run on unpatched baseline (#0): same UNPATCHED/PATCHED branch contrast | 1.3 KB | view raw |
| patched_run.log | run-log | second harness run on patched kernel | 1.3 KB | view raw |
| fix.diff | suggested-fix | git-apply-able unified diff adding idx>=length -> goto abort_cfe guards in power do-while and misc while-loop (matches finding proposal) | 608 B | view raw |
| fix_build.log | build-log | full make -j6 nativekernel output for the single-fix kernel (35553 lines, rc=0) | 5.6 MB | β download |
| env.txt | environment | guest uname, kern.version, cc version, kernel sha256, pccard sysctls | 503 B | view raw |
| VERDICT.md | verdict | full narrative: source-level trace, mechanism, harness demonstration, fix validation | 8.3 KB | β raw |
| README.md | readme | build/run instructions and CIS image layout | 3.6 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1041 PoC β CISTPL_CFTABLE_ENTRY power/misc unbounded read loops
Verdict
REPRODUCED (harness-level β runtime unreachable on guest without PCMCIA HW). Fix VALIDATED on a built-and-booted single-fix kernel.
See VERDICT.md for the full narrative. See manifest.json for the artifact
catalog. The decisive log is run.log; fix_build.log is the full 35553-line
single-fix kernel build.
Why a harness (not a runtime panic)
The QEMU/KVM audit guest has no PCMCIA/CardBus bridge β devinfo shows
no pccard/cbb device, /dev/pccard* and /dev/cardbus* do not exist, and
there is no syscall/ioctl/devfs path that feeds attacker bytes into
pccard_parse_cis_tuple. The bug is latent, triggerable only by physical
PCMCIA card insertion (the threat model in the finding markdown).
harness.c is a verbatim, line-cited replication of the CFTABLE_ENTRY
parser (pccard_cis.c:871-1253) that demonstrates the unbounded idx
growth on the supplied CIS image without needing real PCMCIA hardware.
This follows the precedent set by sibling finding DF-1040 (same parser).
Trigger
CIS attribute-memory image containing a malformed CISTPL_CFTABLE_ENTRY:
| Offset | Value | Meaning |
|---|---|---|
| 0x00 | 0x1B | CISTPL_CFTABLE_ENTRY code |
| 0x02 | 0x08 | declared length (8 bytes, intentionally short) |
| 0x04 | 0x80 | INDX: interface present |
| 0x06 | 0x41 | interface byte |
| 0x08 | 0x01 | feature byte: power=Vcc only |
| 0x0A | 0x7F | param selection: all 7 bits set |
| 0x0C.. | 0xFF | long run of 0xFF (each keeps reg2 & 0x80 true) |
(cis_image.bin is the 64 KiB version of this β every even byte after 0x0C
is 0xFF. The kernel's PCCARD_CIS_SIZE is 4096, so on real HW the panic
would occur much earlier, at byte_off >= 4096, i.e. idx >= 2046.)
Build & run
./build.sh # cc -O2 -Wall -o harness harness.c ./run.sh # ./harness cis_image.bin
Expected:
=== UNPATCHED (master pccard_cis.c) === final idx reached = 50001 bytes (cap; first OOB at idx=32766) === PATCHED (fix.diff: idx>=length -> abort_cfe) === final idx reached = 8 bytes (capped at length=8) SUMMARY: unpatched idx=50001 (>49993 past length) vs patched idx=8 (==length).
harness_valid (run separately) confirms the fix doesn't break legitimate
input β both branches reach the same idx on a well-formed tuple.
On real hardware
A malicious PC Card / CardBus card flashed with this CIS, when inserted
into a DragonFlyBSD system with the default kernel, drives the parser's
idx past the 4096-byte bus-space CIS window. The expected kernel panic:
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x<offset past bridge window> pccard_parse_cis_tuple(...) at pccard_cis.c:996 (or :1244) pccard_scan_cis(...) at pccard_cis.c:349
Kernel references
sys/bus/pccard/pccard_cis.c:984-1007β power parameter loop (no length guard)sys/bus/pccard/pccard_cis.c:995-1003β do-while continuing on card byte's bit 7sys/bus/pccard/pccard_cis.c:1243-1246β misc-extension while loop (no length guard)sys/bus/pccard/pccard_cis.c:1024, 1102, 1134, 1225β existing length checkssys/bus/pccard/pccard_cis.c:62βPCCARD_CIS_SIZE 4096sys/bus/pccard/pccardvar.h:255-259βpccard_tuple_read_1(no length check)
DF-1041 β VERDICT
Verdict
REPRODUCED (at the harness level β runtime unreachable on this guest). The bug is real and the fix is validated on a built-and-booted single-fix kernel.
Severity: Low. Local/physical DoS via a malicious PCMCIA card.
No privilege-escalation surface β the corrupted reads are byte comparisons
that the parser immediately discards (/* skip over power, don't save */,
pccard_cis.c:985; cfe->maxtwins = reg & ... then the rest is dropped).
Why runtime-inconclusive on this guest (but code-certain)
The guest has no PCMCIA/CardBus bridge hardware:
$ ssh dfbsd-maxx 'devinfo 2>/dev/null | grep -iE "pccard|pcmcia|cardbus|cbb"' (empty) $ ls /dev/pccard* /dev/cardbus* ls: No such file or directory
The kernel compiles in pccard and cbb (they show in kldstat -v),
but with no bridge device the pccard_scan_cis code path is never entered.
There is no syscall, devfs node, or ioctls that feeds attacker-controlled
CIS bytes into pccard_parse_cis_tuple. This is a latent bug triggerable
only by physical PCMCIA card insertion (the threat model in the finding
markdown). Per the procedure (and the precedent set by DF-1040, the sibling
finding in the same parser), the bug is confirmed via source-level trace +
userspace harness replicating the parser logic verbatim.
Mechanism (source trace)
pccard_parse_cis_tuple (sys/bus/pccard/pccard_cis.c) handles
CISTPL_CFTABLE_ENTRY (0x1B) starting at line 871. Inside, two loops
advance the parse index idx without ever comparing it against
tuple->length:
Bug locus 1 β power do-while (pccard_cis.c:995-1003)
if (power) { /* :984 */
for (i = 0; i < power; i++) { /* :987 */
reg = pccard_tuple_read_1(tuple, idx); idx++; /* :988 */
for (j = 0; j < 7; j++) { /* :991 */
if ((reg >> j) & 0x01) { /* :993 */
do {
reg2 = pccard_tuple_read_1(tuple, idx); /* :996 */
idx++; /* :997 */
} while (reg2 & 0x80); /* :1003 */
}
}
}
}
Termination is solely on the card-supplied byte's bit 7. A malicious card
that keeps bit 7 set forces idx to grow indefinitely. There is no
entry guard (if (tuple->length <= idx) goto abort_cfe;) before this
block, and no in-loop guard. Compare the iospace/irq/memspace/misc-entry
guards at pccard_cis.c:1024, 1102, 1134, 1225.
Bug locus 2 β misc-extension while loop (pccard_cis.c:1243-1246)
if (misc) {
if (tuple->length <= idx) goto abort_cfe; /* :1225 entry guard */
reg = pccard_tuple_read_1(tuple, idx); idx++; /* :1230 */
...
while (reg & PCCARD_TPCE_MI_EXT) { /* :1243 */
reg = pccard_tuple_read_1(tuple, idx); /* :1244 β NO guard */
idx++; /* :1245 */
}
}
The misc section has an entry guard at line 1225 but the continuation while loop reads again with no guard. Same continuation-bit-trust bug.
Sink
pccard_tuple_read_1(tuple, idx) (sys/bus/pccard/pccardvar.h:258-259):
#define pccard_tuple_read_1(tuple, idx1) \
(pccard_cis_read_1((tuple), ((tuple)->ptr+(2+(idx1)))))
#define pccard_cis_read_1(tuple, idx0) \
(bus_space_read_1((tuple)->memt, (tuple)->memh, (tuple)->mult*(idx0)))
No length validation. When mult*(ptr+2+idx) exceeds the 4096-byte
PCCARD_CIS_SIZE bus-space mapping (pccard_cis.c:62, 131),
bus_space_read_1 page-faults β kernel panic.
Harness demonstration
harness.c is a verbatim, line-cited replication of the CFTABLE_ENTRY
parser. Fed cis_image.bin (declared length 8, all-0xFF continuation
bytes), the harness reports:
=== UNPATCHED (master pccard_cis.c) === declared tuple->length = 8 bytes pccard_tuple_read_1 calls= 50001 (harness cap=50000) final idx reached = 50001 bytes overshoot past length = 49993 bytes (idx grew 6250x past declared length) kernel byte offset = mult*(ptr+2+idx) = 2*(0+2+50001) = 100006 faulted past img_len? = YES -> first OOB read at idx=32766, byte_off=65536 (>= img_len=65536)
In the actual kernel with PCCARD_CIS_SIZE = 4096 (pccard_cis.c:62), the
page-fault panic occurs at byte_off >= 4096, i.e. idx >= 2046. The
65536-byte image in the harness makes the demonstration robust; the bug
manifests identically regardless of image size β what matters is that
idx runs past the declared tuple body with no length-based termination.
=== PATCHED (fix.diff: idx>=length -> abort_cfe) === final idx reached = 8 bytes (capped at length=8) pccard_tuple_read_1 calls= 8 overshoot past length = 0 bytes faulted? = no
harness_valid.c further proves the fix is benign on legitimate input:
a well-formed CFTABLE_ENTRY tuple parses to the same idx (5 reads) under
both unpatched and patched logic.
Exploit chain
none β read-only OOB. There is no privilege boundary to cross. The
read bytes feed only reg2 & 0x80 (continuation test) and
cfe->maxtwins = reg & 0x03 (a 2-bit field), and the parser doesn't
exfiltrate the bytes anywhere observable to userspace. Realistic impact
ceiling: kernel panic on physical card insertion.
Recommended fix (validated)
fix.diff β adds if (idx >= tuple->length) goto abort_cfe; at the top
of the power do-while body (pccard_cis.c:995) and at the top of the misc
while-loop body (pccard_cis.c:1243), mirroring the existing
tuple->length <= idx checks already present at lines 1024, 1102, 1134,
1225. Reuses the existing abort_cfe label (pccard_cis.c:1251).
Matches finding proposal β the diff is byte-for-byte the one in the
finding markdown's ## Recommended fix section.
Phase 8 β fix validation
- Baseline (
#0, unpatched): harness UNPATCHED branch reproduces the bug (idxβ50001, first OOB read at idx=32766). Booted6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026, kernel sha2565dc83dac19ad09effd6241c33e0c0669d41b6497ee92d87d3a2e45f287bc22ad. - Apply fix:
patch -p1 --forward < /root/fix.diffβ both hunks applied cleanly. - Build:
make -j6 nativekernel KERNCONF=X86_64_GENERICβ rc=0, 35553-line log, pccard_cis.c recompiled with-Werrorand no errors. - Install + reboot: copied
kernel.stripped+kernel.debug, rebooted. New kernel:6.5-DEVELOPMENT #1: Tue Jul 14 14:55:19 UTC 2026, sha256e37bbca3558de6abb66906b850f2d2c0791e7e7bea732629fed1f5eb71d8ac27. - Confirm patched source in running kernel:
sedof/usr/src/sys/bus/pccard/pccard_cis.cshows the bounds checks in place at lines 995-996 and 1244-1245. - Confirm code path closed: harness PATCHED branch (the fix.diff logic) aborts at idx=8 (== declared length), no OOB read.
Since the parser is unreachable at runtime on this guest, the runtime behavior of the booted single-fix kernel cannot differ from the unpatched one (neither triggers anything without PCMCIA HW). The validation is at the compile+source+harness level β the fix closes the code path the bug traces through.
PoC changes
- Added
harness.cβ userspace verbatim replication of the CFTABLE_ENTRY parser, demonstrating the unbounded idx growth on the supplied CIS image. - Added
harness_valid.cβ well-formed-tuple sanity test proving the fix is benign on legitimate input. - Added
build.sh/run.shβ exact build and run commands. - Added
fix.diffβ the recommended fix, matches the finding proposal. - Removed the placeholder Python CIS-image generator snippet from the
README (the binary
cis_image.binships with the evidence pack).
Kernel references
sys/bus/pccard/pccard_cis.c:984-1007β power loop, missing length guardsys/bus/pccard/pccard_cis.c:995-1003β power do-while, the bug locus 1sys/bus/pccard/pccard_cis.c:1243-1246β misc while loop, bug locus 2sys/bus/pccard/pccard_cis.c:1024, 1102, 1134, 1225β existinglength <= idxguards that the power/misc loops should mirrorsys/bus/pccard/pccard_cis.c:62βPCCARD_CIS_SIZE 4096(the bus mapping size)sys/bus/pccard/pccard_cis.c:871-1253β the CISTPL_CFTABLE_ENTRY handlersys/bus/pccard/pccardvar.h:255-259βpccard_cis_read_1/pccard_tuple_read_1(no length check)
Fix verification
fixedVALIDATED: harness BEFORE idx=50001 OOB; AFTER idx=8 capped. Compile+boot clean. harness_valid benign.
BEFORE: idx=50001. AFTER: idx=8. Valid: idx=5 both.
Confirmed kernel references
- sys/bus/pccard/pccard_cis.c:984-1007
- sys/bus/pccard/pccard_cis.c:995-1003
- sys/bus/pccard/pccard_cis.c:1243-1246
- sys/bus/pccard/pccard_cis.c:1024
- sys/bus/pccard/pccard_cis.c:1102
- sys/bus/pccard/pccard_cis.c:1134
- sys/bus/pccard/pccard_cis.c:1225
- sys/bus/pccard/pccard_cis.c:62
- sys/bus/pccard/pccard_cis.c:871
- sys/bus/pccard/pccardvar.h:255-259
Detail
Exploit chain
none -- read-only OOB. Overread bytes feed only continuation bit + 2-bit maxtwins field. No info exfil path. DoS-only.
Evidence (decisive lines)
BEFORE: idx=50001, first OOB at idx=32766. AFTER: idx=8 (capped). harness_valid: idx=5 both branches (benign).
PoC changes
Authored: harness.c (verbatim parser replication), harness_valid.c (benign input), fix.diff (idx>=length goto abort_cfe at :995 + :1243), VERDICT.md, manifest.json.
Verified recommended fix
Add if(idx>=tuple->length) goto abort_cfe at top of power do-while (:995) and misc while (:1243). Mirrors existing guards at :1024/:1102/:1134/:1225. Matches finding proposal byte-for-byte. Full diff in findings/poc/DF-1041/fix.diff.
Verdict
REPRODUCED (harness). pccard_parse_cis_tuple CFTABLE_ENTRY power do-while (:995-1003) and misc while (:1243-1246) advance idx via pccard_tuple_read_1 with no length check -> idx=50001 vs length=8 -> page-fault on HW. No PCMCIA bridge on guest.
No comments yet.