diff mbox series

[UTIL-LINUX] sulogin: relabel terminal according to SELinux policy

Message ID 20231213161412.23022-1-cgzones@googlemail.com (mailing list archive)
State Handled Elsewhere
Headers show
Series [UTIL-LINUX] sulogin: relabel terminal according to SELinux policy | expand

Commit Message

Christian Göttsche Dec. 13, 2023, 4:14 p.m. UTC
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@googlemail.com>
---
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(-)

Comments

James Carter Jan. 5, 2024, 7:14 p.m. UTC | #1
On Wed, Dec 13, 2023 at 11:14 AM Christian Göttsche
<cgzones@googlemail.com> wrote:
>
> 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@googlemail.com>

The SELinux parts look ok to me.
Reviewed-by: James Carter <jwcart2@gmail.com>

> ---
> 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
>
>
Christian Göttsche Jan. 17, 2024, 2:50 p.m. UTC | #2
On Fri, 5 Jan 2024 at 20:15, James Carter <jwcart2@gmail.com> wrote:
>
> On Wed, Dec 13, 2023 at 11:14 AM Christian Göttsche
> <cgzones@googlemail.com> wrote:
> >
> > 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@googlemail.com>
>
> The SELinux parts look ok to me.
> Reviewed-by: James Carter <jwcart2@gmail.com>

Thanks for the review Jim.
Applied via https://github.com/util-linux/util-linux/commit/eb02db62685cca30e5afc61652c8b6e9cd0774e9

>
> > ---
> > 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
> >
> >
diff mbox series

Patch

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;
 }