From e1ed334011845cefd372efe7c47fad9f812d7826 Mon Sep 17 00:00:00 2001 From: Mark Johnston Date: Mon, 23 Mar 2026 11:22:48 -0400 Subject: [PATCH] tty: Avoid leaving dangling pointers in tty_drop_ctty() The TIOCNOTTY handler detaches the calling process from its controlling terminal. It clears the link from the session to the tty, but not the pointers from the tty to the session and process group. This means that sess_release() doesn't call tty_rel_sess(), and that pgdelete() doesn't call tty_rel_pgrp(), so the pointers are left dangling. Fix this by clearing pointers in tty_drop_ctty(). Add a standalone regression test. Approved by: so Security: FreeBSD-SA-26:10.tty Security: CVE-2026-5398 Reported by: Nicholas Carlini Reviewed by: kib, kevans Fixes: 1b50b999f9b5 ("tty: implement TIOCNOTTY") Differential Revision: https://reviews.freebsd.org/D56046 --- sys/kern/tty.c | 4 ++ tests/sys/kern/Makefile | 1 + tests/sys/kern/tiocnotty.c | 82 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 tests/sys/kern/tiocnotty.c diff --git a/sys/kern/tty.c b/sys/kern/tty.c index b1b3b268d0e..a968762d616 100644 --- a/sys/kern/tty.c +++ b/sys/kern/tty.c @@ -1262,6 +1262,10 @@ tty_drop_ctty(struct tty *tp, struct proc *p) session->s_ttydp = NULL; SESS_UNLOCK(session); + if (tp->t_session == session) { + tp->t_session = NULL; + tp->t_pgrp = NULL; + } tp->t_sessioncnt--; p->p_flag &= ~P_CONTROLT; PROC_UNLOCK(p); diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile index eb5ea8c3c54..ac7e4898dfa 100644 --- a/tests/sys/kern/Makefile +++ b/tests/sys/kern/Makefile @@ -41,6 +41,7 @@ ATF_TESTS_C+= subr_physmem_test PLAIN_TESTS_C+= subr_unit_test ATF_TESTS_C+= sysctl_kern_proc ATF_TESTS_C+= sys_getrandom +PLAIN_TESTS_C+= tiocnotty ATF_TESTS_C+= tty_pts ATF_TESTS_C+= unix_dgram ATF_TESTS_C+= unix_passfd_dgram diff --git a/tests/sys/kern/tiocnotty.c b/tests/sys/kern/tiocnotty.c new file mode 100644 index 00000000000..2581f976b2e --- /dev/null +++ b/tests/sys/kern/tiocnotty.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2026 Mark Johnston + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * A regression test that exercises a bug where TIOCNOTTY would leave some + * dangling pointers behind in the controlling terminal structure. + */ + +#include +#include + +#include +#include +#include +#include +#include + +int +main(void) +{ + int master, slave, status; + pid_t child; + + master = posix_openpt(O_RDWR | O_NOCTTY); + if (master < 0) + err(1, "posix_openpt"); + if (grantpt(master) < 0) + err(1, "grantpt"); + if (unlockpt(master) < 0) + err(1, "unlockpt"); + + child = fork(); + if (child < 0) + err(1, "fork"); + if (child == 0) { + if (setsid() < 0) + err(1, "setsid"); + slave = open(ptsname(master), O_RDWR | O_NOCTTY); + if (slave < 0) + err(2, "open"); + if (ioctl(slave, TIOCSCTTY, 0) < 0) + err(3, "ioctl(TIOCSCTTY)"); + /* Detach ourselves from the controlling terminal. */ + if (ioctl(slave, TIOCNOTTY, 0) < 0) + err(4, "ioctl(TIOCNOTTY)"); + _exit(0); + } + + if (waitpid(child, &status, 0) < 0) + err(1, "waitpid"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + errx(1, "child exited with status %d", WEXITSTATUS(status)); + + child = fork(); + if (child < 0) + err(1, "fork"); + if (child == 0) { + struct winsize winsz; + + if (setsid() < 0) + err(1, "setsid"); + slave = open(ptsname(master), O_RDWR | O_NOCTTY); + if (slave < 0) + err(2, "open"); + /* Dereferences dangling t_pgrp pointer in the terminal. */ + memset(&winsz, 0xff, sizeof(winsz)); + if (ioctl(slave, TIOCSWINSZ, &winsz) < 0) + err(3, "ioctl(TIOCSWINSZ)"); + /* Dereferences dangling t_session pointer in the terminal. */ + if (ioctl(slave, TIOCSCTTY, 0) < 0) + err(4, "ioctl(TIOCSCTTY)"); + _exit(0); + } + + if (waitpid(child, &status, 0) < 0) + err(1, "waitpid"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + errx(1, "child exited with status %d", WEXITSTATUS(status)); +}