# DF-1090 — pnp_parse_desc fixed-offset accesses without length validation

## Verdict

**NOT REPRODUCED at runtime (hardware-gated) — STATIC VERIFICATION + HARNESS CONFIRMED.**

The bug exists verbatim in `sys/bus/isa/pnpparse.c:90-328`. Every
descriptor-type branch except `PNP_TAG_ID_ANSI` accesses fixed offsets of
the resource buffer (`res[N]`, `I16(res+N)`, `I32(res+N)`) without first
checking that the attacker-declared TLV length `len` is large enough —
even though the parser is called with `l` (the length field from the TLV
header, supplied by the PnP card) as the `len` argument
(`pnpparse.c:518,538`).

The audit QEMU guest has **no ISA-PnP cards** (`dmesg | grep -c pnp` = 0),
so `pnp_identify` finds nothing and `pnp_parse_desc` is never invoked at
runtime. The trigger requires attacker-controlled PnP hardware (or a
hostile QEMU PnP device model) — `AV:P/AC:L`. This is the same hardware
constraint as the sibling finding DF-1071.

The `df1090_harness` userspace C program mirrors the parser algorithm
byte-for-byte for all 8 affected descriptor types. Run with a declared
length shorter than the offsets each type accesses, the **unpatched**
algorithm reports `OOB DETECTED` for 8/8 cases; the **patched**
algorithm (per-descriptor length guards) reports `no OOB` for 8/8.

## Mechanism (confirmed by source trace)

`pnp_parse_resources` (`pnpparse.c:408-545`) iterates the TLV resource
blob and for each tag extracts the declared length `l`:

```c
/* small resource: */
l = PNP_SRES_LEN(tag);              /* :438 - 4 bits from the tag byte */
...
pnp_parse_desc(dev, tag, p, l, config, ldn);     /* :518 */

/* large resource: */
l = I16(p);                          /* :529 - 9 bits from card */
p += 2; len -= 2;
...
pnp_parse_desc(dev, tag, p, l, config, ldn);     /* :538 */
```

In both cases `l` is **the length declared by the card**, not validated
against the descriptor type's required minimum. `pnp_parse_desc` then
dispatches on the tag and accesses offsets of `res`:

```c
case PNP_TAG_IO_RANGE:
    ...
    if (res[6] == 0) { ... }                       /* :141 — needs len >= 7 */
    ...
    I16(res + 1)                                    /* :154 */
    I16(res + 3) + res[6] - 1                       /* :155 */
    ...
    if (res[5] == 0) { res[5] = 1; }                /* :163-165 — OOB WRITE */
```

A malicious card sets the `PNP_TAG_IO_RANGE` length field to 3, sends 3
payload bytes, and the parser reads `res[6]` (3 bytes past end),
`I16(res+1)` (1 byte past end), `I16(res+3)` (3 bytes past end), and
**writes** `res[5] = 1` (2 bytes past end). On the `kmalloc(1024)` buffer
the resource data lives in, an attacker within 17 bytes of the 1024-byte
boundary writes outside the allocation entirely.

The `PNP_TAG_ID_ANSI` case at `:223-235` is the control — it correctly
clamps `len` before its `bcopy`. Every other type lacks that clamp.

## Reproduction

```
$ sh verify.sh        # 11/11 static checks pin the missing guards
$ cc -O0 -o df1090_harness df1090_harness.c
$ cc -O0 -DFIX -o df1090_harness_fix df1090_harness.c
$ ./df1090_harness        # 8/8 "OOB DETECTED"
$ ./df1090_harness_fix    # 8/8 "no OOB"
```

## Fix

`fix.diff` adds a single `if (len < N) break;` guard at the head of each
of the 8 affected descriptor-type branches, using the minimum length that
type's accesses require (4, 2, 1, 7, 3, 9, 17, 9 respectively). The
guards mirror the existing `PNP_TAG_ID_ANSI` clamp in spirit: if the
card-declared length is too short for the descriptor type, skip it rather
than read past the buffer. The kernel `nativekernel` build of the patched
file succeeds (`fix_build.log`, `NK_DONE rc=0`); the harness proves the
fix is correct at the algorithm level.
