# DF-2953 — /dev/klog `logtimeout()` use-after-free read of `sc_sigio`

## What

`sys/kern/subr_log.c:247-248` — the periodic `logtimeout()` softclock callout
loads `logsoftc.sc_sigio` **without `sigio_token`** and passes the raw
`struct sigio *` to `pgsigio()`:

```c
if ((logsoftc.sc_state & LOG_ASYNC) && logsoftc.sc_sigio != NULL)
        pgsigio(logsoftc.sc_sigio, SIGIO, 0);
```

A concurrent `FIOSETOWN` / `TIOCSPGRP` ioctl on the (open) klog fd calls
`fsetown()` (`sys/kern/subr_log.c:292-301` → `sys/kern/kern_descrip.c:1296`),
which — under `sigio_token` — replaces and **frees** the old sigio
(`funsetown()`, `sys/kern/kern_descrip.c:1239-1272`: `sio_pgrp=NULL`,
`sio_ucred=NULL`, `kfree(sigio, M_SIGIO)`).

`pgsigio()` (`sys/kern/kern_sig.c:2639-2662`) then dereferences the freed
chunk: `sigio->sio_pgid`, `sigio->sio_pgrp` → `pgref(NULL)`, and
`CANSIGIO(sigio->sio_ruid, sigio->sio_ucred, p)` →
`(sigio->sio_ucred)->cr_uid` on a NULL/stale ucred
(`sys/kern/kern_sig.c:99-104`).

All other accessors of a `sc_sigio`-style pointer (`fsetown`, `funsetown`,
`fgetown`) hold `sigio_token`; `logtimeout` is the only unlocked reader in
the tree's klog path. (FreeBSD fixed this class by passing
`struct sigio **` to `pgsigio` and locking internally.)

The `logclose()` → `funsetown()` path is NOT a racer: `callout_terminate()`
synchronously waits for an in-progress callback
(`sys/kern/kern_timeout.c:1104-1126`, wait loop at `:926-941`).

## Impact

Kernel NULL-pointer deref panic in the softclock thread (verified: fatal
trap 12, `fault virtual address = 0x40`, `Stopped at pgsigio+0xcd`). If the
freed M_SIGIO chunk is recycled before the deref, the kernel reads
attacker-adjacent slab data and follows garbage `sio_pgrp`/`sio_ucred`
pointers. Trigger requires an open fd on `/dev/klog` (mode 0600
root:wheel, `sys/kern/subr_log.c:317`): host root, or jailed root where a
devfs ruleset exposes klog (the DF-0190 jail-check gap) — the latter turns
this into a host-kernel panic from inside a jail.

## Reproduce (guest is single-tenant, stock INVARIANTS kernel)

```sh
# build (in guest, as root)
cc -O2 -Wall -o /root/klog_sigio_race /root/klog_sigio_race.c
# run (as root)
/etc/rc.d/syslogd stop            # syslogd holds /dev/klog
sysctl -w kern.log_wakeups_per_second=100
/root/klog_sigio_race
```

Expected (vulnerable kernel): panic within seconds —
`Fatal trap 12: page fault while in kernel mode`,
`fault virtual address = 0x40`, `Stopped at pgsigio+0xcd: movl 0x40(%rdx),%edx`,
`current process = Idle` (softclock thread).

Mechanism of the PoC: opens klog, sets FIOASYNC (LOG_ASYNC), becomes a
process-group leader with 16 members (widens `pgsigio()`'s member loop →
microsecond-scale window), one child hammers `kill(-pg, 0)` for pg_lock
contention, one child writes to `/dev/console` in a loop
(`log_console()` sets `msgbuftrigger=1`, `sys/kern/subr_prf.c:296`), and
three processes hammer `ioctl(FIOSETOWN, -pgid)` / `ioctl(FIOSETOWN, 0)`
(install pgrp-owner sigio / free it).

Fixed kernel: identical run completes cleanly (no panic).
