# DF-0006 — kern.ttys sysctl leaks kernel function/heap pointers to unprivileged users

## Verdict
**REPRODUCED** — `kern.ttys` is world-readable and copies each `struct tty`
verbatim to userland, leaking 102 kernel-range pointer-sized values per read,
including exact `.text` function-pointer addresses that defeat KASLR. Confirmed
on DragonFly master DEV `v6.5.0.1712.g89e6a-DEVELOPMENT`.

## Mechanism (confirmed by source + run)

`sysctl_kern_ttys` (`sys/kern/tty.c:2891-2921`) iterates the global `tty_list`
and, for each `struct tty`, does:

```c
t = *tp;                                      /* tty.c:2911  whole-struct copy */
if (t.t_dev)
    t.t_dev = (cdev_t)(uintptr_t)devid_from_dev(t.t_dev);   /* ONLY t_dev sanitized */
error = SYSCTL_OUT(req, (caddr_t)&t, sizeof(t));            /* tty.c:2914 */
```

The OID is registered `CTLTYPE_OPAQUE|CTLFLAG_RD` (`tty.c:2923-2924`) — a plain
read. `sysctl_root` (`sys/kern/kern_sysctl.c:1446-1450`) applies its privilege
check (`caps_priv_check(.., SYSCAP_NOSYSCTL_WR)`) **only when `req->newptr` is
set** (a write). A read sets no `newptr`, so **no privilege check runs**; any
unprivileged user reads `kern.ttys`.

`struct tty` (`sys/sys/tty.h`) carries, all copied raw:
- `.text` function pointers `t_oproc` / `t_stop` / `t_param` / `t_unhold` (lines 94-99)
- kernel heap pointers `t_pgrp` (86), `t_session` (87), `t_sigio` (88), `t_sc` (100), `t_slsc` (101)
- per-clist data-buffer pointers `t_rawq.c_data` / `t_canq.c_data` / `t_outq.c_data` (line 50)
- an embedded `lwkt_token t_token` (74)

Only `t_dev` (82) is rewritten before copyout.

## Proof

`./leak_ttys` run as `maxx` (uid 1001, not in wheel) returns 3760 bytes (10 ttys
× 376-byte `struct tty`) and scans 102 kernel-range pointers. Cross-referencing
the leaked `.text` values against `nm /boot/kernel/kernel` (also world-readable)
yields **exact symbol matches**:

| intra-off | field (struct tty) | leaked value        | nm symbol    | match |
|----------:|--------------------|---------------------|--------------|-------|
| 256       | `t_oproc`          | `0xffffffff80b8b800`| `scstart`    | EXACT |
| 264       | `t_stop`           | `0xffffffff806b7eb0`| `nottystop`  | EXACT |
| 272       | `t_param`          | `0xffffffff80b86930`| `scparam`    | EXACT |
| 256       | `t_oproc` (com)    | `0xffffffff80c2e9b0`| `comstart`   | EXACT |
| 264       | `t_stop`  (com)    | `0xffffffff80c2fe30`| `comstop`    | EXACT |
| 272       | `t_param` (com)    | `0xffffffff80c30120`| `comparam`   | EXACT |

Plus heap pointers in the `0xfffff8xxxxxxxxxx` direct-map region (clist `c_data`
buffers, `t_pgrp`/`t_session` slab objects, the global token at intra-off 352).
Stable across 3 reads in the same boot (see `run.log`/`run.2.log`/`run.3.log`).

Because the leaked function-pointer values equal the static `nm` addresses, this
build has no KASLR slide on the tty ops — an attacker recovers the exact kernel
`.text` base and heap layout from an unprivileged account.

## Impact
Information disclosure: precise kernel `.text` base + kernel heap layout to any
local unprivileged user. Not a memory-corruption primitive itself, but it is a
KASLR-defeat and slab-grooming enabler that escalates the practical severity of
any local heap/stack corruption bug into reliable exploitation. Rated Low
standalone (info disclosure, no integrity/availability impact).

## Is the node world-readable? (privilege-gate trace)
Yes. The OID at `sys/kern/tty.c:2923` is `CTLFLAG_RD` with no `CTLFLAG_ANYBODY`
needed (that flag is write-only). `sysctl_root` (`kern_sysctl.c:1446-1450`)
gates only writes:
```c
if (!(oid->oid_kind & CTLFLAG_ANYBODY) && req->newptr && p &&
    (error = caps_priv_check(td->td_ucred, SYSCAP_NOSYSCTL_WR)))
    return (error);
```
`req->newptr` is NULL on reads, so the branch is skipped. `kern.ttys` is
readable by `maxx` (verified). No per-handler privilege check exists in
`sysctl_kern_ttys`. → the gate that would prevent the leak is absent on master.

## PoC changes
Rewrote `leak_ttys.c`: the original used the wrong kernel-address range
(`>= 0xffffffff80000000`) which misses the DragonFly direct-map/heap region
(`0xfffff8xxxxxxxxxx`) where most of the pointers live, and printed only a
subset. The new version detects the full canonical kernel upper half
(`>= 0xffff800000000000`), prints up to 60 candidates, and dumps the raw blob
to `ttys.bin` so the leaked function pointers can be cross-referenced against
`nm /boot/kernel/kernel` (done in `leak_sample.txt`). Added `build.sh`/`run.sh`.

## Recommended fix
Matches the finding markdown's proposal. Authoritative `fix.diff` in this folder
zeros all pointer fields of the local copy before `SYSCTL_OUT` while preserving
`t_dev` sanitization and every field `pstat(8)` reads. A stronger long-term fix
is a dedicated pointer-free `struct kinfo_tty` (mirroring `kinfo_file`).
