Merge pull request #9961 from fbuihuu/logind-fix-vt-reinit-race

Logind fix vt reinit race
This commit is contained in:
Lennart Poettering 2018-11-21 17:28:23 +01:00 committed by GitHub
commit 9d52a6e5a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 27 deletions

View file

@ -1271,3 +1271,53 @@ int vt_reset_keyboard(int fd) {
return 0;
}
int vt_restore(int fd) {
static const struct vt_mode mode = {
.mode = VT_AUTO,
};
int r, q = 0;
r = ioctl(fd, KDSETMODE, KD_TEXT);
if (r < 0)
q = log_debug_errno(errno, "Failed to set VT in text mode, ignoring: %m");
r = vt_reset_keyboard(fd);
if (r < 0) {
log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m");
if (q >= 0)
q = r;
}
r = ioctl(fd, VT_SETMODE, &mode);
if (r < 0) {
log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m");
if (q >= 0)
q = -errno;
}
r = fchown(fd, 0, (gid_t) -1);
if (r < 0) {
log_debug_errno(errno, "Failed to chown VT, ignoring: %m");
if (q >= 0)
q = -errno;
}
return q;
}
int vt_release(int fd, bool restore) {
assert(fd >= 0);
/* This function releases the VT by acknowledging the VT-switch signal
* sent by the kernel and optionally reset the VT in text and auto
* VT-switching modes. */
if (ioctl(fd, VT_RELDISP, 1) < 0)
return -errno;
if (restore)
return vt_restore(fd);
return 0;
}

View file

@ -154,3 +154,5 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode);
int vt_default_utf8(void);
int vt_reset_keyboard(int fd);
int vt_restore(int fd);
int vt_release(int fd, bool restore_vt);

View file

@ -36,6 +36,7 @@
#define RELEASE_USEC (20*USEC_PER_SEC)
static void session_remove_fifo(Session *s);
static void session_restore_vt(Session *s);
int session_new(Session **ret, Manager *m, const char *id) {
_cleanup_(session_freep) Session *s = NULL;
@ -1223,35 +1224,55 @@ error:
return r;
}
void session_restore_vt(Session *s) {
static void session_restore_vt(Session *s) {
pid_t pid;
int r;
static const struct vt_mode mode = {
.mode = VT_AUTO,
};
int vt, old_fd;
/* We need to get a fresh handle to the virtual terminal,
* since the old file-descriptor is potentially in a hung-up
* state after the controlling process exited; we do a
* little dance to avoid having the terminal be available
* for reuse before we've cleaned it up.
*/
old_fd = TAKE_FD(s->vtfd);
vt = session_open_vt(s);
safe_close(old_fd);
if (vt < 0)
if (s->vtnr < 1)
return;
(void) ioctl(vt, KDSETMODE, KD_TEXT);
if (s->vtfd < 0)
return;
(void) vt_reset_keyboard(vt);
/* The virtual terminal can potentially be entering in hung-up state at any time
* depending on when the controlling process exits.
*
* If the controlling process exits while we're restoring the virtual terminal,
* the VT will enter in hung-up state and we'll fail at restoring it. To prevent
* this case, we kick off the current controlling process (if any) in a child
* process so logind doesn't play around with tty ownership.
*
* If the controlling process already exited, getting a fresh handle to the
* virtual terminal reset the hung-up state. */
r = safe_fork("(logind)", FORK_REOPEN_LOG|FORK_CLOSE_ALL_FDS|FORK_RESET_SIGNALS|FORK_WAIT|FORK_LOG, &pid);
if (r == 0) {
char path[sizeof("/dev/tty") + DECIMAL_STR_MAX(s->vtnr)];
int vt;
(void) ioctl(vt, VT_SETMODE, &mode);
(void) fchown(vt, 0, (gid_t) -1);
/* We must be a session leader in order to become the controlling process. */
pid = setsid();
if (pid < 0) {
log_error_errno(errno, "Failed to become session leader: %m");
_exit(EXIT_FAILURE);
}
sprintf(path, "/dev/tty%u", s->vtnr);
vt = acquire_terminal(path, ACQUIRE_TERMINAL_FORCE, USEC_INFINITY);
if (vt < 0) {
log_error_errno(vt, "Cannot acquire VT %s of session %s: %m", path, s->id);
_exit(EXIT_FAILURE);
}
r = vt_restore(vt);
if (r < 0)
log_warning_errno(r, "Failed to restore VT, ignoring: %m");
/* Give up and release the controlling terminal. */
safe_close(vt);
_exit(EXIT_SUCCESS);
}
/* Close the fd in any cases. */
s->vtfd = safe_close(s->vtfd);
}
@ -1275,9 +1296,9 @@ void session_leave_vt(Session *s) {
return;
session_device_pause_all(s);
r = ioctl(s->vtfd, VT_RELDISP, 1);
r = vt_release(s->vtfd, false);
if (r < 0)
log_debug_errno(errno, "Cannot release VT of session %s: %m", s->id);
log_debug_errno(r, "Cannot release VT of session %s: %m", s->id);
}
bool session_is_controller(Session *s, const char *sender) {

View file

@ -173,7 +173,6 @@ const char* tty_validity_to_string(TTYValidity t) _const_;
TTYValidity tty_validity_from_string(const char *s) _pure_;
int session_prepare_vt(Session *s);
void session_restore_vt(Session *s);
void session_leave_vt(Session *s);
bool session_is_controller(Session *s, const char *sender);

View file

@ -25,6 +25,7 @@
#include "selinux-util.h"
#include "signal-util.h"
#include "strv.h"
#include "terminal-util.h"
static Manager* manager_unref(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
@ -747,7 +748,29 @@ static int manager_vt_switch(sd_event_source *src, const struct signalfd_siginfo
active = m->seat0->active;
if (!active || active->vtnr < 1) {
log_warning("Received VT_PROCESS signal without a registered session on that VT.");
_cleanup_close_ int fd = -1;
int r;
/* We are requested to acknowledge the VT-switch signal by the kernel but
* there's no registered sessions for the current VT. Normally this
* shouldn't happen but something wrong might have happened when we tried
* to release the VT. Better be safe than sorry, and try to release the VT
* one more time otherwise the user will be locked with the current VT. */
log_warning("Received VT_PROCESS signal without a registered session, restoring VT.");
/* At this point we only have the kernel mapping for referring to the
* current VT. */
fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
if (fd < 0) {
log_warning_errno(fd, "Failed to open, ignoring: %m");
return 0;
}
r = vt_release(fd, true);
if (r < 0)
log_warning_errno(r, "Failed to release VT, ignoring: %m");
return 0;
}