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

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 pccard configured; physical access to the PCMCIA slot.
  • Reachability: pccard_parse_cis_tuple is the callback registered at line 95 and invoked for every tuple in pccard_scan_cis's default switch case (lines 347/349). A CISTPL_CFTABLE_ENTRY (0x1B) tuple whose feature byte sets PCCARD_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, drives idx into 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.

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1041 Β· 16 files
FileTypeDescriptionSize
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
README.md readme build/run instructions and CIS image layout
↓ download 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

VERDICT.md verdict full narrative: source-level trace, mechanism, harness demonstration, fix validation
↓ download raw

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.

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). Booted 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026, kernel sha256 5dc83dac19ad09effd6241c33e0c0669d41b6497ee92d87d3a2e45f287bc22ad.
  • 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 -Werror and no errors.
  • Install + reboot: copied kernel.stripped + kernel.debug, rebooted. New kernel: 6.5-DEVELOPMENT #1: Tue Jul 14 14:55:19 UTC 2026, sha256 e37bbca3558de6abb66906b850f2d2c0791e7e7bea732629fed1f5eb71d8ac27.
  • Confirm patched source in running kernel: sed of /usr/src/sys/bus/pccard/pccard_cis.c shows 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.bin ships with the evidence pack).

Kernel references

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: 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.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Tue Jul 14 14:55:19 UTC 2026

Confirmed kernel references

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.