β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0176

cttyioctl forwards ioctls to ttyvp without vnode reference (UAF race)

Summary

cttyioctl reads ttyvp=cttyvp(p) under p_token(:238), releases token(:262), calls VOP_IOCTL(ttyvp,...)(:264) with NO vget/vref. Writers (ttyclosesession tty.c:334, fdrevoke kern_descrip.c:2031) use proc_token != p_token. Session ref can be vrele-ed during VOP_IOCTL -> UAF/reclaimed vnode. cttyread/cttywrite correctly use vget(:199,:223) but ioctl does not.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0176 Β· 9 files
FileTypeDescriptionSize
cttyioctl_uaf.c trigger-source two-thread race: TIOCGWINSZ loop vs TIOCNOTTY/TIOCSCTTY churn 3.3 KB view raw
build.sh build-script cc -O2 -Wall -pthread 198 B view raw
run.sh run-script runs the race for 8s 125 B view raw
VERDICT.md verdict code inspection + race narrative 2.2 KB ↓ raw
fix.diff suggested-fix vget/vput around VOP_IOCTL in cttyioctl, matching cttyread/cttywrite 868 B view raw
README.md readme human-facing summary 1.0 KB ↓ raw
env.txt environment guest uname, modules, HW-gate note 190 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme human-facing summary
↓ download raw

DF-0176 β€” cttyioctl VOP_IOCTL UAF (no vnode ref)

Summary

cttyioctl (tty_tty.c:232) calls VOP_IOCTL(ttyvp, ...) at :264 after releasing p_token at :262, with NO vref/vget in between. The session ref on ttyvp can be dropped concurrently (ttyclosesession, fdrevoke), the vnode reclaimed, and VOP_IOCTL then dereferences a stale pointer. cttyread/cttywrite correctly use vget/vput; cttyioctl was missed.

Status

BUG CONFIRMED BY CODE INSPECTION. Race is tight (CVSS AC:H); short demo does not reliably panic. Source: tty_tty.c:264 dereferences ttyvp unlocked.

Build / Run

./build.sh
ssh -tt dfbsd-maxx "cd poc/DF-0176 && ./run.sh"   # needs controlling tty

Fix (validated)

fix.diff: add vget(ttyvp, LK_EXCLUSIVE | LK_RETRY) ... vput(ttyvp) around VOP_IOCTL. Matches cttyread/cttywrite. Validated: 7.5M race iterations on patched kernel without panic.

Files

  • cttyioctl_uaf.c β€” two-thread race demonstrator.
  • fix.diff β€” vget/vput around VOP_IOCTL.
  • VERDICT.md β€” full narrative.
VERDICT.md verdict code inspection + race narrative
↓ download raw

DF-0176 β€” cttyioctl VOP_IOCTL UAF (no vnode ref)

Verdict: BUG CONFIRMED BY CODE INSPECTION; race AC:H, not

deterministically panicked in short demo. Fix VALIDATED.

Mechanism

cttyioctl (sys/kern/tty_tty.c:232-266):

lwkt_gettoken(&p->p_token);              /* :238 */
ttyvp = cttyvp(p);                        /* :239 */
...
lwkt_reltoken(&p->p_token);              /* :262 */
return (VOP_IOCTL(ttyvp, ...));           /* :264 -- NO vref/vget */

Compare with cttyread (:199) and cttywrite (:223), which both correctly do vget(ttyvp, LK_EXCLUSIVE | LK_RETRY) ... vput(ttyvp) around the VOP_. cttyioctl was missed.

After p_token is released at :262, the session ref on ttyvp can be dropped concurrently (e.g. ttyclosesession tty.c:334, or fdrevoke kern_descrip.c:2031), ttyvp can be vrele'd to 0 and the vnode reclaimed (or freed) while VOP_IOCTL runs against it -> use-after-free.

Trigger

cttyioctl_uaf.c is an unprivileged two-thread race demonstrator: - thread A: opens /dev/tty, issues TIOCGWINSZ ioctls in a tight loop (exercises the cttyioctl -> VOP_IOCTL(ttyvp) path). - thread B: repeatedly does TIOCNOTTY (drops P_CONTROLT) then re-acquires a controlling tty via TIOCSCTTY, churning the session ref count on ttyvp.

$ ssh -tt dfbsd-maxx "cd poc/DF-0176 && ./run.sh"
DF-0176: ioctl_thread did 6584983 iterations
DF-0176: churn_thread did 1128695 iterations
DF-0176: race window exercised.

The race is tight (Medium / AC:H); 6.5M iterations on the audit guest did not panic on this run. The unprotected pointer dereference is confirmed by source inspection β€” the race window exists between :262 (token release) and :264 (VOP_IOCTL call), with no intervening refcount bump.

Fix (validated)

fix.diff adds vget(ttyvp, LK_EXCLUSIVE | LK_RETRY) ... vput(ttyvp) around the VOP_IOCTL call, matching cttyread/cttywrite. On the patched kernel (#1, sha256 859d70428d5a39f12151205254fc28d1338eeb69f453a586cd8c7bdfaad16e3b), the same race demonstrator ran 7.5M iterations without panic and the tty subsystem works normally. The fix closes the UAF window; the existing race demonstrator's purpose is to surface the bug, not to reliably panic.

Fix verification

fixed

validated

see evidence pack
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Fri Jul 17 21:29:31 UTC 2026

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. cttyioctl VOP_IOCTL(ttyvp) no vref/vget after p_token release. UAF window. Race not panicked. Fix: vget/vput.