]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
sulogin: relabel terminal according to SELinux policy
authorChristian Göttsche <cgzones@googlemail.com>
Wed, 13 Dec 2023 15:53:20 +0000 (16:53 +0100)
committerKarel Zak <kzak@redhat.com>
Wed, 17 Jan 2024 08:56:38 +0000 (09:56 +0100)
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
Reviewed-by: James Carter <jwcart2@gmail.com>
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
login-utils/sulogin-consoles.c
login-utils/sulogin-consoles.h
login-utils/sulogin.c

index 9ae5255560b3b61920116db4364f7cee4e002f41..0dca949f439476b0d8ab0193a2998c72f6c2461d 100644 (file)
@@ -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;
 }
index 12032c997b4a0e47751f64843c801664a2dc9c49..608c4f84f09c76bcf6036cb036abe85f28987ed8 100644 (file)
@@ -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,
index 019f35092bfdd3ff5d4a6a69c5e0fc0aea16ef4d..2682c30fb6e212709c2e9de60d0435160fba0998 100644 (file)
@@ -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;
 }