The common SELinux practice is to have a distinct label for terminals in use by logged in users. This allows to differentiate access on the associated terminal (e.g. user_tty_device_t) vs foreign ones (e.g. tty_device_t or sysadm_tty_device_t). Therefore the application performing the user login and setting up the associated terminal should label that terminal according to the loaded SELinux policy. Commonly this is done by pam_selinux(7). Since sulogin(8) does not use pam(7) perform the necessary steps manually. Fixes: https://github.com/util-linux/util-linux/issues/1578 Signed-off-by: Christian Göttsche <cgzones@xxxxxxxxxxxxxx> --- Upstream pull-request: https://github.com/util-linux/util-linux/pull/2650 --- login-utils/sulogin-consoles.c | 4 + login-utils/sulogin-consoles.h | 4 + login-utils/sulogin.c | 156 +++++++++++++++++++++++++++++---- 3 files changed, 146 insertions(+), 18 deletions(-) diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c index 9ae525556..0dca949f4 100644 --- a/login-utils/sulogin-consoles.c +++ b/login-utils/sulogin-consoles.c @@ -341,6 +341,10 @@ int append_console(struct list_head *consoles, const char * const name) tail->id = last ? last->id + 1 : 0; tail->pid = -1; memset(&tail->tio, 0, sizeof(tail->tio)); +#ifdef HAVE_LIBSELINUX + tail->reset_tty_context = NULL; + tail->user_tty_context = NULL; +#endif return 0; } diff --git a/login-utils/sulogin-consoles.h b/login-utils/sulogin-consoles.h index 12032c997..608c4f84f 100644 --- a/login-utils/sulogin-consoles.h +++ b/login-utils/sulogin-consoles.h @@ -44,6 +44,10 @@ struct console { pid_t pid; struct chardata cp; struct termios tio; +#ifdef HAVE_LIBSELINUX + char *reset_tty_context; + char *user_tty_context; +#endif }; extern int detect_consoles(const char *device, int fallback, diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c index 019f35092..2682c30fb 100644 --- a/login-utils/sulogin.c +++ b/login-utils/sulogin.c @@ -99,6 +99,81 @@ static int locked_account_password(const char * const passwd) return 0; } +#ifdef HAVE_LIBSELINUX +/* + * Cached check whether SELinux is enabled. + */ +static int is_selinux_enabled_cached(void) +{ + static int cache = -1; + + if (cache == -1) + cache = is_selinux_enabled(); + + return cache; +} + +/* Computed SELinux login context. */ +static char *login_context; + +/* + * Compute SELinux login context. + */ +static void compute_login_context(void) +{ + char *seuser = NULL; + char *level = NULL; + + if (is_selinux_enabled_cached() == 0) + goto cleanup; + + if (getseuserbyname("root", &seuser, &level) == -1) { + warnx(_("failed to compute seuser")); + goto cleanup; + } + + if (get_default_context_with_level(seuser, level, NULL, &login_context) == -1) { + warnx(_("failed to compute default context")); + goto cleanup; + } + +cleanup: + free(seuser); + free(level); +} + +/* + * Compute SELinux terminal context. + */ +static void tcinit_selinux(struct console *con) +{ + security_class_t tclass; + + if (!login_context) + return; + + if (fgetfilecon(con->fd, &con->reset_tty_context) == -1) { + warn(_("failed to get context of terminal %s"), con->tty); + return; + } + + tclass = string_to_security_class("chr_file"); + if (tclass == 0) { + warnx(_("security class chr_file not available")); + freecon(con->reset_tty_context); + con->reset_tty_context = NULL; + return; + } + + if (security_compute_relabel(login_context, con->reset_tty_context, tclass, &con->user_tty_context) == -1) { + warnx(_("failed to compute relabel context of terminal")); + freecon(con->reset_tty_context); + con->reset_tty_context = NULL; + return; + } +} +#endif + /* * Fix the tty modes and set reasonable defaults. */ @@ -132,6 +207,10 @@ static void tcinit(struct console *con) errno = 0; #endif +#ifdef HAVE_LIBSELINUX + tcinit_selinux(con); +#endif + #ifdef TIOCGSERIAL if (ioctl(fd, TIOCGSERIAL, &serinfo) >= 0) con->flags |= CON_SERIAL; @@ -785,7 +864,7 @@ out: /* * Password was OK, execute a shell. */ -static void sushell(struct passwd *pwd) +static void sushell(struct passwd *pwd, struct console *con) { char shell[PATH_MAX]; char home[PATH_MAX]; @@ -842,22 +921,21 @@ static void sushell(struct passwd *pwd) mask_signal(SIGHUP, SIG_DFL, NULL); #ifdef HAVE_LIBSELINUX - if (is_selinux_enabled() > 0) { - char *scon = NULL; - char *seuser = NULL; - char *level = NULL; - - if (getseuserbyname("root", &seuser, &level) == 0) { - if (get_default_context_with_level(seuser, level, 0, &scon) == 0) { - if (setexeccon(scon) != 0) - warnx(_("setexeccon failed")); - freecon(scon); - } + if (is_selinux_enabled_cached() == 1) { + if (con->user_tty_context) { + if (fsetfilecon(con->fd, con->user_tty_context) == -1) + warn(_("failed to set context to %s for terminal %s"), con->user_tty_context, con->tty); + } + + if (login_context) { + if (setexeccon(login_context) == -1) + warn(_("failed to set exec context to %s"), login_context); } - free(seuser); - free(level); } +#else + (void)con; #endif + execl(su_shell, shell, (char *)NULL); warn(_("failed to execute %s"), su_shell); @@ -866,6 +944,30 @@ static void sushell(struct passwd *pwd) warn(_("failed to execute %s"), "/bin/sh"); } +#ifdef HAVE_LIBSELINUX +static void tcreset_selinux(struct list_head *consoles) { + struct list_head *ptr; + struct console *con; + + if (is_selinux_enabled_cached() == 0) + return; + + list_for_each(ptr, consoles) { + con = list_entry(ptr, struct console, entry); + + if (con->fd < 0) + continue; + if (!con->reset_tty_context) + continue; + if (fsetfilecon(con->fd, con->reset_tty_context) == -1) + warn(_("failed to reset context to %s for terminal %s"), con->reset_tty_context, con->tty); + + freecon(con->reset_tty_context); + con->reset_tty_context = NULL; + } +} +#endif + static void usage(void) { FILE *out = stdout; @@ -1015,6 +1117,10 @@ int main(int argc, char **argv) return EXIT_FAILURE; } +#ifdef HAVE_LIBSELINUX + compute_login_context(); +#endif + /* * Ask for the password on the consoles. */ @@ -1034,9 +1140,18 @@ int main(int argc, char **argv) } ptr = (&consoles)->next; - if (ptr->next == &consoles) { - con = list_entry(ptr, struct console, entry); - goto nofork; +#ifdef HAVE_LIBSELINUX + /* + * Always fork with SELinux enabled, so the parent can restore the + * terminal context afterwards. + */ + if (is_selinux_enabled_cached() == 0) +#endif + { + if (ptr->next == &consoles) { + con = list_entry(ptr, struct console, entry); + goto nofork; + } } @@ -1087,7 +1202,7 @@ int main(int argc, char **argv) #endif if (doshell) { /* sushell() unmask signals */ - sushell(pwd); + sushell(pwd, con); mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit); mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp); @@ -1193,5 +1308,10 @@ int main(int argc, char **argv) } while (1); mask_signal(SIGCHLD, SIG_DFL, NULL); + +#ifdef HAVE_LIBSELINUX + tcreset_selinux(&consoles); +#endif + return EXIT_SUCCESS; } -- 2.43.0