TIOCSCTTY reassignment orphans the old tty's t_session, yielding an unprivileged use-after-free of struct session (deterministic panic; ctty hijack of recycled sessions)
| Field | Value |
|---|---|
| ID | DF-2687 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:H |
| CWE | CWE-416 Use After Free (CWE-667 secondary) |
| File | sys/kern/tty.c |
| Lines | 1190-1199 (missing dissociation), 294-296 (ttyclearsession deref), 1623-1624 (ttymodem) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | memcorrupt |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
ttioctl() TIOCSCTTY (tty.c:1181-1204) moves s_ttyp to the new tty and
ttyunhold()s the old one but never clears the old tty's
t_session/t_pgrp β every other s_ttyp transition is paired with
ttyclearsession (:279-316). Because the vnode layer
(devfs_vnops.c:1598-1611) accepts a pty MASTER fd for TIOCSCTTY,
making the master vnode s_ttyvp, the master's last close clears
s_ttyvp via devfs' controlling-tty half-close (:1153-1156) without
running ttyclose() (ptcclose never calls it), leaving tp->t_session set
while the guard at :1183-1185 now passes (s_ttyvp == NULL). The leader
then re-TIOCSCTTYs a second tty and exits; sess_rele()
(kern_proc.c:930-947) repairs only sess->s_ttyp->t_session and kfrees
the session, so the first tty's t_session dangles into freed M_SESSION
memory. Closing the orphaned slave runs ttyclearsession β
lwkt_gettoken(&prg->proc_token) on freed memory (:294-296);
ttymodem()'s ksignal(tp->t_session->s_leader, SIGHUP) (:1623-1624)
is the same class. Additionally leaks a pgrp reference per trigger and
enables ctty hijack: the freed 104-byte M_SESSION chunk is
LIFO-recycled by the next setsid(), making the orphaned tty act as that
unrelated session's controlling terminal (isctty matches; cross-session
TIOCSPGRP/TIOCSWINSZ pgsignal reach).
Threat model & preconditions
Any local unprivileged user with access to /dev/ptmx. Reproduced:
deterministic kernel panic (Fatal trap 9, lwkt_gettoken+0x64) from uid
1002 with debug.use_weird_array=1 making the freed-chunk read fault;
without the flag the same unconditional deref silently consumes stale
freed-memory contents. Ceiling beyond DoS: session-structure recycling
gives attacker-influenced t_session targets (ksignal on chosen
s_leader where ttymodem is reachable; cross-session signal delivery and
job-control hijack), plus permanent kernel state corruption.
Proof of concept
findings/poc/DF-2687/df2687.c (unprivileged): open ptmx+pts; fork
leader; parent drops master; leader setsid β ioctl(master, TIOCSCTTY) β
close(master) β open pty2 + TIOCSCTTY (orphans tp1) β exit (session
kfreed); parent closes the orphaned slave β panic. hold mode +
kern.ttys decode shows the orphaned TS_ZOMBIE tty with a non-NULL freed
session pointer. uid0 route not completed (documented: M_SESSION LIFO
recycle β t_session aliases a chosen live session β ctty hijack;
ksignal sink gated by devfs invisibility of closed clone masters on the
stock guest).
Recommended fix
Dissociate the replaced tty inside TIOCSCTTY (token-guarded t_session/t_pgrp clear + pgrel) and reject TIOCSCTTY on D_MASTER fds (devfs_fo_ioctl / vn_ioctl). Full diff in findings/poc/DF-2687/fix.diff. Hunk 1 validated in-guest (nativekernel build + reboot): identical PoC + poison β no panic, kern.ttys shows the orphan fully dissociated.
Timeline
- 2026-08-30 Discovered during pass-2 audit of tty.c (GLM 5.3); deterministic unpriv panic reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2687 Β· 24 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df2687.c | β | 7.3 KB | view raw | |
| df2687x.c | β | 8.3 KB | view raw | |
| df2687race.c | β | 5.4 KB | view raw | |
| forensic.c | β | 1.2 KB | view raw | |
| build.sh | β | 185 B | view raw | |
| run.sh | β | 357 B | view raw | |
| pin.sh | β | 511 B | view raw | |
| race.sh | β | 705 B | view raw | |
| build.log | β | 753 B | view raw | |
| run.log | β | 1.2 KB | view raw | |
| run.fix.log | β | 402 B | view raw | |
| run.pin.log | β | 1.7 KB | view raw | |
| run.alias.log | β | 1018 B | view raw | |
| run.race.log | β | 3.8 KB | view raw | |
| run.poison1.log | β | 1008 B | view raw | |
| panic.txt | β | 630 B | view raw | |
| forensic.baseline.txt | β | 661 B | view raw | |
| forensic.fixed.txt | β | 349 B | view raw | |
| env.txt | β | 536 B | view raw | |
| env.round2.txt | β | 987 B | view raw | |
| fix.diff | β | 2.3 KB | view raw | |
| FIXNOTES.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 13.0 KB | β raw | |
| verdict.json | β | 7.7 KB | view raw |
DF-2687 VERDICT β REPRODUCED (deterministic unprivileged kernel UAF, panic)
Bottom line
An unprivileged local user can leave a struct tty's t_session pointer
dangling into freed struct session memory and then force the kernel to
dereference it, with full control of the trigger timing. Demonstrated as a
deterministic kernel panic (lwkt_gettoken GP-fault on
0xdeadc0dedeadc0de after enabling the INVARIANTS free-poison
debug.use_weird_array) and as a raw dangling kernel heap pointer in the
kern.ttys sysctl dump. A one-hunk fix in sys/kern/tty.c (dissociate the
replaced tty in TIOCSCTTY) was built, installed, and verified to eliminate
both the panic and the dangling pointer.
The bug, line by line
ttioctl()TIOCSCTTY guard β sys/kern/tty.c:1183-1185:((p->p_session->s_ttyvp || tp->t_session) && (tp->t_session != p->p_session))β the only thing preventing a session from re-assigning its controlling tty is a non-NULLs_ttyvp(ortp->t_sessionequal to ours).- The vnode layer records the ioctl fd's vnode as
s_ttyvp(sys/vfs/devfs/devfs_vnops.c:1598-1611, sys/kern/vfs_vnops.c:1030-1052) with no validation that the fd is a terminal slave. A pty master fd is accepted. - Last close of that master vnode runs devfs' controlling-tty half-close
s_ttyvp = NULL(sys/vfs/devfs/devfs_vnops.c:1153-1156) and, because the ptm device's last close isptcclose(),ttyclose()is never called (sys/kern/tty_pty.c:636-689 has nottyclose), sotp->t_sessionsurvives whiles_ttyvpis NULL. - The session leader now passes the guard in (1) and TIOCSCTTYs a second
tty: sys/kern/tty.c:1190-1203 β
ttyhold(tp2),tp2->t_session = sp,otp = s_ttyp; s_ttyp = tp2; ttyunhold(otp).otp->t_sessionis never cleared β unlike every others_ttyptransition, which is paired withttyclearsession()(tty.c:279-316). - Leader exits:
sess_rele()repairs onlysess->s_ttyp->t_session(sys/kern/kern_proc.c:930-943) andkfree()s the session (kern_proc.c:947). The orphaned tty'st_sessionnow dangles. - Dereference sites (unprivileged reachability on the stock guest):
* final slave close β
ptscloseβttyclose(tty.c:261) βttyclearsession:sp = tp->t_session(tty.c:294),prg = sp->s_prg(tty.c:295),lwkt_gettoken(&prg->proc_token)(tty.c:296) β deterministic, this is the panic we hit. * master carrier loss βptccloseβttymodem:tp->t_session->s_leader+ksignal()(tty.c:1623-1624) β reachable on configurations where the master can be re-opened (e.g. a second devfs mount, jails); not reachable on the stock guest because unix98 master nodes are devfs-invisible after device close. *TIOCGSID/isctty()(tty.c:1009-1015, sys/sys/tty.h:216-217) β safe NULL-wise (compares against the caller's live session) but becomes a session-aliasing oracle when the freed chunk is recycled into a new session: the orphaned tty starts acting as the new session's controlling terminal (ctty hijack, cross-sessionTIOCSPGRP/TIOCSWINSZβpgsignalreach).
Reproduction evidence
panic.txtβ Fatal trap 9,Stopped at lwkt_gettoken+0x64: movq (%r12),%rax, current process = the unprivileged PoC (pid 2179), guest wedged atdb>.forensic.baseline.txtβ kern.ttys dump during PoC hold mode: orphaned TS_ZOMBIE pty (state=01100020, refs=0) withsession=0xfffff80116d4e970while its session is already freed (leader reaped) β the dangling pointer itself.run.logβ full run transcript (build + 40 pre-fix rounds + poison run leading to the panic).- Trigger chain:
df2687.csteps (1)-(6), all unprivileged: open ptmx/pts β fork L β L: setsid, TIOCSCTTY(master fd), close master, TIOCSCTTY(pts2), exit β parent closes orphaned slave β UAF.
Determinism: every code path is sequenced by the program; the only heap
dependency is what occupies the freed 104-byte chunk, which the
debug.use_weird_array poison fixes at 0xdeadc0de (a kernel INVARIANTS
facility provided precisely for exposing use-after-frees). With the flag
off the dereference still occurs β it reads stale freed-memory contents.
Exploit chain (assessed, not completed to uid=0)
[Round-2 correction, 2026-10-16] The recycling scenarios assessed below ("next setsid() reuses the freed chunk", session aliasing, TIOCGSID oracle) were refuted by measurement β see R1/R2 above: the chunk is pinned until the orphan's own close and the dangling read happens ~10 instructions after the same-syscall free. They are kept here only as the historical round-1 assessment.
The panic is proof of the primitive. Toward escalation on this bug:
groom M_SESSION chunks (setsid churn is same-type/same-size, LIFO per
CPU) so the freed session is recycled into an attacker-influenced session
(verified conceptually; the TIOCGSID alias oracle needs P_CONTROLT which a
candidate can set via its own TIOCSCTTY). s_leader then points at a
chosen live process β ttymodem() delivers SIGHUP to it; conversely, in
allocator states where the chunk holds non-session data, ksignal() is
called on a raw heap-content pointer. DF's slab allocator is per-CPU,
per-size-class and shared across malloc types, so cross-type grooming of
the 128-byte class is possible given CPU affinity. Full uid=0 was NOT
developed: on the stock guest the ttymodem sink is gated by devfs node
invisibility of closed clone masters, and ttyclearsession's s_prg
read is the only deterministic deref. The impact demonstrated here is
therefore: reliable local DoS (panic) + permanent kernel state corruption
(dangling pointer, leaked pgrp) + ctty hijack of recycled sessions.
Round 2 β weaponization attempt (2026-10-16, exploit-development mode)
Goal: zero-precondition unprivileged uid=0 (or equivalent) from this bug on the stock guest. Terminal result: not achievable β blocked structurally, not by luck. What was done and proven:
R1. The pin invariant (corrects round 1's lifecycle model)
Round 1 asserted the session is "already freed (leader reaped)" while the
orphan slave is held, implying an unbounded reclaim window. That is wrong.
The orphan's leaked t_pgrp reference keeps the session's pgrp alive, and
a live pgrp's session reference (s_count, sys/kern/kern_proc.c:783/797)
pins the session. Measured on the guest (run.pin.log):
vmstat -mM_SESSION in-use: BEFORE=19, MIDHOLD=27 (+8 while 8 orphan slaves are held open), AFTER=19 β the chunks free exactly at the orphan's ownclose(), never before.
Consequence: the free and the dangling read happen inside the same
ttyclearsession() invocation: pgrel(leaked pgrp) β sess_rele() β
kfree(session) (kern_proc.c:947) followed ~10 instructions later by
sp = tp->t_session; prg = sp->s_prg (tty.c:294-295). The reclaim window
is that same-thread, same-syscall sliver β there are no sleep points after
kfree(sess) (only lwkt_reltoken, kfree(pgrp), two returns).
R2. The recycled-session-hijack route is closed
run.alias.log: 0/256 setsid candidates ever aliased the orphan (TIOCGSID oracle never fired) β impossible under the pin model, and consistent with round 1's own 40-round transcript (no alias ever observed). The "ctty hijack of recycled sessions" escalation assessed in round 1 cannot occur on stock configuration.
R3. The reclaim race is unwinnable by userland
An unprivileged controlled-content reclaim primitive for the 104-byte
session zone exists and was weaponized (df2687race.c): the
sysctl KERN_PROC_ARGS self-write performs
kmalloc(8+96=104, M_PARGS) + SYSCTL_IN(96 controlled binary bytes) +
kfree (kern_proc.c:1962-1971) β same per-size slab zone as
struct session (zones are shared across malloc types,
kern_slaballoc.c zoneindex), fully controlled, repeatable at full speed.
Detection was non-destructive: spray s_prg = VA of a private userland
fake lwkt token (no SMAP on this guest) with t_count=TOK_EXCLREQ(2);
a kernel token dance on it leaves the persistent trace t_count 2β0.
Result (run.race.log): 8750 window attempts, 0 hits. Structural reasons, not variance:
- No sleep/block points exist in the window (above).
- DragonFly LWKT does not switch a CPU-bound user thread to another user thread mid-syscall; switches happen at block/yield/boundary points. Only interrupt context can interleave, and nothing interrupt-side allocates from the 104-byte kmalloc zone.
- A chunk freed to a CPU's zone array can only be handed back out by a subsequent allocation on that same CPU (or a remote drain), neither of which userland can cause inside the window.
So on a default-config kernel the dangling deref always consumes the
stale-but-valid pre-free contents (s_prg = the real procglob pointer),
the token dance is a no-op, and close() returns cleanly. Cumulative
empirical support: 40 (round 1) + 3 + 8 + 8750 closes, zero panics
without the INVARIANTS poison. The round-1 deterministic panic requires
debug.use_weird_array=1 β a root-set, INVARIANTS-only debugging aid
(the poison code is #ifdef INVARIANTS, kern_slaballoc.c:1433-1438;
the current reset snapshot's kernel is non-INVARIANTS and the sysctl is
inert there β run.poison1.log).
R4. Even with full chunk control the sink ceiling is zero
The only deterministic consumer of the dangling pointer is
lwkt_gettoken(&sp->s_prg->proc_token) / lwkt_reltoken() on ONE
fully-controlled 64-bit value (s_prg; &prg->proc_token == prg
since proc_token is the first member of struct procglob). That dance
is by construction reversible (net-zero RMW at one address):
exclusive-acquire CAS (count & ~EXCLREQ)|EXCLUSIVE then release
atomic_clear_long(TOK_EXCLUSIVE) + t_ref = NULL (lwkt_token.c:262-296,
439-459); the contended path merely ORs TOK_EXCLREQ and blocks the
closer itself (self-DoS). The powerful sinks are all unreachable:
ttymodem()'sksignal(sp->s_leader, SIGHUP)(tty.c:1623-1624) needs a master carrier-loss after the free; ordering makes this impossible (the master close that orphans the tty precedes the session free), and master re-open is fenced on stock single-devfs mounts (no/dev/ptmon this guest; DF-2769's control run was FENCED for the same reason).sess_rele()on a controlled chunk (LIST_REMOVE(sess, s_list)= a controlled-address write;ttyunhold(sess->s_ttyp)= atp->t_unholdfunction-pointer call, tty.c:1580-1589 β would be an instant ring-0 with SMEP off) andpgrel()'s write throughpg_session->s_ttyp(kern_proc.c:683-685) require the session/pgrp to free while still referenced. The pin invariant (R1) plus the fact that a live process's own pgrp always pins its session (a live process'sp->p_sessioncan never dangle) close every ordering that would reach them. Each was traced exhaustively (multi-orphan chains, pgrp swaps via TIOCSPGRP, alias-repair TIOCSCTTY, concurrent closers racing pgrel, the subr_prf.c tprintf transient sess_hold, exit1 revoke paths).
Bottom line of round 2
On the stock guest, DF-2687 is a genuine lifetime bug (orphaned
t_session/t_refs=0 tty, leaked pgrp reference) whose dangling
dereference is structurally guaranteed to consume stale-valid contents.
Zero-precondition unprivileged impact: none observable (silent).
Root-assisted INVARIANTS demonstration: deterministic panic (round 1).
uid=0: not reachable from this bug on stock configuration; the strongest
theoretical primitive (arbitrary-address atomic RMW via a forged s_prg)
is both unreachable (R3) and net-zero (R4). The fix from round 1 remains
correct and validated.
Round-1 fix validation (retained)
fix.diffhunk 1 (tty.c) applied alone inside the guest (patch -p1),make -j6 nativekernel KERNCONF=X86_64_GENERIC(BUILD_RC=0, 357 s),make installkernel, reboot intoDragonFly 6.5-DEVELOPMENT #1: Sun Aug 30 20:17:44 UTC 2026.- Baseline (stock #0 kernel, same PoC,
debug.use_weird_array=1): Fatal trap 9 panic β reproduced. - Patched (#1 kernel):
/tmp/df2687 3β all rounds complete, POC_RC=0, guest up β no panic. - Patched forensic (hold mode):
state=01100020 dev=0x2100 pgrp=0x0 session=0x0β the orphaned tty is now fully dissociated (both the danglingt_sessionand the leakedt_pgrpare gone).
Environmental notes
- Guest: DragonFly 6.5-DEVELOPMENT, stock INVARIANTS kernel #0 (Thu Jul 2 06:02:54 UTC 2026), x86_64, 6 vCPUs, single devfs mount, no vfs.usermount.
- unix98 ptys only (
/dev/ptmxclone); no legacy/dev/pty??devices. debug.use_weird_arrayis 0 by default; enabling it does not change control flow of the bug β it poisons freed slab memory (first 64 bytes,0xdeadc0de) so the UAF read faults instead of silently consuming stale data.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix for DF-2687: orphaned tp->t_session UAF via TIOCSCTTY reassignment
Root cause (two cooperating defects)
-
sys/kern/tty.c ttioctl() TIOCSCTTY moves the session's s_ttyp to the new tty and drops the old tty's hold (ttyunhold) but never clears the old tty's t_session / t_pgrp. Every other s_ttyp transition is paired with a full dissociation (ttyclearsession); this one is not.
-
The vnode layer (sys/kern/vfs_vnops.c / sys/vfs/devfs/devfs_vnops.c) records the ioctl fd's vnode as s_ttyvp without validating that it is a terminal slave. Issuing TIOCSCTTY on a pty MASTER fd therefore makes the master vnode the session's ctty vnode; the master's last close then clears s_ttyvp (devfs_spec_close controlling-tty half-close) without running ttyclose() (ptcclose never calls it), leaving the session reassignable while the first tty's t_session still points at it.
After the session is freed (sess_rele only repairs sess->s_ttyp->t_session), the first tty's t_session dangles into freed M_SESSION memory and is dereferenced by ttyclearsession() (tp->t_session->s_prg -> lwkt_gettoken) on the final slave close and by ttymodem() (tp->t_session->s_leader -> ksignal) on master carrier loss.
Patch
- tty.c: dissociate the replaced tty inside TIOCSCTTY (clear t_session and t_pgrp under its token, release the pgrp reference) before ttyunhold().
- devfs_vnops.c / vfs_vnops.c: reject TIOCSCTTY on D_MASTER devices.
--- a/sys/kern/tty.c
+++ b/sys/kern/tty.c
@@ -1193,6 +1193,22 @@ case TIOCSCTTY: /* become controlling tty */
ttyhold(tp);
tp->t_session = p->p_session;
opgrp = tp->t_pgrp;
pgref(p->p_pgrp);
tp->t_pgrp = p->p_pgrp;
otp = p->p_session->s_ttyp;
p->p_session->s_ttyp = tp;
p->p_flags |= P_CONTROLT;
if (otp) {
+ /*
+ * Fully dissociate the tty we are replacing,
+ * otherwise its t_session pointer is left
+ * dangling into the (possibly soon-freed)
+ * session [DF-2687].
+ */
+ if (otp != tp) {
+ lwkt_gettoken(&otp->t_token);
+ if (otp->t_session == p->p_session) {
+ otp->t_session = NULL;
+ opgrp = otp->t_pgrp;
+ otp->t_pgrp = NULL;
+ if (opgrp) {
+ pgrel(opgrp);
+ opgrp = NULL;
+ }
+ }
+ lwkt_reltoken(&otp->t_token);
+ }
ttyunhold(otp);
}
if (opgrp) {
See fix.diff in this directory for the git-apply-able version including the devfs/vnops master-fd rejection.
Fix verification
fixedKernel rebuilt with fix.diff hunk 1 (sys/kern/tty.c TIOCSCTTY dissociation) only; exact same PoC with debug.use_weird_array=1 completes all rounds with no panic; kern.ttys forensic shows the orphaned tty fully dissociated (session=0x0, pgrp=0x0) where the stock kernel showed the dangling session pointer and panicked.
run.fix.log; forensic.fixed.txt; build.log (nativekernel BUILD_RC=0, installkernel INSTALL_RC=0)
Confirmed kernel references
- sys/kern/tty.c:1181-1204
- sys/kern/tty.c:279-316
- sys/kern/tty.c:294-296
- sys/kern/tty.c:1580-1589
- sys/kern/tty.c:1623-1624
- sys/kern/kern_proc.c:649-692
- sys/kern/kern_proc.c:776-807
- sys/kern/kern_proc.c:896-948
- sys/kern/kern_proc.c:1955-1976
- sys/kern/kern_slaballoc.c:1433-1438
- sys/kern/lwkt_token.c:262-296
- sys/kern/lwkt_token.c:439-459
Detail
Exploit chain
attempted: unpriv orphan chain -> hold -> KERN_PROC_ARGS 104-byte controlled reclaim churn (df2687race.c) -> close(orphan) racing the ~30ns same-syscall window between kfree(session) and the sp->s_prg read -> forged s_prg = userland fake lwkt token (no SMAP) -> persistent trace t_count 2->0. Result: 0/8750 hits; window is structurally uninterleavable by userland (no mid-syscall user-thread switches, no interrupt-side 104-zone allocations, no sleep points post-free). uid0 chain not reachable: sink inventory for the dangling t_session on stock config = {net-zero token dance} only; ttymodem ksignal gated; sess_rele/pgrel controlled-chunk sinks impossible while pin invariants hold
Evidence (decisive lines)
['run.pin.log: BEFORE=19 MIDHOLD=27 AFTER=19 M_SESSION in-use -> session chunks pinned while orphan slaves held, freed exactly at close', 'run.alias.log: 0/256 setsid candidates aliased the orphan -> recycled-session hijack impossible', 'run.race.log: df2687race 3 8 60 -> ~8750 window attempts, 0 fake-token trace hits, plus structural analysis of why', 'run.poison1.log: poison sysctl inert on non-INVARIANTS snapshot #1; bug presence confirmed behaviorally via pin experiment', 'panic.txt / run.log (round 1): deterministic INVARIANTS-poison panic at lwkt_gettoken+0x64, unprivileged trigger', 'df2687race.c / df2687x.c: round-2 sources (race weaponizer with fake-token trace detector; pin/alias probes)']
PoC changes
Round 2 added: df2687x.c (pin/alias/spray probes) and df2687race.c (weaponization race: 3 closer threads x orphan chains + 8 sprayer threads x KERN_PROC_ARGS 104-byte controlled transient alloc churn; benign persistent-trace detector via userland fake lwkt token with t_count=TOK_EXCLREQ transitioning 2->0 on consumption; no crash needed to detect a hit). Round-1 trigger unchanged.
Verified recommended fix
TIOCSCTTY: fully dissociate the replaced tty (clear t_session/t_pgrp under its token, pgrel) before ttyunhold(); reject TIOCSCTTY on D_MASTER fds in devfs_fo_ioctl/vn_ioctl before the device ioctl runs (fix.diff)
Verdict
Round 2 (weaponization mode) terminal: uid=0 is NOT achievable from DF-2687 on the stock single-devfs guest, blocked structurally rather than probabilistically. (1) Pin invariant measured: the orphan's leaked t_pgrp pgref pins the session's pgrp, which pins the session (s_count) - vmstat -m shows M_SESSION in-use +8 while 8 orphan slaves are held and back to baseline only at their closes; the session frees INSIDE the orphan's own ttyclearsession (pgrel->sess_rele->kfree) ~10 instructions before the same function reads tp->t_session->s_prg. There is no persistent reclaim window and no sleep point after the free, so the dangling deref always consumes stale-but-valid contents (s_prg = real procglob) and close() returns cleanly - 3+8+8750 more closes in round 2 with zero panics, matching round 1's 40 surviving rounds. The round-1 deterministic panic requires the root-set INVARIANTS-only debug.use_weird_array poison (kern_slaballoc.c:1433-1438); zero-precondition unprivileged impact is silent. (2) The recycled-session-hijack route is closed: 0/256 setsid candidates ever aliased the orphan (TIOCGSID oracle never fired), consistent with the pin model and with round 1's own logs. (3) A real unprivileged controlled-content reclaim primitive was built (sysctl KERN_PROC_ARGS self-write = kmalloc(104)+96 controlled bytes+kfree, same slab zone as struct session) and raced against the window for 8750 attempts with a persistent non-destructive fake-token trace detector: 0 hits, explained by DFly LWKT not switching user threads mid-syscall and nothing interrupt-side allocating the 104-byte zone. (4) Even with full chunk control the sole deterministic consumer is the lwkt_gettoken/lwkt_reltoken pair on one controlled s_prg value - by construction net-zero (reversible CAS/clear; contended path only self-DoSes); the powerful sinks (ttymodem ksignal(s_leader), sess_rele LIST_REMOVE + ttyunhold(s_ttyp)->t_unhold function-pointer call, pgrel write through pg_session->s_ttyp) are each unreachable: ksignal is gated by master-reopen fencing (no /dev/ptm) and ordering, and the sess_rele/pgrel-on-controlled-chunk sinks require the session/pgrp to free while still referenced, which the pin invariant plus live-process pgrp pinning prohibit in every ordering traced. Round-1 panic and fix validation stand unchanged.
No comments yet.