# DF-3902 — sioclose() gone-path double-free: com->ibuf kfree'd twice (no NULL-after-free) + live tty bzero'd

**File:** `sys/dev/serial/sio/sio.c`
**Class:** double free (CWE-415) + object-lifetime hygiene on pccard hot-removal
**Severity:** Low (requires PCMCIA/pccard sio device + hot ejection + ≥2 open fds;
deterministic when triggered — no race needed)
**Related-but-distinct:** DF-2191 (sio_pccard.c: dtr_ch/busy_ch callouts not
stopped before softc free — UAF via callout). This finding is the *other* half:
the double-free through repeated sioclose() in sio.c itself.

## What

`sioclose()` (sio.c:1449) tail:

```c
if (com->gone) {
	kprintf("sio%d: gone\n", com->unit);
	crit_enter();
	if (com->ibuf != NULL)
		kfree(com->ibuf, M_DEVBUF);      /* :1479 — NOT NULLed after free */
	bzero(tp, sizeof *tp);              /* :1480 — bzero of live struct tty */
	crit_exit();
}
```

The freeing branch is guarded only by `com->ibuf != NULL`, but the pointer is
never cleared, and `com->gone` stays set, so **every subsequent sioclose() on
another still-open fd of the same unit re-frees the same M_DEVBUF block.**

Trigger sequence (deterministic):

1. Process A and B each hold an open fd on `/dev/ttydN` (or cuaaN) of a
   pccard-attached sio unit.
2. Card is ejected → `sio_pccard_detach()` (sio_pccard.c:118) sets
   `com->gone = 1`, tears down intr + ioport, and — because TS_ISOPEN —
   forces `ttyclose(com->tp)` without freeing `ibuf` (the "still open,
   forcing close" branch deliberately leaves ibuf to sioclose).
3. A closes its fd → sioclose → gone-branch → `kfree(com->ibuf)` (1st free),
   `bzero(tp)`. `com->ibuf` still points at freed memory.
4. B closes its fd → sioclose → gone-branch → `com->ibuf != NULL` (dangling)
   → `kfree(com->ibuf)` **again** → double free of an M_DEVBUF block.

Aggravators in the same branch:
* `bzero(tp, sizeof *tp)` destroys a `struct tty` that `com->tp` still
  references (clist state, termios) — any straggling tty-layer reference
  (blocked l_read woken later, dev->si_tty) then operates on a zeroed tty.
* `comhardclose()` on the 2nd close still does `outb()` MMIO writes via the
  bus handle released by detach (sio_pccard.c:135).

## Impact

Kernel heap corruption (double free of M_DEVBUF) on hardware with PCMCIA
serial devices under card ejection. Requires physical hot-unplug (or
driver-controller modunload path), so privilege is "physical access /
hotplug event": Low severity, certain confidence — the sequence is
deterministic, no race window involved.

## Live verification performed (on this guest — console IS sio0)

The ioctl-glue and data-plane around the buggy path were exercised live
(`sio_ioctl_test.c`, guest console = sio0): control-device termios ioctls,
lock-merge enforcement, timestamp zero-init, TIOCMGET, 100KB TX burst —
all behave exactly as source predicts; see run.log / run_user_boundary.log.
The double-free itself needs a pccard device, which the guest lacks →
status inconclusive (source-certain), like DF-2191's guest half.

## Fix

NULL `com->ibuf` after the kfree (and don't bzero a tty that com->tp still
references) — see fix.diff.
