# DF-5443 VERDICT — REPRODUCED (kernel panic; primitive = heap double-free)

## Bottom line

The bug is real, attacker-reachable through card data, and was reproduced
**end-to-end on the guest** with the real, unmodified `sys/bus/isa/pnp.c`
engine: a card that delivers >1 KiB of resource data and then stalls
mid-descriptor makes `pnp_isolation_protocol()` free an already-freed buffer
→ **`panic: memory chunk 0xfffff8008db3b400 is already free!`**
A single-fix kernel (fix.diff) makes the identical fault injection complete
cleanly (RC=0, exactly one free of the live grown buffer).

## Root cause (path:line)

  * `pnp_read_bytes()` grows the buffer: kmalloc → bcopy → **kfree(old)**
    at sys/bus/isa/pnp.c:506-515.
  * The short-read error path returns EINVAL **without updating
    `*resourcesp`** (pnp.c:517-518) — the out-params are only written on
    success (pnp.c:521-523).
  * `pnp_read_resources()` propagates its (now stale) local pointer at
    `out:` (pnp.c:576-580).
  * `pnp_isolation_protocol()` breaks on the error and runs
    `if (resources) kfree(resources, M_TEMP);` (pnp.c:673-674) → **second
    free of the pre-growth buffer**; the grown replacement is leaked.

Trigger precondition is exactly the "PnP device failed to report resource
data" condition the code already anticipates for flaky cards (pnp.c:218-221)
— no exotic state required, only a card (malicious OR merely misbehaving)
that stalls after >1024 delivered bytes.

## Reproduction (baseline kernel #0, Thu Jul 2 06:02:54)

Rig: KLD compiling pnp.c **verbatim** (only inb/outb/kmalloc/kfree and three
global names macro-substituted; DRIVER_MODULE neutered) + a virtual PnP card
implementing the serial-isolation protocol (checksum-valid 72-bit ID served
LSB-first per pnp.c:192, PNP_STATUS/PNP_RESOURCE_DATA service).  Script 1:
large vendor tag header (0x84, len 2048) served, body stalled.

Observed (serial console, run.log / serial_runA_full.log / panic.txt):

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

The stack is the genuine in-vivo kldload → `isa_driver_added`
(isa_common.c:816-831) → identify → engine path.  DragonFly's slab
allocator (`chunk_mark_free`) converts the double free into a deterministic
panic; on the same evidence the free-list is poisoned twice — the classic
two-owners primitive (the witness probes in the rig demonstrate re-handout
mechanics when the allocator does not abort).

## Why not uid0

There is no unprivileged-local-user input path: pnp.c runs only during ISA
bus autoconfiguration (boot) or `isa_driver_added` on driver kldload.  The
attacker model is crafted/counterfeit PnP hardware or a VM-emulated device —
same class as DF-5392/5393/5394 (pccard CIS family analogy).  For that
attacker the primitive is ring-0 boot-time heap corruption with a
fully-controlled stall point (attacker chooses which 1024-byte-aligned
growth boundary and when), i.e., arbitrary-code-execution-grade; severity
kept at Medium consistent with the hardware-trigger model of the sibling
findings.

## Fix validation (kernel #1, Mon Oct 5 16:47:13, fix.diff applied)

Identical rig, identical card script, patched engine compiled in:

    PNPRIG: kmalloc(1024, temp) = 0xfffff8008db3b800
    PNPRIG: kmalloc(3072, temp) = 0xfffff8008dc05500
    PNPRIG: kfree(0xfffff8008db3b800, temp)     <- growth free (unchanged)
    PnP device failed to report resource data
    PNPRIG: kfree(0xfffff8008dc05500, temp)     <- cleanup frees the LIVE buffer
    RUN_A_RC=0 ; witness: no double-freed address recorded ; guest healthy

The leak is fixed simultaneously (the grown buffer is now reachable by the
caller and freed exactly once).  Baseline behavior GONE.

## Fix

`fix.diff` hunk in pnp.c:514-533 publishes `*resourcesp`/`*spacep` before
the read attempt (and drops the redundant success-path copies).  The same
diff also carries the DF-5444/DF-5445 fixes (see those packs); full kernel
build log in fix_build.log (4299 compile steps, clean).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
