# DF-0005 — PoC: TIOCSTI unrestricted terminal input injection (no killswitch)

**Status: REPRODUCED** on DragonFlyBSD master DEV (`v6.5.0.1712.g89e6a-DEVELOPMENT`).
Unprivileged `uid=1001` can inject arbitrary bytes into a controlling terminal
via `TIOCSTI`; the injected bytes are consumed and executed by the next reader
of the tty. No `EPERM`/`EACCES` is returned and **no sysctl/capability
killswitch exists** to disable it.

## The issue

In `ttioctl` (`sys/kern/tty.c:1158-1173`) the two
`caps_priv_check_td(td, SYSCAP_RESTRICTEDROOT)` guards (`tty.c:1160,1167`)
are each gated on a condition a legitimate controlling-tty owner does **not**
meet:

| Guard | Gating condition | Value for ctty owner (`O_RDWR` open of `/dev/tty`) | Result |
|-------|------------------|----------------------------------------------------|--------|
| 1st (`tty.c:1159-1163`) | `(flag & FREAD) == 0` | **FALSE** — `FREAD=0x0001` is set (`fcntl.h:68`) | short-circuits → no `EPERM` |
| 2nd (`tty.c:1165-1169`) | `!isctty(p, tp)` | **FALSE** — `isctty` is true for the ctty (`tty.h:216`) | short-circuits → no `EACCES` |

Control falls through to the inject sink:

```c
(*linesw[tp->t_line].l_rint)(*(u_char *)data, tp);   /* sys/kern/tty.c:1172 */
```

…which pushes the attacker byte into the tty input queue exactly as if typed.
A grep for `tty_tiocsti` / `legacy_tiocsti` across `sys/` returns nothing, and
on the running guest `sysctl kern.tty_tiocsti`, `kern.legacy_tiocsti`, and
`dev.tty.legacy_tiocsti` all return "unknown oid" — there is no killswitch.

## Build

On the DragonFlyBSD guest, as any user:

```
cc -Wall -o tiocsti tiocsti.c
# or simply:
./build.sh
```

## Run

As an **unprivileged** user (no external interactive terminal required — the
PoC allocates its own pty pair, so it runs cleanly under non-interactive ssh):

```
./tiocsti
# or inject a custom downstream command:
./tiocsti "echo custom_payload"
```

## Expected output (bug present)

```
[*] uid=1001 euid=1001  pty slave=/dev/pts/0
[+] TIOCSTI ioctl succeeded (no EPERM/EACCES) -- guards at tty.c:1160,1167 bypassed for ctty owner
[+] readback from /dev/tty (25 bytes): echo TIOCSTI_READBACK_OK
echo TIOCSTI_EXEC_BY_DOWNSTREAM_SHELL
exit
$ echo TIOCSTI_EXEC_BY_DOWNSTREAM_SHELL
TIOCSTI_EXEC_BY_DOWNSTREAM_SHELL          <-- injected command, EXECUTED by the downstream shell
$ exit
[*] downstream sh exit status: 0
```

This proves all three properties:

1. **No `EPERM`/`EACCES`** for unprivileged `uid=1001` → both privilege guards
   bypassed for the ctty owner (the core claim).
2. **Bytes reach the `l_rint` sink** (`tty.c:1172`) → the 25-byte injected
   line is read straight back out of `/dev/tty`.
3. **Confused-deputy execution** → a *different* downstream reader of the tty
   (a shell exec'd on the pty slave) reads the second injected line out of the
   input queue and **executes it**. This is the trust-boundary crossing that
   makes TIOCSTI dangerous (e.g. injecting into a setuid utility reading a
   password/command from the tty).

On a kernel patched with `fix.diff`, `sysctl kern.tty_tiocsti=0` makes the
ioctl return `EPERM` and the PoC prints `TIOCSTI DENIED: Operation not
permitted`.

The run is deterministic (3/3 identical — see `run.log`, `run.2.log`,
`run.3.log`) and non-destructive (no panic; guest stays up). Concrete
privilege escalation would require a setuid/privileged tty-reader victim,
which is program-specific and out of scope for this Low finding — hence the
finding's Low severity is confirmed accurate.

## Files

- `tiocsti.c` — self-contained trigger source.
- `build.sh` / `run.sh` — exact build/run commands.
- `build.log`, `run.log`, `run.2.log`, `run.3.log` — full untrimmed outputs.
- `env.txt` — guest uname, compiler, unprivileged uid, killswitch search.
- `VERDICT.md` — full mechanism walkthrough with `path:line` citations.
- `fix.diff` — git-apply-able `kern.tty_tiocsti` killswitch for `sys/kern/tty.c`.
- `manifest.json` — machine-readable artifact catalog.
