summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2022-01-13 19:10:58 +0000
committerLaurent Bercot <ska@appnovation.com>2022-01-13 19:10:58 +0000
commit654ab33e97830a4eba4427407776f85abd6f95cf (patch)
tree9fa29c8a6c69e8e1ada9fe3d2008d22083d3e2d3
parent0840786b5826d81b447e4cd4292f0615b17a15ae (diff)
downloads6-linux-init-654ab33e97830a4eba4427407776f85abd6f95cf.tar.xz
Add satanic verses for ctty-passing in s6-l-i
Signed-off-by: Laurent Bercot <ska@appnovation.com>
-rw-r--r--src/init/s6-linux-init.c89
1 files changed, 82 insertions, 7 deletions
diff --git a/src/init/s6-linux-init.c b/src/init/s6-linux-init.c
index e88bccf..4168523 100644
--- a/src/init/s6-linux-init.c
+++ b/src/init/s6-linux-init.c
@@ -6,6 +6,7 @@
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
+#include <termios.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/reboot.h>
@@ -99,7 +100,6 @@ static inline void run_stage2 (char const *basedir, char const **argv, unsigned
for (unsigned int i = 0 ; i < argc ; i++)
childargv[i+2] = argv[i] ;
childargv[argc + 2] = 0 ;
- if (!inns) setsid() ;
if (nologger)
{
close(notifpipe[1]) ;
@@ -117,6 +117,82 @@ static inline void run_stage2 (char const *basedir, char const **argv, unsigned
xmexec_fm(childargv, envp, envlen, modifs, modiflen) ;
}
+/*
+ This is ugly voodoo, keep away from innocent eyes.
+ If stdin is a terminal, it means we're in a "docker run -it" container
+(or equivalent) and stdin is a ctty. We don't want the supervision tree to
+have this ctty (else ^C kills everything) but we want stage 2 to keep it,
+so that if we have a CMD run by stage 2 (as is the case with s6-overlay),
+it remains interactive (control sequences can be sent to it).
+ In order to achieve that, we have the child (future stage 2) steal the
+ctty from the parent (future s6-svscan). But that may not work, for instance
+in a USER container, where we don't have the appropriate permissions; in
+which case the parent must be informed that the operation failed, so it can
+relinquish the ctty itself - the child won't get the ctty but at least the
+parent won't keep it.
+ Transmission of the information is done via a pipe, it's the simplest
+way to properly order operations.
+ If the parent abandons its ctty itself, the child needs to protect against
+the SIGHUP it receives when it happens.
+*/
+
+static inline pid_t fork_and_setup_session (int ttyfd) /* closes ttyfd */
+{
+ pid_t pid ;
+ if (ttyfd < 0) /* the common case is simple */
+ {
+ pid = fork() ;
+ if (pid == -1) strerr_diefu1sys(111, "fork") ;
+ setsid() ;
+ }
+ else /* let the insanity begin */
+ {
+ int p[2] ;
+ if (pipe(p) == -1) strerr_diefu1sys(111, "pipe") ;
+ pid = fork() ;
+ if (pid == -1) strerr_diefu1sys(111, "fork") ;
+ if (!pid)
+ { /* child */
+ PROG = "s6-linux-init (child)" ;
+ close(p[0]) ;
+ if (setsid() == -1) strerr_diefu1sys(111, "setsid") ;
+ if (fd_move(0, ttyfd) == -1) strerr_diefu1sys(111, "move stdin fd") ;
+ if (ioctl(0, TIOCSCTTY, 1) == -1)
+ {
+ strerr_warnwu1sys("grab controlling terminal") ;
+ if (!sig_altignore(SIGHUP))
+ strerr_diefu1sys(111, "ignore SIGHUP") ;
+ if (write(p[1], "1", 1) < 1) /* '1' == problem */
+ strerr_diefu1sys(111, "write to parent") ;
+ }
+ else if (write(p[1], "0", 1) < 1) /* '0' == ok */
+ strerr_diefu1sys(111, "write to parent") ;
+ close(p[1]) ;
+ }
+ else
+ { /* parent */
+ int r ;
+ char c ;
+ close(p[1]) ;
+ r = read(p[0], &c, 1) ;
+ if (r == -1)
+ strerr_diefu1sys(111, "read from child") ;
+ else if (!r)
+ strerr_dief1x(111, "child died") ;
+ else if (c == '1')
+ {
+ if (ioctl(ttyfd, TIOCNOTTY) == -1)
+ strerr_warnwu1sys("relinquish controlling terminal") ;
+ }
+ else if (c != '0')
+ strerr_dief1x(100, "bad parent/child protocol") ;
+ close(p[0]) ;
+ close(ttyfd) ;
+ }
+ }
+ return pid ;
+}
+
int main (int argc, char const **argv, char const *const *envp)
{
mode_t mask = 0022 ;
@@ -127,6 +203,7 @@ int main (int argc, char const **argv, char const *const *envp)
char const *initdefault = "default" ;
int mounttype = 1 ;
int hasconsole = 1 ;
+ int ttyfd = -1 ;
stralloc envmodifs = STRALLOC_ZERO ;
PROG = "s6-linux-init" ;
@@ -162,7 +239,7 @@ int main (int argc, char const **argv, char const *const *envp)
if (fcntl(1, F_GETFD) < 0) hasconsole = 0 ;
if (inns)
- { /* If there's a Docker synchronization pipe, wait on it */
+ {
char c ;
ssize_t r = read(3, &c, 1) ;
if (r < 0)
@@ -174,6 +251,7 @@ int main (int argc, char const **argv, char const *const *envp)
if (r) strerr_warnw1x("parent wrote to fd 3!") ;
close(3) ;
}
+ if (isatty(0) && !slashdev) ttyfd = dup(0) ;
}
else if (hasconsole) allwrite(1, BANNER, sizeof(BANNER) - 1) ;
if (chdir("/") == -1) strerr_diefu1sys(111, "chdir to /") ;
@@ -256,7 +334,6 @@ int main (int argc, char const **argv, char const *const *envp)
{
char const *newenvp[2] = { 0, 0 } ;
size_t pathlen = path ? strlen(path) : 0 ;
- pid_t pid ;
char fmtfd[2 + UINT_FMT] = "-" ;
char const *newargv[5] = { S6_EXTBINPREFIX "s6-svscan", fmtfd, "--", S6_LINUX_INIT_TMPFS "/" SCANDIR, 0 } ;
char pathvar[6 + pathlen] ;
@@ -269,10 +346,8 @@ int main (int argc, char const **argv, char const *const *envp)
newenvp[0] = pathvar ;
}
if (nologger && pipe(notifpipe) < 0) strerr_diefu1sys(111, "pipe") ;
- pid = fork() ;
- if (pid == -1) strerr_diefu1sys(111, "fork") ;
- if (!pid) run_stage2(basedir, argv, argc, newenvp, !!path, envmodifs.s, envmodifs.len, initdefault) ;
- setsid() ;
+ if (!fork_and_setup_session(ttyfd))
+ run_stage2(basedir, argv, argc, newenvp, !!path, envmodifs.s, envmodifs.len, initdefault) ;
if (nologger)
{
close(notifpipe[0]) ;