# DF-3902 VERDICT

**Status: inconclusive for the double-free itself (source-certain, pccard-gated); live verification performed for the surrounding ioctl/data-plane on the guest console (sio0)**
**reproduced: 0 · impact: none (demonstrated class: heap corruption / panic on affected hardware) · confidence: certain**

## Why the double-free is certain from source

1. `sioclose()` tail (sio.c:1475-1482): when `com->gone`, it runs
   `if (com->ibuf != NULL) kfree(com->ibuf, M_DEVBUF);` and then
   `bzero(tp, sizeof *tp);` — **without setting `com->ibuf = NULL` and with
   `com->tp` still pointing at the now-zeroed tty**.
2. `com->gone` is sticky (only set by sio_pccard_detach, sio_pccard.c:127).
3. `sio_pccard_detach` with TS_ISOPEN takes the "still open, forcing close"
   branch (sio_pccard.c:135-137): it calls `ttyclose()` (clearing TS_ISOPEN)
   and deliberately leaves `ibuf` for sioclose to free.
4. With ≥2 fds open at ejection time, each fd close enters sioclose, passes
   the control-device early-out (non-control fds), reaches the gone branch,
   and the **second** close passes `com->ibuf != NULL` on the **dangling**
   pointer → `kfree()` of an already-freed M_DEVBUF block (sio.c:1478-1479).
   Deterministic — no race, no window: the guard is the stale pointer itself.
5. Additionally the 2nd close runs `comhardclose()` which issues `outb()` to
   `com->modem_ctl_port` etc. through the bus handle released by detach
   (sio_pccard.c:131-135), and `bzero(tp)` corrupts the still-referenced
   `struct tty` (`com->tp`, `dev->si_tty`).

Distinct from DF-2191 (that is the dtr_ch/busy_ch callout UAF in
sio_pccard.c's own teardown; this is the repeated-sioclose double-free whose
fix belongs in sio.c). Both are facets of the same missing-drain detach, but
the defects and fixes are independent.

## Live verification on the guest (console = sio0, 16550A)

* `df3902_trigger.c` run against the live console `/dev/ttyd0` as root:
  two opens + two closes, RC=0, no panic, guest healthy (run.log) — the
  non-gone close path is exercised live and behaves correctly (gone==0 so the
  free branch is never taken — exactly as source predicts).
* `sio_ioctl_test.c` live on the console (run_ioctl.log):
  - TIOCTIMESTAMP/TIOCDCDTIMESTAMP return all-zero timevals (attach-time
    bzero holds; no stale-data leak from these ioctls),
  - TIOCMGET returns 0x167 (commctl DMGET correct),
  - console speed lock enforced: TIOCSETA(57600) accepted (rc=0) but
    tp->t_ispeed stays 115200 — the merge block sio.c:2077-2099 silently
    restores locked fields, live-confirmed,
  - control devices (ttyid0): TIOCGETA/TIOCSETA/TIOCGWINSZ all as source
    predicts (TIOCGWINSZ zeroed; root TIOCSETA mutates it_in, restored).
* Unprivileged boundary live (run_user_boundary.log): uid=1001 cannot open
  any of /dev/ttyd0 /dev/ttyid0 /dev/ttyld0 /dev/cuaa0 (EACCES; 0600/0660
  nodes), so the in-ioctl caps_priv_check calls are correct second layer.
* 100KB TX burst through the console tty: clean, guest stable.

## Why not fully reproduced

The guest has no PCMCIA/pccard bridge or serial card, so `com->gone` can
never be set on it; the double free cannot be triggered on this VM. Recorded
`inconclusive` (source-certain), consistent with DF-2191's guest treatment.

## Exploit chain

Physical-access class: insert pccard serial card, hold 2 fds, eject →
double free of an M_DEVBUF block whose size is attacker-influenced via the
last set baud rate (siosetwater: 2*ibufsize, 256..2048 bytes) → classic
malloc-arena corruption → potential code execution under the right heap
state, at minimum a panic. Out of scope to weaponize for a hotplug-only,
dead-hardware-class bug on this guest.

## Fix validation

Not applicable (no reproduced corruption on guest). fix.diff NULLs
com->ibuf after the kfree and skips the redundant bzero of a live tty.

## kernel_refs

- sys/dev/serial/sio/sio.c:1475-1482 (gone branch: kfree without NULL + bzero tp)
- sys/dev/serial/sio/sio.c:1449-1485 (sioclose)
- sys/dev/serial/sio/sio_pccard.c:127 (gone=1), :135-137 (force-close leaves ibuf to sioclose)
- sys/dev/serial/sio/sio.c:1488-1545 (comhardclose does MMIO on released handle on 2nd close)

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