# DF-1090 — pnp_parse_desc accesses fixed descriptor offsets without length validation

## Build

```
cc -O0 -o df1090_harness df1090_harness.c
cc -O0 -DFIX -o df1090_harness_fix df1090_harness.c
```

## Run

```
./df1090_harness        # unpatched algorithm — 8/8 OOB
./df1090_harness_fix    # patched algorithm — 0/8 OOB
sh verify.sh            # 11 static source-tree checks
```

## Expected (bug present)

- `verify.sh` reports `PASS=11 FAIL=0` on the audit tree (the 8 missing
  guards are pinned, the ANSI control case is pinned, the function
  signature and call site are pinned).
- `df1090_harness` reports `OOB DETECTED` for **all 8** descriptor types
  when fed a declared TLV length shorter than the offsets the parser
  accesses unconditionally.
- `df1090_harness_fix` reports `no OOB` for all 8 — each type now bails
  out cleanly on a short declared length.

## Bug shape

`pnp_parse_desc` at `sys/bus/isa/pnpparse.c:60-340` receives the resource
pointer `res` and the **attacker-declared** TLV length `len` (the length
field inside the TLV header, supplied by the PnP card itself). For every
descriptor type except `PNP_TAG_ID_ANSI` (which correctly clamps `len` to
`sizeof(buf)-1` at line 224 before `bcopy`), the parser accesses fixed
offsets of `res` without first checking that `len` is large enough:

| descriptor                       | access                           | needs | cite     |
|----------------------------------|----------------------------------|-------|----------|
| `PNP_TAG_COMPAT_DEVICE`          | `bcopy(res,&compat_id,4)`        | ≥4    | :95      |
| `PNP_TAG_IRQ_FORMAT`             | `I16(res)`                       | ≥2    | :105     |
| `PNP_TAG_DMA_FORMAT`             | `res[0]`                         | ≥1    | :123     |
| `PNP_TAG_IO_RANGE`               | `res[6]`,`I16(res+1)`,`I16(res+3)`,`res[5]` (write!) | ≥7 | :141-167 |
| `PNP_TAG_IO_FIXED`               | `res[2]`,`I16(res)`              | ≥3    | :178     |
| `PNP_TAG_MEMORY_RANGE`           | `I16(res+7)`,`I16(res+5)`        | ≥9    | :242     |
| `PNP_TAG_MEMORY32_RANGE`         | `I32(res+13)`,`I32(res+9)`,`I32(res+5)` | ≥17 | :278 |
| `PNP_TAG_MEMORY32_FIXED`         | `I32(res+5)`,`I32(res+1)`        | ≥9    | :308     |

The `IO_RANGE` case is the most interesting because line 165 performs an
**OOB write** (`res[5] = 1` as part of the align-normalization) when the
declared length is `< 6`, which can escape the 1024-byte `kmalloc` bucket
the resource buffer was allocated from by `pnp_read_bytes`.

## Impact / preconditions

A malicious ISA-PnP card (or a hostile QEMU PnP device model) supplies
crafted TLV data with a truncated length field. The PnP bus is probed at
boot (`pnp_identify` → `pnp_isolation_protocol` → `pnp_create_devices` →
`pnp_parse_resources` → `pnp_parse_desc`) and on `kldload pnp`. The audit
guest has zero PnP cards, so the path is not exercised at runtime here;
the bug is confirmed by source trace + userspace harness mirroring the
algorithm.
