From 95f5b58d391013634259b6552a66d2bfbb48397b Mon Sep 17 00:00:00 2001 From: Serge Hallyn Date: Mon, 8 Jun 2026 20:08:04 -0500 Subject: [PATCH] Avoid falling prey to tiocsti We've long known (see https://www.halfdog.net/Security/2012/TtyPushbackPrivilegeEscalation/ and https://jdebp.uk/FGA/dont-abuse-su-for-dropping-privileges.html) that su should be used to gain but not drop privilege. Much more recently, linux added the ability to prevent TIOCSTI through a configurable /proc/sys/dev/tty/legacy_tiocsti setting. If /proc/sys/dev/tty/legacy_tiocsti is set to 0, then we are protected from the callee injecting commands on caller's tty through TIOCSTI. If it's 1, or doesn't exist, then we are not. That can be dangerous if caller is root. We currently give up the controlling terminal for non-interactive uses of su (-c). Let's do that for interactive calls as well, only in the dangerous case. --- src/su.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/su.c b/src/su.c index 8cea0d1f04..d07f9590c5 100644 --- a/src/su.c +++ b/src/su.c @@ -56,6 +56,7 @@ #ifdef USE_PAM #include "pam_defs.h" #endif /* USE_PAM */ +#include "io/fgets/fgets.h" #include "pwauth.h" #include "prototypes.h" #include "shadowlog.h" @@ -996,6 +997,29 @@ static void set_environment (struct passwd *pw) } +/* + * See the following kernel commit: + * commit 83efeeeb3d04b22aaed1df99bc70a48fe9d22c4d + * Author: Kees Cook + * Date: Sat Oct 22 11:29:49 2022 -0700 + * Subject: tty: Allow TIOCSTI to be disabled + */ +static bool legacy_tiocsti_is_disabled(void) { + char buf[3]; + FILE *fp; + void *ret; + + fp = fopen("/proc/sys/dev/tty/legacy_tiocsti", "r"); + if (NULL == fp) + return false; + ret = fgets_a(buf, fp); + fclose(fp); + if (ret == NULL) + return false; + + return buf[0] == '0'; +} + /* * su - switch user id * @@ -1010,6 +1034,7 @@ int main (int argc, char **argv) { const char *cp; struct passwd *pw = NULL; + bool need_pty_prot; #ifdef USE_PAM int ret; @@ -1023,6 +1048,8 @@ int main (int argc, char **argv) save_caller_context(); + need_pty_prot = caller_is_root && !legacy_tiocsti_is_disabled(); + OPENLOG (Prog); process_flags (argc, argv); @@ -1152,7 +1179,7 @@ int main (int argc, char **argv) set_environment (pw); - if (!doshell) { + if (!doshell || need_pty_prot) { /* There is no need for a controlling terminal. * This avoids the callee to inject commands on * the caller's tty. */