# 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)

```c
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)

```c
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):

```c
#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). 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

- `sys/bus/pccard/pccard_cis.c:984-1007` — power loop, missing length guard
- `sys/bus/pccard/pccard_cis.c:995-1003` — power do-while, the bug locus 1
- `sys/bus/pccard/pccard_cis.c:1243-1246` — misc while loop, bug locus 2
- `sys/bus/pccard/pccard_cis.c:1024, 1102, 1134, 1225` — existing `length <= idx`
  guards that the power/misc loops should mirror
- `sys/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 handler
- `sys/bus/pccard/pccardvar.h:255-259` — `pccard_cis_read_1` /
  `pccard_tuple_read_1` (no length check)
