# DF-5443 — pnp_read_bytes() error path abandons the grown buffer → double free + leak (sys/bus/isa/pnp.c)

## What

`pnp_read_bytes()` (sys/bus/isa/pnp.c:493-526) grows the resource buffer with
kmalloc/bcopy/kfree, but on a short read (`pnp_get_resource_info(...) !=
amount`, pnp.c:517-518) returns EINVAL **without updating `*resourcesp`**.
The caller `pnp_read_resources()` then propagates the caller-local pointer —
which still references the **already-freed pre-growth buffer** — through its
own `out:` block (pnp.c:576-580), and `pnp_isolation_protocol()` frees that
stale pointer at cleanup (pnp.c:673-674).  Result: **kernel heap double
free** of the old buffer plus a **leak** of the newly-grown buffer.

## Trigger model

Any ISA-PnP card (or emulated/counterfeit PnP device in a VM) that delivers
more than 1024 bytes of resource data (forcing the growth path) and then
stops asserting the resource-data-ready bit mid-descriptor.  This is not
limited to malicious hardware: the "PnP device failed to report resource
data" path (pnp.c:218-221) exists precisely for flaky cards.

## Rig

`rig/` contains a KLD that compiles the **real, unmodified**
`sys/bus/isa/pnp.c` (included verbatim; only inb/outb/kmalloc/kfree and three
global symbol names are macro-substituted, and its DRIVER_MODULE neutered)
and drives it with a virtual PnP card implementing the serial-isolation
state machine (checksum-valid 72-bit ID, PNP_STATUS/PNP_RESOURCE_DATA byte
service).  Module `hack_dfa` (script 1):

  * card passes serial isolation (checksum-valid),
  * serves a large vendor tag header (0x84, len 2048) — 3 bytes,
  * then stalls (status never ready).

`pnp_read_bytes(2048)` grows 1024→3072 (freeing the 1024 block), the read
fails short, the stale pointer is freed again at isolation cleanup.

## Build

    # on the guest (root), sources pushed to /usr/src/sys/bus/isa/
    (cd /usr/src/sys/bus/isa/hack_dfa && make obj && make depend && make)

## Run

    kldload /usr/obj/usr/src/sys/bus/isa/hack_dfa/rigdfa.ko

## Expected (unfixed baseline kernel)

    PNPRIG: kmalloc(1024, temp) = 0xfffff8008db3b400     <- buffer A
    PNPRIG: kmalloc(3072, temp) = 0xfffff8008dc05500     <- grown B
    PNPRIG: kfree(0xfffff8008db3b400, temp)              <- growth free of A (pnp.c:512)
    PnP device failed to report resource data            <- pnp.c:219 short read
    PNPRIG: kfree(0xfffff8008db3b400, temp)              <- cleanup free of STALE A (pnp.c:674)
    panic: memory chunk 0xfffff8008db3b400 is already free!
    _kfree() <- rig_identify <- isa_driver_added <- devclass_add_driver

  * baseline: kernel **panic** (allocator double-free check), B leaked.
  * fixed kernel: the error path publishes the live buffer; the cleanup
    kfree targets the grown buffer exactly once, kldload returns 0.

## Fix

`fix.diff` (also fixes DF-5444/DF-5445 in the same files — see those packs).
Validated on a rebuilt kernel: see VERDICT.md.
