Ensure Tini doesn't lock up on of tty ownership

In the rare scenario where Tini is not running as PID 1 and its
child (or something else) sets TOSTOP on the tty, then Tini would
hang when if it tries to write debug messages. This commit fixes
this problem by ignoring SIGTTOU.

In the also rare scenario where two Tini instances are running, the
tty-passing could end up being done improperly (if the parent Tini
instance passes the tty to the child before the grandparent Tini
instance passes it to the parent Tini instance), and result in the
parent Tini instance running in the foreground. This commit fixes
this problem by passing the tty in the child (which we can do
because we are ignoring SIGTTOU).
pull/21/head
Thomas Orozco 9 years ago
parent 82acbc5ccc
commit 88342efc3c
  1. 132
      src/tini.c

@ -24,6 +24,12 @@
#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
typedef struct {
sigset_t* const sigmask_ptr;
struct sigaction* const sigttin_action_ptr;
struct sigaction* const sigttou_action_ptr;
} signal_configuration_t;
#ifdef PR_SET_CHILD_SUBREAPER
#define HAS_SUBREAPER 1
@ -55,24 +61,71 @@ static const char reaper_warning[] = "Tini is not running as PID 1 "
#endif
"run Tini as PID 1.";
int restore_signals(const signal_configuration_t* const sigconf_ptr) {
if (sigprocmask(SIG_SETMASK, sigconf_ptr->sigmask_ptr, NULL)) {
PRINT_FATAL("Restoring child signal mask failed: '%s'", strerror(errno));
return 1;
}
if (sigaction(SIGTTIN, sigconf_ptr->sigttin_action_ptr, NULL)) {
PRINT_FATAL("Restoring SIGTTIN handler failed: '%s'", strerror((errno)));
return 1;
}
if (sigaction(SIGTTOU, sigconf_ptr->sigttou_action_ptr, NULL)) {
PRINT_FATAL("Restoring SIGTTOU handler failed: '%s'", strerror((errno)));
return 1;
}
return 0;
}
int isolate_child() {
// Put the child into a new process group.
if (setpgid(0, 0) < 0) {
PRINT_FATAL("setpgid failed: '%s'", strerror(errno));
return 1;
}
int spawn(const sigset_t* const child_sigset_ptr, char* const argv[], int* const child_pid_ptr) {
// If there is a tty, allocate it to this new process group. We
// can do this in the child process because we're blocking
// SIGTTIN / SIGTTOU.
// Doing it in the child process avoids a race condition scenario
// if Tini is calling Tini (in which case the grandparent may make the
// parent the foreground process group, and the actual child ends up...
// in the background!)
if (tcsetpgrp(STDIN_FILENO, getpgrp())) {
if (errno == ENOTTY) {
PRINT_DEBUG("tcsetpgrp failed: no tty (ok to proceed)")
} else {
PRINT_FATAL("tcsetpgrp failed: '%s'", strerror(errno));
return 1;
}
}
return 0;
}
int spawn(const signal_configuration_t* const sigconf_ptr, char* const argv[], int* const child_pid_ptr) {
pid_t pid;
// TODO: check if tini was a foreground process to begin with (it's not OK to "steal" the foreground!")
pid = fork();
if (pid < 0) {
PRINT_FATAL("Fork failed: '%s'", strerror(errno));
return 1;
} else if (pid == 0) {
// Child
if (sigprocmask(SIG_SETMASK, child_sigset_ptr, NULL)) {
PRINT_FATAL("Setting child signal mask failed: '%s'", strerror(errno));
// Put the child in a process group and make it the foreground process if there is a tty.
if (isolate_child()) {
return 1;
}
// Put the child into a new process group
if (setpgid(0, 0) < 0) {
PRINT_FATAL("setpgid failed: '%s'", strerror(errno));
// Restore all signal handlers to the way they were before we touched them.
if (restore_signals(sigconf_ptr)) {
return 1;
}
@ -83,17 +136,6 @@ int spawn(const sigset_t* const child_sigset_ptr, char* const argv[], int* const
// Parent
PRINT_INFO("Spawned child process '%s' with pid '%i'", argv[0], pid);
*child_pid_ptr = pid;
// If there is a tty, pass control over to the child process group
if (tcsetpgrp(STDIN_FILENO, pid)) {
if (errno == ENOTTY) {
PRINT_DEBUG("tcsetpgrp failed: no tty (ok to proceed)")
} else {
PRINT_FATAL("tcsetpgrp failed: '%s'", strerror(errno));
return 1;
}
}
return 0;
}
}
@ -218,27 +260,48 @@ void reaper_check () {
}
int prepare_sigmask(sigset_t* const parent_sigset_ptr, sigset_t* const child_sigset_ptr) {
/* Prepare signals to block; make sure we don't block program error signals. */
int configure_signals(sigset_t* const parent_sigset_ptr, const signal_configuration_t* const sigconf_ptr) {
/* Block all signals that are meant to be collected by the main loop */
if (sigfillset(parent_sigset_ptr)) {
PRINT_FATAL("sigfillset failed: '%s'", strerror(errno));
return 1;
}
// These ones shouldn't be collected by the main loop
uint i;
int ignore_signals[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS} ;
for (i = 0; i < ARRAY_LEN(ignore_signals); i++) {
if (sigdelset(parent_sigset_ptr, ignore_signals[i])) {
PRINT_FATAL("sigdelset failed: '%i'", ignore_signals[i]);
int signals_for_tini[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS, SIGTTIN, SIGTTOU};
for (i = 0; i < ARRAY_LEN(signals_for_tini); i++) {
if (sigdelset(parent_sigset_ptr, signals_for_tini[i])) {
PRINT_FATAL("sigdelset failed: '%i'", signals_for_tini[i]);
return 1;
}
}
if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, child_sigset_ptr)) {
if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, sigconf_ptr->sigmask_ptr)) {
PRINT_FATAL("sigprocmask failed: '%s'", strerror(errno));
return 1;
}
// Handle SIGTTIN and SIGTTOU separately. Since Tini makes the child process group
// the foreground process group, there's a chance Tini can end up not controlling the tty.
// If TOSTOP is set on the tty, this could block Tini on writing debug messages. We don't
// want that. Ignore those signals.
struct sigaction ign_action;
memset(&ign_action, 0, sizeof ign_action);
ign_action.sa_handler = SIG_IGN;
sigemptyset(&ign_action.sa_mask);
if (sigaction(SIGTTIN, &ign_action, sigconf_ptr->sigttin_action_ptr)) {
PRINT_FATAL("Failed to ignore SIGTTIN");
return 1;
}
if (sigaction(SIGTTOU, &ign_action, sigconf_ptr->sigttou_action_ptr)) {
PRINT_FATAL("Failed to ignore SIGTTOU");
return 1;
}
return 0;
}
@ -356,10 +419,19 @@ int main(int argc, char *argv[]) {
return 1;
}
/* Prepare sigmask */
sigset_t parent_sigset;
sigset_t child_sigset;
if (prepare_sigmask(&parent_sigset, &child_sigset)) {
/* Configure signals */
sigset_t parent_sigset, child_sigset;
struct sigaction sigttin_action, sigttou_action;
memset(&sigttin_action, 0, sizeof sigttin_action);
memset(&sigttou_action, 0, sizeof sigttou_action);
signal_configuration_t child_sigconf = {
.sigmask_ptr = &child_sigset,
.sigttin_action_ptr = &sigttin_action,
.sigttou_action_ptr = &sigttou_action,
};
if (configure_signals(&parent_sigset, &child_sigconf)) {
return 1;
}
@ -374,7 +446,7 @@ int main(int argc, char *argv[]) {
reaper_check();
/* Go on */
if (spawn(&child_sigset, *child_args_ptr, &child_pid)) {
if (spawn(&child_sigconf, *child_args_ptr, &child_pid)) {
return 1;
}
free(child_args_ptr);

Loading…
Cancel
Save