From e03c713294a94fbd9b175c4f7b99607064e43529 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Jan 2018 00:24:15 +0530 Subject: [PATCH] Remove a few more signal unsafe function calls between fork() and exec() --- kitty/child.c | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/kitty/child.c b/kitty/child.c index d5a84c975..051f3977b 100644 --- a/kitty/child.c +++ b/kitty/child.c @@ -23,54 +23,73 @@ serialize_string_tuple(PyObject *src) { extern char **environ; +static inline void +write_to_stderr(const char *text) { + size_t sz = strlen(text); + size_t written = 0; + while(written < sz) { + ssize_t amt = write(2, text + written, sz - written); + if (amt == 0) break; + if (amt < 0) { + if (errno == EAGAIN || errno == EINTR) continue; + break; + } + written += amt; + } +} + static PyObject* spawn(PyObject *self UNUSED, PyObject *args) { PyObject *argv_p, *env_p; int master, slave, stdin_read_fd, stdin_write_fd; char* cwd; if (!PyArg_ParseTuple(args, "sO!O!iiii", &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL; + char name[2048] = {0}; + if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } char **argv = serialize_string_tuple(argv_p); char **env = serialize_string_tuple(env_p); +#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); } pid_t pid = fork(); if (pid == 0) { // child - // We cannot use malloc before exec() as it might deadlock if a thread in the parent process is in the middle of a malloc itself + // Use only signal-safe functions (man 7 signal-safety) if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to / - if (setsid() == -1) { perror("setsid() in child process failed"); exit(EXIT_FAILURE); } - if (dup2(slave, 1) == -1) { perror("dup2() failed for fd number 1"); exit(EXIT_FAILURE); } - if (dup2(slave, 2) == -1) { perror("dup2() failed for fd number 2"); exit(EXIT_FAILURE); } + if (setsid() == -1) exit_on_err("setsid() in child process failed"); + if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1"); + if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2"); if (stdin_read_fd > -1) { - if (dup2(stdin_read_fd, 0) == -1) { perror("dup2() failed for fd number 0"); exit(EXIT_FAILURE); } + if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0"); close(stdin_read_fd); close(stdin_write_fd); } else { - if (dup2(slave, 0) == -1) { perror("dup2() failed for fd number 0"); exit(EXIT_FAILURE); } + if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0"); } close(slave); close(master); for (int c = 3; c < 201; c++) close(c); // Establish the controlling terminal (see man 7 credentials) - char *name = ttyname(1); - if (name == NULL) { perror("Failed to call ttyname()"); exit(EXIT_FAILURE); } int tfd = open(name, O_RDWR); - if (tfd == -1) { perror("Failed to open controlling terminal"); exit(EXIT_FAILURE); } + if (tfd == -1) exit_on_err("Failed to open controlling terminal"); close(tfd); environ = env; execvp(argv[0], argv); // Report the failure and exec a shell instead, so that we are not left - // with a forked but not execed process - fprintf(stderr, "Failed to launch child: %s\nWith error: %s [%d]\n", argv[0], strerror(errno), errno); - fprintf(stderr, "Press Enter to exit.\n"); - fflush(stderr); + // with a forked but not exec'ed process + write_to_stderr("Failed to launch child: "); + write_to_stderr(argv[0]); + write_to_stderr("\nWith error: "); + write_to_stderr(strerror(errno)); + write_to_stderr("\nPress Enter to exit.\n"); execlp("sh", "sh", "-c", "read w", NULL); exit(EXIT_FAILURE); } else { free(argv); free(env); } +#undef exit_on_err return PyLong_FromLong(pid); }