sio_pccard_detach mutates com->gone/com->tp/com->ibuf and calls ttyclose without tty_token, racing siopen/sioclose/siosetwater into kernel heap UAF and double-free of com->ibuf
Summary
The only detachable sio path bypasses the tty_token protocol every core path holds - four concrete interleavings reach double kfree of ibuf (freed without NULLing), UAF writes through com->iptr under an open sleeping in the DTR wait, duplicated linesw-close/ttyclose sequences, and an ibufold-alias double free. Unpriv holds the tty open/close; the detach needs eject/kldunload (Medium, DF-2191 tier). Hardware-gated (no PCMCIA on guest); proven by lock-protocol trace. Token+drain+NULL fix (coordinates DF-2191/DF-3902).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5191 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 6.7 KB | β raw | |
| race_harness.c | β | 2.0 KB | view raw | |
| fix.diff | β | 2.7 KB | view raw | |
| verdict.json | β | 5.1 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| env.txt | β | 264 B | view raw |
DF-5191 β sio_pccard_detach races the sio core without tty_token (UAF / double-free)
Verdict in one line
sys/dev/serial/sio/sio_pccard.c:117-143 β sio_pccard_detach() sets
com->gone, calls ttyclose(com->tp) and frees com->ibuf without taking
tty_token, the exact token every sio-core path holds while touching those
fields (sioopen sio.c:1273/1277, sioclose sio.c:1460/1475-1482,
siosetwater sio.c:2446). Card eject (or pccardc power off / kldunload)
racing an open/close in flight double-frees com->ibuf (M_DEVBUF) or
double-executes the tty close sequence. Medium / likely / CWE-362 β
CWE-415/CWE-416.
Reproduce
Cannot run on the audit guest (QEMU i440fx: no PCMCIA bridge, no pccard
in dmesg.boot; sio_pccard.c is optional sio pccard). On hardware, or on
any DragonFly system with a pccard serial device:
cc -O2 -o race_harness race_harness.c ./race_harness /dev/ttyd0 & # unprivileged # as root, repeatedly: pccardc power off 0 ; sleep 1 ; pccardc power on 0
Success criterion: kernel panic or M_DEVBUF double-free diagnostics.
Contents
VERDICT.mdβ four concrete interleavings (detachβ₯close double-free, detachβ₯open UAF+double-free, duplicated ttyclose/clist free, ibufold UAF), relationship to DF-2191 / DF-3902, why isa/pci/puc sio are unaffected (they have no detach method at all)race_harness.cβ illustrative trigger harness (not runnable on guest)fix.diffβ token + drain + NULL-out fix (coordinates DF-2191/DF-3902)verdict.json/manifest.json
DF-5191 VERDICT β sio_pccard_detach races the sio core without tty_token β UAF / double-free of com->ibuf and duplicated tty close
File: sys/dev/serial/sio/sio_pccard.c (pass 2, GLM 5.3)
Lines: 117β143 (sio_pccard_detach)
Status: NOT REPRODUCED ON GUEST β code-trace proof only
The audit guest is QEMU/i440fx: no PCMCIA bridge, pccard absent from
dmesg.boot, sio_pccard.c is compiled only for optional sio pccard
(sys/platform/pc64/conf/files:197). No sio-pccard device can exist on this
guest, so the race has no runtime surface here. The defect is established by
inspection below; reproduction requires real pccard hardware (see
race_harness.c for the exact interleaving harness).
Root cause
Every path in the sio core that touches com->gone, com->tp, com->ibuf
or the UART runs serialized under lwkt_gettoken(&tty_token):
sioopenβ token at sys/dev/serial/sio/sio.c:1277 (gone-check at :1273 deliberately before the token β see below)siocloseβ token at sio.c:1460siosetwaterβ "called with tty_token held" comment, sio.c:2446sioparam,comstop,siosettimeout, intr paths β same token
sio_pccard_detach() mutates exactly those fields with no token at all
(sio_pccard.c:117-143 β lwkt_gettoken does not appear in the file):
com->gone = 1; /* :127 */
...
if (com->tp && (com->tp->t_state & TS_ISOPEN)) { /* :134 */
device_printf(dev, "still open, forcing close\n");
ttyclose(com->tp); /* :136 */
} else {
if (com->ibuf != NULL) /* :138 */
kfree(com->ibuf, M_DEVBUF); /* :139 β never NULLed */
}
Concrete interleavings (all fields unsynchronized)
Pair 1 β detach β₯ sioclose β double-free of com->ibuf.
Thread A is inside sioclose (token held) between comhardclose and the
com->gone re-check at sio.c:1475. Thread B (detach) sets gone=1, reads
TS_ISOPEN as clear (A has not reached ttyclose yet / t_state is being
torn down concurrently), frees com->ibuf at :139 and does not NULL it.
Thread A then executes if (com->gone) { if (com->ibuf != NULL)
kfree(com->ibuf β¦) } (sio.c:1475-1479) β second kfree of the same
M_DEVBUF chunk. Heap corruption of the kernel malloc zone (ibuf is a
1024β8192-byte M_DEVBUF block, siosetwater). This is the detach-side twin of
pass-1 DF-3902 (sioclose-side non-NULLed pointer); the missing token is what
makes the detach-side free racy in the first place.
Pair 2 β detach β₯ sioopen β UAF use of com->ibuf + later double-free.
sioopen checks com->gone at sio.c:1273 before taking the token, so an
open in flight when the card is ejected continues into ttymalloc,
comhardopen (which sleeps seconds waiting for DTR β tsleep(β¦
"siodtr"β¦) at sio.c:1284-1295, and the core even rechecks com->gone
afterwards because ejection-during-open was anticipated). During that sleep
detach runs: TS_ISOPEN is not set yet (linesw l_open runs only after
comhardopen returns), so detach takes the else branch and frees
com->ibuf. The open then resumes with com->ibuf dangling β
com->iptr = com->ibuf (sio.c:1698) and the input path bcopy(com->ibufβ¦)
(sio.c:1703-1708) operate on freed memory (UAF write). When the user closes,
sioclose's gone-branch frees the same pointer again β double-free.
Pair 3 β detach β₯ sioclose β duplicated ttyclose.
Detach's forced ttyclose(com->tp) (:136) runs while a concurrent
sioclose executes (*linesw[tp->t_line].l_close)() + comhardclose +
ttyclose (sio.c:1468-1472) under the token. ttyclose itself takes only
tp->t_token (sys/kern/tty.c:247), which does not serialize against the
linesw close sequence: queues are flushed and clist_free_cblocks() runs
three times (tty.c:256-258) in both threads β the second ttyclose on the
already-torn-down tty re-frees clist blocks and scrambles t_state while
the first close is still inside l_close.
Pair 4 β detach β₯ siosetwater (TIOCSETAW speed change) β ibufold UAF.
siosetwater (token held, sio.c:2446+) swaps com->ibufold = com->ibuf,
installs a new com->ibuf, and frees ibufold at its tail (sio.c:2438-2441).
A tokenless detach interleaving frees com->ibuf (old or new depending on
the swap point) while siosetwater still holds/frees the other alias of the
same chunk β at minimum one UAF access (com->iptr still points into the
old buffer, sio.c:1698) and one double-free.
Trigger / threat model
device_detach for a pccard sio child fires on card ejection (physical),
pccardc power off (root), or controller kldunload (root) β the file's own
comment says so (sio_pccard.c:108-115). The racing counterpart only needs
the tty device node open/closed β an unprivileged user on a multi-user
machine. Timing window is wide (DTR wait alone sleeps seconds). Impact class
is kernel heap UAF/double-free (M_DEVBUF) β corruption, in principle
exploitable; practically gated behind a card-eject event. Medium severity,
consistent with the sibling DF-2191 (same function, different lifetime bug:
callouts not drained).
Relationship to known findings
- DF-2191 (pass 1): callouts
dtr_ch/busy_chnot stopped/drained before softc free β different objects, same function. My fix.diff includes the callout drain so the function is fixed as a whole; the finding credit for that part stays with DF-2191. - DF-3902 (pass 1, sio.c): sioclose gone-path double-free due to
non-NULLed
com->ibufβ the deterministic close-side consequence. DF-5191 supplies the missing synchronization that enables the detach-side racy frees (and covers pairs 2β4, which DF-3902 does not). - sio isa/pci/puc attachments have no detach method at all (sio.c:193-238) β pccard is the only detachable sio, so this race family exists only through this file.
Fix
Take tty_token around the whole detach body, NULL the freed pointers, and
drain the callouts (DF-2191). See fix.diff (authored against the read-only
sys/ tree; never applied there). Not validated on the guest: no pccard
hardware, and a kernel build cannot exercise a bus that does not exist.
Guest
DragonFly dfbsd 6.5-DEVELOPMENT #0 β¦ X86_64_GENERIC; grep -c pccard
/var/run/dmesg.boot = 0; no PCMCIA bridge in pciconf -l. Guest left clean
(reset with-src).
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.
Fix verification
not_testableFix validation impossible on the audit guest: no pccard hardware means no baseline detach event exists to race against, patched or unpatched. fix.diff is a lock-protocol-reviewed proposal (release-drain-retake ordering chosen because siodtrwakeup/siobusycheck acquire tty_token, so draining under the token would deadlock).
n/a - no runnable baseline on guest
Confirmed kernel references
- sys/dev/serial/sio/sio_pccard.c:117
- sys/dev/serial/sio/sio_pccard.c:127
- sys/dev/serial/sio/sio_pccard.c:134
- sys/dev/serial/sio/sio_pccard.c:136
- sys/dev/serial/sio/sio_pccard.c:138
- sys/dev/serial/sio/sio.c:1273
- sys/dev/serial/sio/sio.c:1277
- sys/dev/serial/sio/sio.c:1460
- sys/dev/serial/sio/sio.c:1475
- sys/dev/serial/sio/sio.c:2438
- sys/dev/serial/sio/sio.c:2446
- sys/kern/tty.c:247
Detail
Exploit chain
root/physical triggers pccard detach; unprivileged user holds open/close in flight on /dev/ttydX; detach's tokenless kfree(com->ibuf) lands while sioclose/sioopen/siosetwater still reference it -> M_DEVBUF (1024-8192 byte) double free / UAF write through com->iptr -> kernel heap corruption. Chain NOT developed further: no runtime surface on the audit guest (no pccard hardware), so exploitation could not be attempted.
Evidence (decisive lines)
VERDICT.md (interleaving-by-interleaving trace with path:line); race_harness.c (trigger harness for hardware owners); fix.diff (token + callout_drain + NULL-out fix coordinating DF-2191/DF-3902); env.txt (guest shows no pccard)
PoC changes
No seed PoC existed. race_harness.c written fresh; explicitly documented as non-runnable on the audit guest.
Verified recommended fix
Serialize sio_pccard_detach against the sio core: publish com->gone under tty_token, drop the token, callout_drain dtr_ch/busy_ch (callbacks take the token themselves), re-take the token for the teardown, and NULL com->ibuf/com->ibufold after kfree - see fix.diff
Verdict
Code-trace-proven race, not runnable on the audit guest: the guest is QEMU/i440fx with no PCMCIA/pccard hardware (0 'pccard' lines in dmesg.boot; sio_pccard.c compiles only for 'optional sio pccard', sys/platform/pc64/conf/files:197), so no sio-pccard device and no detach event can exist there. The defect is established by inspection: sio_pccard_detach (sio_pccard.c:117-143) sets com->gone, calls ttyclose(com->tp) and kfrees com->ibuf without acquiring tty_token, while every sio-core path touching those fields holds it (sioopen gone-check sio.c:1273 sits BEFORE its lwkt_gettoken at sio.c:1277; sioclose takes the token at sio.c:1460 and re-frees com->ibuf at sio.c:1475-1482 when com->gone is set; siosetwater documents 'called with tty_token held' at sio.c:2446 and frees the ibuf alias com->ibufold at sio.c:2438-2441; the callout callbacks siodtrwakeup/siobusycheck also run under the token). Four interleavings reach memory corruption: (1) detach frees com->ibuf (sio_pccard.c:138-139, never NULLed) while a token-holding sioclose passes its gone-check and kfrees the same M_DEVBUF chunk - double free; (2) an open that passed the pre-token gone-check sleeps in comhardopen (DTR wait, sio.c:1284-1295) while detach takes the !TS_ISOPEN branch and frees com->ibuf underneath it - com->iptr = com->ibuf (sio.c:1698) and bcopy(com->ibuf,...) (sio.c:1703-1708) become UAF, and the later close double-frees; (3) detach's forced ttyclose (sio_pccard.c:136) duplicates the linesw-close+ttyclose sequence a concurrent sioclose runs (sio.c:1468-1472), re-freeing clist blocks; (4) detach interleaved with siosetwater's ibuf/ibufold swap frees an alias siosetwater still frees at its tail. Trigger requires a card-eject/power-off/modunload event (physical or root) racing an unprivileged open/close - wide window (seconds of DTR wait). Same function as DF-2191 (callouts) and the detach-side twin of DF-3902 (sioclose-side non-NULLed ibuf); isa/pci/puc sio attachments have no detach method at all (sio.c:193-238), so only pccard exposes this race family.
No comments yet.