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)
PoC verification
Evidence pack
findings/poc/DF-0176 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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.
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
fixedvalidated
see evidence pack
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.
No comments yet.