From 753da4320f01ec5486371eddc4c98f50d86dbef9 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Wed, 25 Sep 2019 14:51:40 +0200 Subject: [PATCH 4/7] gdm-session-worker: Drop login_vt assuming it is GDM_INITIAL_VT When a session ends, its "session worker" is closed. Since 3e8220921bb608afd06ed677104fd2244b901a28 (3.33.4), we uninitialise PAM when this happens. As part of this procedure, we jump back to the login screen, if the screen being killed is not itself the login screen. This has broken fast user switching. It goes like this - this explanation is a bit complicated, bear with us: We want to jump back to the login screen when a normal user session ends, so that people can log in again. We do not want to do this when a login screen itself ends. When session workers start up, they query for the *currently active VT* and save this in `login_vt`. Then later on, we check if our session ID is the same as `login_vt`, and jump to `login_vt` if they are different - this means that it was a user session not a login session. Querying the currently active VT is fine for the first greeter, but when initiating a user switch it's wrong as this gives the user VT. GDM greeters are killed once they have spawned a session. They are associated with a logind session, and therefore a PAM session. There are some actions performed when unregistering PAM sessions, including the previously mentioned VT jump. Before 3e8220921bb608afd06ed677104fd2244b901a28 we only uninitialised PAM when the session itself exited so the bug was masked, but now (since this commit), if the login screen's *worker* exits first - as happens in the normal case when GDM kills it - we also do this uninitialisation. Since we falsely recorded the login screen as the first user's VT, this means that checking `login_vt != session_vt` returns `TRUE` and we jump back to the previous user's session immediately after logging into the new session: fast user switching is broken. Since the work on shutting down the GDM session has been finished, we can assume that the login_vt is always on GDM_INITIAL_VT (see example c71bc5d6c3bc2ec448b5c72ce9a811d9c0c7905e "local-display-factory: Remove initial VT is in use check" and 39fb4ff64e6a0653e70a3bfab31da47b49227d59 "manager: don't run autologin display on tty1"). So simply replace all usages of login_vt with GDM_INITIAL_VT to solve the above problem. Note that in the case where ENABLE_USER_DISPLAY_SERVER is not enabled, the login_vt is always the same as the session_vt. We can simply remove the VT switching magic there and everything should be working as expected. This is a simpler version of the patch by Iain Lane , taking into account that we can make the assumption about the login_vt. Closes #515 --- daemon/gdm-session-worker.c | 47 ++++++++++--------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/daemon/gdm-session-worker.c b/daemon/gdm-session-worker.c index f9bc82177..07117d857 100644 --- a/daemon/gdm-session-worker.c +++ b/daemon/gdm-session-worker.c @@ -119,61 +119,60 @@ typedef struct } ReauthenticationRequest; struct GdmSessionWorkerPrivate { GdmSessionWorkerState state; int exit_code; pam_handle_t *pam_handle; GPid child_pid; guint child_watch_id; /* from Setup */ char *service; char *x11_display_name; char *x11_authority_file; char *display_device; char *display_seat_id; char *hostname; char *username; char *log_file; char *session_id; uid_t uid; gid_t gid; gboolean password_is_required; char **extensions; int cred_flags; - int login_vt; int session_vt; int session_tty_fd; char **arguments; guint32 cancelled : 1; guint32 timed_out : 1; guint32 is_program_session : 1; guint32 is_reauth_session : 1; guint32 display_is_local : 1; guint32 display_is_initial : 1; guint state_change_idle_id; GdmSessionDisplayMode display_mode; char *server_address; GDBusConnection *connection; GdmDBusWorkerManager *manager; GHashTable *reauthentication_requests; GdmSessionAuditor *auditor; GdmSessionSettings *user_settings; GDBusMethodInvocation *pending_invocation; }; #ifdef SUPPORTS_PAM_EXTENSIONS static char gdm_pam_extension_environment_block[_POSIX_ARG_MAX]; static const char * const gdm_supported_pam_extensions[] = { @@ -1007,157 +1006,137 @@ gdm_session_worker_set_state (GdmSessionWorker *worker, static void gdm_session_worker_uninitialize_pam (GdmSessionWorker *worker, int status) { g_debug ("GdmSessionWorker: uninitializing PAM"); if (worker->priv->pam_handle == NULL) return; gdm_session_worker_get_username (worker, NULL); if (worker->priv->state >= GDM_SESSION_WORKER_STATE_SESSION_OPENED) { pam_close_session (worker->priv->pam_handle, 0); gdm_session_auditor_report_logout (worker->priv->auditor); } else { gdm_session_auditor_report_login_failure (worker->priv->auditor, status, pam_strerror (worker->priv->pam_handle, status)); } if (worker->priv->state >= GDM_SESSION_WORKER_STATE_ACCREDITED) { pam_setcred (worker->priv->pam_handle, PAM_DELETE_CRED); } pam_end (worker->priv->pam_handle, status); worker->priv->pam_handle = NULL; gdm_session_worker_stop_auditor (worker); + /* If user-display-server is not enabled the login_vt is always + * identical to the session_vt. So in that case we never need to + * do a VT switch. */ +#ifdef ENABLE_USER_DISPLAY_SERVER if (g_strcmp0 (worker->priv->display_seat_id, "seat0") == 0) { - if (worker->priv->login_vt != worker->priv->session_vt) { - jump_to_vt (worker, worker->priv->login_vt); + int initial_vt = atoi (GDM_INITIAL_VT); + + /* Switch to the login VT if we are not the login screen. */ + if (worker->priv->session_vt != initial_vt) { + jump_to_vt (worker, initial_vt); } } +#endif - worker->priv->login_vt = 0; worker->priv->session_vt = 0; g_debug ("GdmSessionWorker: state NONE"); gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_NONE); } static char * _get_tty_for_pam (const char *x11_display_name, const char *display_device) { #ifdef __sun return g_strdup (display_device); #else return g_strdup (x11_display_name); #endif } #ifdef PAM_XAUTHDATA static struct pam_xauth_data * _get_xauth_for_pam (const char *x11_authority_file) { FILE *fh; Xauth *auth = NULL; struct pam_xauth_data *retval = NULL; gsize len = sizeof (*retval) + 1; fh = fopen (x11_authority_file, "r"); if (fh) { auth = XauReadAuth (fh); fclose (fh); } if (auth) { len += auth->name_length + auth->data_length; retval = g_malloc0 (len); } if (retval) { retval->namelen = auth->name_length; retval->name = (char *) (retval + 1); memcpy (retval->name, auth->name, auth->name_length); retval->datalen = auth->data_length; retval->data = retval->name + auth->name_length + 1; memcpy (retval->data, auth->data, auth->data_length); } XauDisposeAuth (auth); return retval; } #endif -static gboolean -ensure_login_vt (GdmSessionWorker *worker) -{ - int fd; - struct vt_stat vt_state = { 0 }; - gboolean got_login_vt = FALSE; - - fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); - - if (fd < 0) { - g_debug ("GdmSessionWorker: couldn't open VT master: %m"); - return FALSE; - } - - if (ioctl (fd, VT_GETSTATE, &vt_state) < 0) { - g_debug ("GdmSessionWorker: couldn't get current VT: %m"); - goto out; - } - - worker->priv->login_vt = vt_state.v_active; - got_login_vt = TRUE; -out: - close (fd); - return got_login_vt; -} - static gboolean gdm_session_worker_initialize_pam (GdmSessionWorker *worker, const char *service, const char * const *extensions, const char *username, const char *hostname, gboolean display_is_local, const char *x11_display_name, const char *x11_authority_file, const char *display_device, const char *seat_id, GError **error) { struct pam_conv pam_conversation; int error_code; - char tty_string[256]; g_assert (worker->priv->pam_handle == NULL); g_debug ("GdmSessionWorker: initializing PAM; service=%s username=%s seat=%s", service ? service : "(null)", username ? username : "(null)", seat_id ? seat_id : "(null)"); #ifdef SUPPORTS_PAM_EXTENSIONS if (extensions != NULL) { GDM_PAM_EXTENSION_ADVERTISE_SUPPORTED_EXTENSIONS (gdm_pam_extension_environment_block, extensions); } #endif pam_conversation.conv = (GdmSessionWorkerPamNewMessagesFunc) gdm_session_worker_pam_new_messages_handler; pam_conversation.appdata_ptr = worker; gdm_session_worker_start_auditor (worker); error_code = pam_start (service, username, &pam_conversation, &worker->priv->pam_handle); if (error_code != PAM_SUCCESS) { g_debug ("GdmSessionWorker: could not initialize PAM: (error code %d)", error_code); /* we don't use pam_strerror here because it requires a valid * pam handle, and if pam_start fails pam_handle is undefined */ g_set_error (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE, @@ -1182,65 +1161,63 @@ gdm_session_worker_initialize_pam (GdmSessionWorker *worker, } /* set RHOST */ if (hostname != NULL && hostname[0] != '\0') { error_code = pam_set_item (worker->priv->pam_handle, PAM_RHOST, hostname); g_debug ("error informing authentication system of user's hostname %s: %s", hostname, pam_strerror (worker->priv->pam_handle, error_code)); if (error_code != PAM_SUCCESS) { g_set_error (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_AUTHENTICATING, "%s", ""); goto out; } } /* set seat ID */ if (seat_id != NULL && seat_id[0] != '\0') { gdm_session_worker_set_environment_variable (worker, "XDG_SEAT", seat_id); } if (strcmp (service, "gdm-launch-environment") == 0) { gdm_session_worker_set_environment_variable (worker, "XDG_SESSION_CLASS", "greeter"); } g_debug ("GdmSessionWorker: state SETUP_COMPLETE"); gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE); - /* Temporarily set PAM_TTY with the currently active VT (login screen) + /* Temporarily set PAM_TTY with the login VT, PAM_TTY will be reset with the users VT right before the user session is opened */ - ensure_login_vt (worker); - g_snprintf (tty_string, 256, "/dev/tty%d", worker->priv->login_vt); - pam_set_item (worker->priv->pam_handle, PAM_TTY, tty_string); + pam_set_item (worker->priv->pam_handle, PAM_TTY, "/dev/tty" GDM_INITIAL_VT); if (!display_is_local) worker->priv->password_is_required = TRUE; out: if (error_code != PAM_SUCCESS) { gdm_session_worker_uninitialize_pam (worker, error_code); return FALSE; } return TRUE; } static gboolean gdm_session_worker_authenticate_user (GdmSessionWorker *worker, gboolean password_is_required, GError **error) { int error_code; int authentication_flags; g_debug ("GdmSessionWorker: authenticating user %s", worker->priv->username); authentication_flags = 0; if (password_is_required) { authentication_flags |= PAM_DISALLOW_NULL_AUTHTOK; } /* blocking call, does the actual conversation */ error_code = pam_authenticate (worker->priv->pam_handle, authentication_flags); -- 2.21.0