diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aa0815671..f17304c1d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -200,6 +200,7 @@ add_subdirectory(external/python/2.6/Python-2.6.9) add_subdirectory(external/python/2.7/Python-2.7.10) add_subdirectory(external/ruby) add_subdirectory(external/expat) +add_subdirectory(shellspawn) #add_subdirectory(external/libauto) add_subdirectory(external/libarchive/libarchive) add_subdirectory(external/apr) diff --git a/src/shellspawn/CMakeLists.txt b/src/shellspawn/CMakeLists.txt new file mode 100644 index 000000000..fc10bc6ab --- /dev/null +++ b/src/shellspawn/CMakeLists.txt @@ -0,0 +1,11 @@ +project(shellspawn) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -nostdinc") + +include(darling_exe) + +add_darling_executable(shellspawn shellspawn.c duct_signals.c) + +install(TARGETS shellspawn DESTINATION libexec/darling/usr/libexec) +install(FILES org.darlinghq.shellspawn.plist DESTINATION libexec/darling/System/Library/LaunchDaemons) + diff --git a/src/shellspawn/duct_signals.c b/src/shellspawn/duct_signals.c new file mode 100644 index 000000000..413442ff2 --- /dev/null +++ b/src/shellspawn/duct_signals.c @@ -0,0 +1,71 @@ +#include +#include "duct_signals.h" + +int signum_linux_to_bsd(int signum) +{ + switch (signum) + { + case LINUX_SIGHUP: + return SIGHUP; + case LINUX_SIGINT: + return SIGINT; + case LINUX_SIGQUIT: + return SIGQUIT; + case LINUX_SIGILL: + return SIGILL; + case LINUX_SIGTRAP: + return SIGTRAP; + case LINUX_SIGABRT: + return SIGABRT; + case LINUX_SIGFPE: + return SIGFPE; + case LINUX_SIGKILL: + return SIGKILL; + case LINUX_SIGBUS: + return SIGBUS; + case LINUX_SIGSEGV: + return SIGSEGV; + case LINUX_SIGSYS: + return SIGSYS; + case LINUX_SIGPIPE: + return SIGPIPE; + case LINUX_SIGALRM: + return SIGALRM; + case LINUX_SIGTERM: + return SIGTERM; + case LINUX_SIGURG: + return SIGURG; + case LINUX_SIGSTOP: + return SIGSTOP; + case LINUX_SIGTSTP: + return SIGTSTP; + case LINUX_SIGCONT: + return SIGCONT; + case LINUX_SIGCHLD: + return SIGCHLD; + case LINUX_SIGTTIN: + return SIGTTIN; + case LINUX_SIGTTOU: + return SIGTTOU; + case LINUX_SIGIO: + return SIGIO; + case LINUX_SIGXCPU: + return SIGXCPU; + case LINUX_SIGXFSZ: + return SIGXFSZ; + case LINUX_SIGVTALRM: + return SIGVTALRM; + case LINUX_SIGPROF: + return SIGPROF; + case LINUX_SIGWINCH: + return SIGWINCH; + case LINUX_SIGUSR1: + return SIGUSR1; + case LINUX_SIGUSR2: + return SIGUSR2; + default: + return 0; + } +} + + diff --git a/src/shellspawn/duct_signals.h b/src/shellspawn/duct_signals.h new file mode 100644 index 000000000..ed0a0163d --- /dev/null +++ b/src/shellspawn/duct_signals.h @@ -0,0 +1,54 @@ +#ifndef LINUX_DUCT_SIGNALS_H +#define LINUX_DUCT_SIGNALS_H + +#define LINUX_NSIG 32 +#define LINUX_SIGHUP 1 +#define LINUX_SIGINT 2 +#define LINUX_SIGQUIT 3 +#define LINUX_SIGILL 4 +#define LINUX_SIGTRAP 5 +#define LINUX_SIGABRT 6 +#define LINUX_SIGIOT 6 +#define LINUX_SIGBUS 7 +#define LINUX_SIGFPE 8 +#define LINUX_SIGKILL 9 +#define LINUX_SIGUSR1 10 +#define LINUX_SIGSEGV 11 +#define LINUX_SIGUSR2 12 +#define LINUX_SIGPIPE 13 +#define LINUX_SIGALRM 14 +#define LINUX_SIGTERM 15 +#define LINUX_SIGSTKFLT 16 +#define LINUX_SIGCHLD 17 +#define LINUX_SIGCONT 18 +#define LINUX_SIGSTOP 19 +#define LINUX_SIGTSTP 20 +#define LINUX_SIGTTIN 21 +#define LINUX_SIGTTOU 22 +#define LINUX_SIGURG 23 +#define LINUX_SIGXCPU 24 +#define LINUX_SIGXFSZ 25 +#define LINUX_SIGVTALRM 26 +#define LINUX_SIGPROF 27 +#define LINUX_SIGWINCH 28 +#define LINUX_SIGIO 29 +#define LINUX_SIGLOST 29 +#define LINUX_SIGPWR 30 +#define LINUX_SIGSYS 31 +#define LINUX_SIGUNUSED 31 +#define LINUX_SIGRTMIN 32 +#define LINUX_SA_NOCLDSTOP 0x00000001u +#define LINUX_SA_NOCLDWAIT 0x00000002u +#define LINUX_SA_SIGINFO 0x00000004u +#define LINUX_SA_ONSTACK 0x08000000u +#define LINUX_SA_RESTART 0x10000000u +#define LINUX_SA_NODEFER 0x40000000u +#define LINUX_SA_RESETHAND 0x80000000u +#define LINUX_SA_RESTORER 0x04000000 +#define LINUX_MINSIGSTKSZ 2048 +#define LINUX_SIGSTKSZ 8192 + +int signum_linux_to_bsd(int signum); + +#endif + diff --git a/src/shellspawn/org.darlinghq.shellspawn.plist b/src/shellspawn/org.darlinghq.shellspawn.plist new file mode 100644 index 000000000..ed4c443ea --- /dev/null +++ b/src/shellspawn/org.darlinghq.shellspawn.plist @@ -0,0 +1,17 @@ + + + + + Label + org.darlinghq.shellspawn + ProgramArguments + + /usr/libexec/shellspawn + + RunAtLoad + + KeepAlive + + + diff --git a/src/shellspawn/shellspawn b/src/shellspawn/shellspawn new file mode 100755 index 0000000000000000000000000000000000000000..f2b8c0ebf4355ed76d869a26cd077e82c03d37f2 GIT binary patch literal 14448 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 literal 0 HcmV?d00001 diff --git a/src/shellspawn/shellspawn.c b/src/shellspawn/shellspawn.c new file mode 100644 index 000000000..a8fb881ae --- /dev/null +++ b/src/shellspawn/shellspawn.c @@ -0,0 +1,383 @@ +/* +This file is part of Darling. + +Copyright (C) 2017 Lubos Dolezel + +Darling is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Darling is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Darling. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "shellspawn.h" +#include "duct_signals.h" + +#define DBG 0 + +int g_serverSocket = -1; + +void setupSocket(void); +void listenForConnections(void); +void spawnShell(int fd); + +int main(int argc, const char** argv) +{ + setupSocket(); + listenForConnections(); + + if (g_serverSocket != -1) + close(g_serverSocket); + return 0; +} + +void setupSocket(void) +{ + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = SHELLSPAWN_SOCKPATH + }; + + g_serverSocket = socket(AF_UNIX, SOCK_STREAM, 0); + if (g_serverSocket == -1) + { + perror("Creating unix socket"); + exit(EXIT_FAILURE); + } + + fcntl(g_serverSocket, F_SETFD, FD_CLOEXEC); + unlink(SHELLSPAWN_SOCKPATH); + + if (bind(g_serverSocket, (struct sockaddr*) &addr, sizeof(addr)) == -1) + { + perror("Binding the unix socket"); + exit(EXIT_FAILURE); + } + + chmod(addr.sun_path, 0600); + + if (listen(g_serverSocket, 1) == -1) + { + perror("Listening on unix socket"); + exit(EXIT_FAILURE); + } +} + +void listenForConnections(void) +{ + int sock; + struct sockaddr_un addr; + socklen_t len = sizeof(addr); + + while (true) + { + sock = accept(g_serverSocket, (struct sockaddr*) &addr, &len); + if (sock == -1) + break; + + if (fork() == 0) + { + fcntl(sock, F_SETFD, FD_CLOEXEC); + spawnShell(sock); + exit(EXIT_SUCCESS); + } + else + { + close(sock); + } + } +} + +void spawnShell(int fd) +{ + pid_t shell_pid = -1; + int shellfd[3] = { -1, -1, -1 }; + int pipefd[2]; + int rv; + struct pollfd pfd[2]; + char** argv = NULL; + int argc = 1; + struct msghdr msg; + struct iovec iov; + char cmsgbuf[CMSG_SPACE(sizeof(int)) * 3]; + int kq; + + bool read_cmds = true; + + argv = (char**) malloc(sizeof(char*) * 2); + argv[0] = "/bin/bash"; + + // Read commands from client + while (read_cmds) + { + struct shellspawn_cmd cmd; + char* param = NULL; + + memset(&msg, 0, sizeof(msg)); + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + iov.iov_base = &cmd; + iov.iov_len = sizeof(cmd); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + if (recvmsg(fd, &msg, 0) != sizeof(cmd)) + { + if (DBG) puts("bad recvmsg"); + goto err; + } + + if (cmd.data_length != 0) + { + param = (char*) malloc(cmd.data_length + 1); + if (read(fd, param, cmd.data_length) != cmd.data_length) + goto err; + param[cmd.data_length] = '\0'; + } + + switch (cmd.cmd) + { + case SHELLSPAWN_ADDARG: + { + if (param != NULL) + { + argv = (char**) realloc(argv, sizeof(char*) * (argc + 1)); + argv[argc] = param; + if (DBG) printf("add arg: %s\n", param); + argc++; + } + break; + } + case SHELLSPAWN_SETENV: + { + if (param != NULL) + { + if (DBG) printf("set env: %s\n", param); + putenv(param); + free(param); + } + break; + } + case SHELLSPAWN_CHDIR: + { + if (param != NULL) + { + if (DBG) printf("chdir: %s\n", param); + chdir(param); + free(param); + } + break; + } + case SHELLSPAWN_GO: + { + struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); + + if (cmptr == NULL) + { + if (DBG) puts("bad cmptr"); + goto err; + } + if (cmptr->cmsg_level != SOL_SOCKET + || cmptr->cmsg_type != SCM_RIGHTS) + { + if (DBG) puts("bad cmsg level/type"); + goto err; + } + if (cmptr->cmsg_len != CMSG_LEN(sizeof(int) * 3)) + { + if (DBG) printf("bad cmsg_len: %d\n", cmptr->cmsg_len); + goto err; + } + + memcpy(shellfd, CMSG_DATA(cmptr), sizeof(int) * 3); + + if (DBG) printf("go, fds={ %d, %d, %d }\n", shellfd[0], shellfd[1], shellfd[2]); + free(param); + read_cmds = false; + break; + } + } + } + + // Add terminating NULL + argv = (char**) realloc(argv, sizeof(char*) * (argc + 1)); + argv[argc] = NULL; + + if (pipe(pipefd) == -1) + goto err; + + setsid(); + setpgrp(); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + dup2(shellfd[0], STDIN_FILENO); + dup2(shellfd[1], STDOUT_FILENO); + dup2(shellfd[2], STDERR_FILENO); + + ioctl(STDIN_FILENO, TIOCSCTTY, STDIN_FILENO); + + shell_pid = fork(); + if (shell_pid == 0) + { + close(fd); + + fcntl(pipefd[1], F_SETFD, FD_CLOEXEC); + + // In future, we may support spawning something else than Bash + // and check the provided shell against /etc/shells + execv("/bin/bash", argv); + + rv = errno; + write(pipefd[1], &rv, sizeof(rv)); + close(pipefd[1]); + + exit(EXIT_FAILURE); + } + + // Check that exec succeeded + close(pipefd[1]); // close the write end + if (read(pipefd[0], &rv, sizeof(rv)) == sizeof(rv)) + { + errno = rv; + goto err; + } + close(pipefd[0]); + + // Now we start passing signals + // and check for child process exit + + kq = kqueue(); + + { + struct kevent changes[2]; + EV_SET(&changes[0], fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL); + EV_SET(&changes[1], shell_pid, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, 0, NULL); + + if (kevent(kq, changes, 2, NULL, 0, NULL) == -1) + goto err; + } + + while (true) + { + struct kevent ev; + + if (kevent(kq, NULL, 0, &ev, 1, NULL) <= 0) + { + if (DBG) puts("kevent fail"); + goto err; + } + + if (ev.filter == EVFILT_PROC && (ev.fflags & NOTE_EXIT)) + { + if (DBG) puts("subprocess exit"); + break; + } + else if (ev.filter == EVFILT_READ) + { + struct shellspawn_cmd cmd; + + if (read(fd, &cmd, sizeof(cmd)) != sizeof(cmd)) + { + if (DBG) puts("Cannot read cmd"); + break; + } + + switch (cmd.cmd) + { + case SHELLSPAWN_SIGNAL: + { + int linux_signal, darwin_signal; + + if (cmd.data_length != sizeof(int)) + goto err; + + if (read(fd, &linux_signal, sizeof(int)) != sizeof(int)) + goto err; + + // Convert Linux signal number to Darwin signal number + darwin_signal = signum_linux_to_bsd(linux_signal); + if (DBG) printf("rcvd signal %d -> %d\n", linux_signal, darwin_signal); + + if (darwin_signal != 0) + { + int fg_pid = tcgetpgrp(shellfd[0]); + if (fg_pid != -1) + { + if (DBG) printf("fg_pid = %d\n", fg_pid); + kill(fg_pid, darwin_signal); + } + else + kill(-shell_pid, darwin_signal); + } + + break; + } + default: + goto err; + } + } + } + + // Kill the child process in case it's still running + kill(shell_pid, SIGKILL); + + // Close shell fds + for (int i = 0; i < 3; i++) + { + if (shellfd[i] != -1) + close(shellfd[0]); + } + + // Reap the child + int wstatus; + waitpid(shell_pid, &wstatus, WEXITED); + wstatus = WEXITSTATUS(wstatus); + + // Report exit code back to the client + write(fd, &wstatus, sizeof(int)); + + if (DBG) printf("Shell terminated with exit code %d\n", wstatus); + close(fd); + return; +err: + if (DBG) fprintf(stderr, "Error spawning shell: %s\n", strerror(errno)); + + for (int i = 0; i < 3; i++) + { + if (shellfd[i] != -1) + close(shellfd[0]); + } + + if (shell_pid != -1) + kill(shell_pid, SIGKILL); + + close(fd); +} + diff --git a/src/shellspawn/shellspawn.h b/src/shellspawn/shellspawn.h new file mode 100644 index 000000000..e73a67bd7 --- /dev/null +++ b/src/shellspawn/shellspawn.h @@ -0,0 +1,47 @@ +/* +This file is part of Darling. + +Copyright (C) 2017 Lubos Dolezel + +Darling is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Darling is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Darling. If not, see . +*/ + +#ifndef _SHELLSPAWN_H +#define _SHELLSPAWN_H + +#ifdef TESTING +#define SHELLSPAWN_SOCKPATH "/tmp/shellspawn.sock" +#else +#define SHELLSPAWN_SOCKPATH "/var/run/shellspawn.sock" +#endif + +typedef unsigned short shellspawn_cmd_type_t; + +enum { + SHELLSPAWN_ADDARG = 1, // add shell argument + SHELLSPAWN_SETENV, // add env variable string + SHELLSPAWN_CHDIR, + SHELLSPAWN_GO, // execute the shell now, must also contain file descriptors + SHELLSPAWN_SIGNAL, // pass a signal from client +}; + +struct __attribute__((packed)) shellspawn_cmd +{ + shellspawn_cmd_type_t cmd; + unsigned short data_length; + char data[]; +}; + +#endif + diff --git a/src/startup/CMakeLists.txt b/src/startup/CMakeLists.txt index 3c8452544..382f06558 100644 --- a/src/startup/CMakeLists.txt +++ b/src/startup/CMakeLists.txt @@ -9,13 +9,15 @@ endif(COMMAND cmake_policy) enable_language(C ASM) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -ggdb -O0") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment,0x400000 -Wl,-Tbss,0x410000 -Wl,-Tdata,0x420000") add_definitions(-DINSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}" -D_GNU_SOURCE -DMLDR_BUILD) add_executable(darling darling.c) +target_link_libraries(darling -lutil) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) set(mldr_sources diff --git a/src/startup/darling.c b/src/startup/darling.c index b2b083221..156489037 100644 --- a/src/startup/darling.c +++ b/src/startup/darling.c @@ -36,7 +36,14 @@ along with Darling. If not, see . #include #include #include +#include +#include +#include +#include #include +#include +#include +#include "../shellspawn/shellspawn.h" #include "darling.h" #include "darling-config.h" @@ -50,7 +57,7 @@ char **g_argv, **g_envp; int main(int argc, char ** argv, char ** envp) { - pid_t pidInit, pidChild; + pid_t pidInit; int wstatus; if (argc <= 1) @@ -156,210 +163,367 @@ start_init: // If prefix's init is not running, start it up if (pidInit == 0) { + char socketPath[4096]; + + snprintf(socketPath, sizeof(socketPath), "%s" SHELLSPAWN_SOCKPATH, prefix); + + unlink(socketPath); + setupWorkdir(); pidInit = spawnInitProcess(); putInitPid(pidInit); - } - - if (strcmp(argv[1], "shell") != 0) - { - char *path = realpath(argv[1], NULL); - char *fullPath; - - if (path == NULL) + + // Wait until shellspawn starts + for (int i = 0; i < 15; i++) { - fprintf(stderr, "Cannot resolve path: %s\n", strerror(errno)); - exit(1); + if (access(socketPath, F_OK) == 0) + break; + sleep(1); } + } - const char *argv_child[argc + 1]; - - argv_child[0] = MLDR_PATH; - - fullPath = malloc(strlen(SYSTEM_ROOT) + strlen(path) + 1); - strcpy(fullPath, SYSTEM_ROOT); - strcat(fullPath, path); - argv_child[1] = fullPath; - - for (int i = 2; i < argc; i++) - argv_child[i] = argv[i]; - argv_child[argc] = NULL; + seteuid(g_originalUid); - pidChild = spawnChild(pidInit, MLDR_PATH, argv_child); - free(path); - free(fullPath); - } - else + if (strcmp(argv[1], "shell") == 0) { // Spawn the shell if (argc > 2) - { - size_t total_len = 0; - for (int i = 2; i < argc; i++) - total_len += strlen(argv[i]); - - char buffer[total_len + argc]; - - char *to = buffer; - for (int i = 2; i < argc; i++) - to = stpcpy(stpcpy(to, argv[i]), " "); - // Overwrite the last whitespace - *(to - 1) = '\0'; - - pidChild = spawnChild(pidInit, MLDR_PATH, - (const char *[5]) {MLDR_PATH, "/bin/bash", "-c", buffer, NULL}); - } + spawnShell((const char**) &argv[2]); else - pidChild = spawnChild(pidInit, MLDR_PATH, - (const char *[3]) {MLDR_PATH, "/bin/bash", NULL}); + spawnShell(NULL); } - if (pidChild == -ENOMEM) + else { - pidInit = 0; - goto start_init; - } + char *fullPath; + char** child_argv; + char *path = realpath(argv[1], NULL); - // Drop the privileges so that we can be killed, etc by the user - seteuid(g_originalUid); + fullPath = malloc(strlen(SYSTEM_ROOT) + strlen(path) + 1); + strcpy(fullPath, SYSTEM_ROOT); + strcat(fullPath, path); - waitpid(pidChild, &wstatus, 0); + argv[1] = fullPath; + spawnShell((const char**) &argv[1]); + } - if (WIFEXITED(wstatus)) - return WEXITSTATUS(wstatus); - if (WIFSIGNALED(wstatus)) - return WTERMSIG(wstatus); return 0; } -static void joinNamespace(pid_t pid, int type, const char* typeName) +static void pushShellspawnCommandData(int sockfd, shellspawn_cmd_type_t type, const void* data, size_t data_length) { - int fdNS; - char pathNS[4096]; - - snprintf(pathNS, sizeof(pathNS), "/proc/%d/ns/%s", pid, typeName); + struct shellspawn_cmd* cmd; + size_t length; - fdNS = open(pathNS, O_RDONLY); + length = sizeof(*cmd) + data_length; - if (fdNS < 0) + cmd = (struct shellspawn_cmd*) malloc(length); + cmd->cmd = type; + cmd->data_length = data_length; + + if (data != NULL) + memcpy(cmd->data, data, data_length); + + if (write(sockfd, cmd, length) != length) { - fprintf(stderr, "Cannot open %s namespace file: %s\n", typeName, strerror(errno)); - exit(1); + fprintf(stderr, "Error sending command to shellspawn: %s\n", strerror(errno)); + exit(EXIT_FAILURE); } +} - // Calling setns() with a PID namespace doesn't move our process into it, - // but our child process will be spawned inside the namespace - if (setns(fdNS, type) != 0) +static void pushShellspawnCommand(int sockfd, shellspawn_cmd_type_t type, const char* value) +{ + if (!value) + pushShellspawnCommandData(sockfd, type, NULL, 0); + else + pushShellspawnCommandData(sockfd, type, value, strlen(value) + 1); +} + +static void pushShellspawnCommandFDs(int sockfd, shellspawn_cmd_type_t type, const int fds[3]) +{ + struct shellspawn_cmd cmd; + char cmsgbuf[CMSG_SPACE(sizeof(int) * 3)]; + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmptr; + + cmd.cmd = type; + cmd.data_length = 0; + + iov.iov_base = &cmd; + + memset(&msg, 0, sizeof(msg)); + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + iov.iov_base = &cmd; + iov.iov_len = sizeof(cmd); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + cmptr = CMSG_FIRSTHDR(&msg); + + cmptr->cmsg_len = CMSG_LEN(sizeof(int) * 3); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(cmptr), fds, sizeof(fds[0])*3); + + if (sendmsg(sockfd, &msg, 0) < 0) { - fprintf(stderr, "Cannot join %s namespace: %s\n", typeName, strerror(errno)); - exit(1); + fprintf(stderr, "Error sending command to shellspawn: %s\n", strerror(errno)); + exit(EXIT_FAILURE); } - close(fdNS); } -pid_t spawnChild(int pidInit, const char *path, const char *const argv[]) +static int _shSockfd = -1; +static struct termios orig_termios; +static int pty_master; +static void signalHandler(int signo) { - pid_t pidChild; - char curPath[4096]; + // printf("Received signal %d\n", signo); - if (getcwd(curPath, sizeof(curPath)) == NULL) + // Forward window size changes + if (signo == SIGWINCH && pty_master != -1) { - fprintf(stderr, "Cannot get current directory: %s\n", strerror(errno)); - exit(1); + struct winsize win; + + ioctl(0, TIOCGWINSZ, &win); + ioctl(pty_master, TIOCSWINSZ, &win); } + + // Foreground process loopkup in shellspawn doesn't work + // if we're not running in TTY mode, so shellspawn falls back + // to forwarding signals to the Bash subprocess. + // + // Hence we translate SIGINT to SIGTERM for user convenience, + // because Bash will not terminate on SIGINT. + if (pty_master == -1 && signo == SIGINT) + signo = SIGTERM; + + pushShellspawnCommandData(_shSockfd, SHELLSPAWN_SIGNAL, &signo, sizeof(signo)); +} - joinNamespace(pidInit, CLONE_NEWPID, "pid"); - joinNamespace(pidInit, CLONE_NEWUTS, "uts"); +static void shellLoop(int sockfd, int master) +{ + struct sigaction sa; + struct pollfd pfds[3]; + const int fdcount = (master != -1) ? 3 : 1; // do we do pty proxying? + + // Vars for signal handler + _shSockfd = sockfd; + pty_master = master; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signalHandler; + sigfillset(&sa.sa_mask); + + for (int i = 1; i < 32; i++) + sigaction(i, &sa, NULL); + + pfds[2].fd = master; + pfds[2].events = POLLOUT; + pfds[2].revents = 0; + pfds[1].fd = STDIN_FILENO; + pfds[1].events = POLLIN; + pfds[1].revents = 0; + pfds[0].fd = sockfd; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + + if (master != -1) + fcntl(master, F_SETFL, O_NONBLOCK); + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); + fcntl(sockfd, F_SETFL, O_NONBLOCK); - pidChild = fork(); - if (pidChild < 0) + while (1) { - if (errno == ENOMEM) + char buf[4096]; + + if (poll(pfds, fdcount, -1) < 0) { - // This condition happens specifically when the init process is a zombie. - // We should simply start a new init process. - return -ENOMEM; + if (errno != EINTR) + { + perror("poll"); + break; + } } - fprintf(stderr, "Cannot spawn a child process: %s\n", strerror(errno)); - exit(1); - } - - if (pidChild == 0) - { - // This is the child process - // We still have the outside PIDs in /proc - joinNamespace(pidInit, CLONE_NEWNS, "mnt"); + if (pfds[2].revents & POLLOUT) + { + int rd; + do + { + rd = read(master, buf, sizeof(buf)); + if (rd > 0) + write(STDOUT_FILENO, buf, rd); + } + while (rd == sizeof(buf)); + } - /* - snprintf(pathNS, sizeof(pathNS), SYSTEM_ROOT "/proc/%d/ns/user", pidInit); - fdNS = open(pathNS, O_RDONLY); - if (fdNS < 0) + if (pfds[1].revents & POLLIN) { - fprintf(stderr, "Cannot open user namespace file: %s\n", strerror(errno)); - exit(1); + int rd; + do + { + rd = read(STDIN_FILENO, buf, sizeof(buf)); + if (rd > 0) + write(master, buf, rd); + } + while (rd == sizeof(buf)); } - */ - - // Drop the privileges. It's important to drop GID first, because - // non-root users can't change their GID. - setresgid(g_originalGid, g_originalGid, g_originalGid); - setresuid(g_originalUid, g_originalUid, g_originalUid); - /* - if (setns(fdNS, CLONE_NEWUSER) != 0) + if (pfds[0].revents & (POLLHUP | POLLIN)) { - fprintf(stderr, "Cannot join user namespace: %s\n", strerror(errno)); - exit(1); + int exitStatus; + + if (read(sockfd, &exitStatus, sizeof(int)) == sizeof(int)) + exit(exitStatus); + else + exit(1); } - close(fdNS); - */ + } +} - setupChild(curPath); +static void restoreTermios(void) +{ + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); +} + +static void setupPtys(int fds[3], int* master) +{ + struct winsize win; + struct termios termios; + bool tty = true; - execv(path, (char * const *) argv); + if (tcgetattr(STDIN_FILENO, &termios) < 0) + tty = false; - fprintf(stderr, "Cannot exec the target program: %s\n", strerror(errno)); + if (openpty(master, &fds[0], NULL, &termios, &win) < 0) + { + perror("opentty"); exit(1); } + fds[2] = fds[1] = fds[0]; - return pidChild; + if (tty) + { + orig_termios = termios; + + ioctl(0, TIOCGWINSZ, &win); + + termios.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); + termios.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | + INPCK | ISTRIP | IXON | PARMRK); + termios.c_oflag &= ~OPOST; + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &termios) < 0) + { + perror("tcsetattr"); + exit(1); + } + ioctl(*master, TIOCSWINSZ, &win); + + atexit(restoreTermios); + } } -void setupChild(const char *curPath) +void spawnShell(const char** argv) { + size_t total_len = 0; + int count; char buffer1[4096]; char buffer2[4096]; + int sockfd; + struct sockaddr_un addr; + char* buffer; + + if (argv != NULL) + { + for (count = 0; argv[count] != NULL; count++) + total_len += strlen(argv[count]); + buffer = malloc(total_len + count*3); - unsetenv("LESSOPEN"); - unsetenv("LESSCLOSE"); - unsetenv("LESSECHO"); + char *to = buffer; + for (int i = 0; argv[i] != NULL; i++) + { + if (to != buffer) + to = stpcpy(to, " "); + to = stpcpy(to, "'"); + to = stpcpy(to, argv[i]); + to = stpcpy(to, "'"); + } + } + else + buffer = NULL; + + // Connect to the shellspawn daemon in the container + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s" SHELLSPAWN_SOCKPATH, prefix); - setenv("PATH", - "/usr/bin:" + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd == -1) + { + fprintf(stderr, "Error creating a unix domain socket: %s\n", strerror(errno)); + exit(1); + } + + if (connect(sockfd, (struct sockaddr*) &addr, sizeof(addr)) == -1) + { + fprintf(stderr, "Error connecting to shellspawn in the container: %s\n", strerror(errno)); + exit(1); + } + + // Push environment variables + pushShellspawnCommand(sockfd, SHELLSPAWN_SETENV, + "PATH=/usr/bin:" "/bin:" "/usr/sbin:" "/sbin:" - "/usr/local/bin", - 1); + "/usr/local/bin"); - sscanf(getenv("HOME"), "/home/%4096s", buffer1); - snprintf(buffer2, sizeof(buffer2), "/Users/%s", buffer1); - setenv("HOME", buffer2, 1); + if (sscanf(getenv("HOME"), "/home/%4096s", buffer1) == 1) + { + snprintf(buffer2, sizeof(buffer2), "HOME=/Users/%s", buffer1); + pushShellspawnCommand(sockfd, SHELLSPAWN_SETENV, buffer2); + } - if (sscanf(curPath, "/home/%4096s", buffer1) == 1) + // Push shell arguments + if (buffer != NULL) { - // We're currently inside our home directory - snprintf(buffer2, sizeof(buffer2), "/Users/%s", buffer1); - setenv("PWD", buffer2, 1); - chdir(buffer2); + pushShellspawnCommand(sockfd, SHELLSPAWN_ADDARG, "-c"); + pushShellspawnCommand(sockfd, SHELLSPAWN_ADDARG, buffer); + + free(buffer); } - else + + if (getcwd(buffer1, sizeof(buffer1)) != NULL) { - snprintf(buffer2, sizeof(buffer2), SYSTEM_ROOT "%s", curPath); - setenv("PWD", buffer2, 1); - chdir(buffer2); + snprintf(buffer2, sizeof(buffer2), SYSTEM_ROOT "%s", buffer1); + pushShellspawnCommand(sockfd, SHELLSPAWN_CHDIR, buffer2); } + + int fds[3], master = -1; + + if (isatty(STDIN_FILENO)) + setupPtys(fds, &master); + else + fds[0] = dup(STDIN_FILENO); // dup() because we close() a few lines below + + if (master == -1 || !isatty(STDOUT_FILENO)) + fds[1] = STDOUT_FILENO; + if (master == -1 || !isatty(STDERR_FILENO)) + fds[2] = STDERR_FILENO; + + pushShellspawnCommandFDs(sockfd, SHELLSPAWN_GO, fds); + close(fds[0]); + + shellLoop(sockfd, master); + + if (master != -1) + close(master); + close(sockfd); } void showHelp(const char* argv0) @@ -432,7 +596,7 @@ pid_t spawnInitProcess(void) { pid_t pid; int pipefd[2]; - // char idmap[100]; + char path[1024]; char buffer[1]; FILE *file; @@ -540,6 +704,8 @@ pid_t spawnInitProcess(void) // if we enable user namespaces darlingPreInit(); + spawnLaunchd(); + // Never returns } @@ -575,13 +741,6 @@ pid_t spawnInitProcess(void) fprintf(stderr, "Cannot set gid_map for the init process: %s\n", strerror(errno)); } */ - - // This is for development only! - if (getenv("TRY_LAUNCHD") != NULL) - { - int status = 0; - waitpid(pid, &status, 0); - } // Here's where we resume the child // if we enable user namespaces @@ -616,33 +775,54 @@ void putInitPid(pid_t pidInit) fclose(fp); } -void darlingPreInit(void) +void spawnLaunchd(void) { - // TODO: Run /usr/libexec/makewhatis + puts("Bootstrapping the container with launchd..."); - // This is for development only! - if (getenv("TRY_LAUNCHD") != NULL) - { - // putenv("KQUEUE_DEBUG=1"); - execl("/bin/mldr", "mldr!/sbin/launchd", "launchd", NULL); - - fprintf(stderr, "Failed to exec launchd: %s\n", strerror(errno)); - abort(); - } - - // TODO: this is where we will exec() launchd in future. - // Instead, we just reap zombies. - while (1) + execl(MLDR_PATH, "mldr!/sbin/launchd", "launchd", NULL); + + fprintf(stderr, "Failed to exec launchd: %s\n", strerror(errno)); + abort(); +} + +static void wipeDir(const char* dirpath) +{ + char path[4096]; + struct dirent* ent; + DIR* dir = opendir(dirpath); + + if (!dir) + return; + + while ((ent = readdir(dir)) != NULL) { - int status, sig; - sigset_t chld; + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; - sigemptyset(&chld); - sigaddset(&chld, SIGCHLD); - sigwait(&chld, &sig); + snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name); - while (waitpid(-1, &status, 0) != -1); + if (ent->d_type == DT_DIR) + { + wipeDir(path); + rmdir(path); + } + else + unlink(path); } + + closedir(dir); +} + +void darlingPreInit(void) +{ + // TODO: Run /usr/libexec/makewhatis + const char* dirs[] = { + "/var/tmp", + "/var/run" + }; + + for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); i++) + wipeDir(dirs[i]); } char* defaultPrefixPath(void) diff --git a/src/startup/darling.h b/src/startup/darling.h index a19333de5..9b1ddbf9e 100644 --- a/src/startup/darling.h +++ b/src/startup/darling.h @@ -40,10 +40,7 @@ int checkPrefixDir(void); // Creates the given directory, exit()ing if not possible void createDir(const char* path); -// Spawn the specified proceess inside the namespaces that PID 1 is in -// Returns the PID of the child -// exit()s on error -pid_t spawnChild(int pidInit, const char *path, const char *const argv[]); +void spawnShell(const char** argv); // Set up some environment variables // As well as the working directory @@ -57,7 +54,9 @@ pid_t getInitProcess(void); pid_t spawnInitProcess(void); void putInitPid(pid_t pidInit); +void mapUids(pid_t pid); +void spawnLaunchd(void); void darlingPreInit(void); void checkPrefixOwner(void);