DF-2769: refuse master re-open while the unix98 pty slave is still open ptcclose() disarms every admission gate a fresh ptcopen() would check: it clears t_oproc (tty_pty.c:674), NULLs pt_prison (tty_pty.c:676) and sets both device nodes to 0:0 mode 0666 (tty_pty.c:677-682), while termination (and thus destruction of the master cdev) is blocked because the slave is still open (PF_SOPEN, tty_pty.c:273). Any process that can name the master device (/dev/ptm/N -- DEVFS_HIDDEN by default, exposed by devfs `show` rules such as those used for jail /dev trees) is then admitted as the new master of a still-live slave session. The TS_ZOMBIE fence is cleared by the new master itself via TIOCSETAW with CLOCAL (ttioctl, sys/kern/tty.c:1065-1072), giving it terminal input injection (ptcwrite), output capture (ptcread) and signal injection (TIOCSIG) against the surviving session -- verified as unprivileged command execution in a surviving root session. Fix: unix98 pty semantics -- once the master has been closed while the slave is open, the pty is dead for everyone; refuse with EIO. PF_SOPEN can only be set after a previous master existed, so first-time master opens are unaffected (a fresh clone is zero-allocated and /dev/pts/N nodes only exist after a ptmx open). --- sys/kern/tty_pty.c.orig +++ sys/kern/tty_pty.c @@ -584,9 +584,19 @@ ptcopen(struct dev_open_args *ap) tp = dev->si_tty; lwkt_gettoken(&tp->t_token); if (tp->t_oproc) { + pti_done(pti); + lwkt_reltoken(&tp->t_token); + lwkt_reltoken(&pti->pt_tty.t_token); + return (EIO); + } + + /* + * Once the previous master closed while the slave side is still + * open the pty is dead: do not let an unrelated process adopt + * the stale master device (session hijack). + */ + if (pti->pt_flags & PF_SOPEN) { pti_done(pti); lwkt_reltoken(&tp->t_token); lwkt_reltoken(&pti->pt_tty.t_token); return (EIO); } /* * If the slave side is not yet open clear any left over zombie