From c15801c716162809542f5b72e005248d991da323 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Sat, 16 Dec 2023 13:07:50 +0000 Subject: [PATCH] jail: Implement PTY forwarding This replaces the previous pipe forwarding with a new controlling PTY that is set up for each jail. Signed-off-by: Michael Tremer --- src/libpakfire/jail.c | 343 +++++++++++++++++++++++++++--------------- 1 file changed, 219 insertions(+), 124 deletions(-) diff --git a/src/libpakfire/jail.c b/src/libpakfire/jail.c index c26b3d09..5f388444 100644 --- a/src/libpakfire/jail.c +++ b/src/libpakfire/jail.c @@ -144,10 +144,6 @@ struct pakfire_jail_exec { // Log pipes struct pakfire_jail_pipes { - int stdin[2]; - int stdout[2]; - int stderr[2]; - // Logging int log_INFO[2]; int log_ERROR[2]; @@ -197,7 +193,7 @@ struct pakfire_jail_exec { // Standard Input struct pakfire_jail_pty_stdio { int fd; - char buffer[LINE_MAX]; + struct pakfire_log_buffer buffer; struct termios attrs; int fdflags; enum pakfire_jail_pty_flags flags; @@ -669,6 +665,7 @@ static int pakfire_jail_handle_log(struct pakfire_jail* jail, return 0; } +#if 0 static int pakfire_jail_stream_stdin(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx, const int fd) { int r; @@ -706,6 +703,7 @@ static int pakfire_jail_stream_stdin(struct pakfire_jail* jail, return r; } +#endif static int pakfire_jail_recv_fd(struct pakfire_jail* jail, int socket, int* fd) { const size_t payload_length = sizeof(fd); @@ -993,6 +991,132 @@ static int pakfire_jail_setup_pty_forwarding(struct pakfire_jail* jail, return 0; } +static int pakfire_jail_fill_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) { + int r; + + // Skip this if there is not space left in the buffer + if (buffer->used >= sizeof(buffer->data)) + return 0; + + // Fill the buffer + r = read(fd, buffer->data + buffer->used, sizeof(buffer->data) - buffer->used); + + // Handle errors + if (r < 0) { + switch (errno) { + case EAGAIN: + case EIO: + break; + + default: + return -errno; + } + + // EOF + } else if (r == 0) { + // XXX What to do here? + + // Successful read + } else { + buffer->used += r; + } + + return 0; +} + +static int pakfire_jail_drain_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) { + int r; + + // Nothing to do if the buffer is empty + if (!buffer->used) + return 0; + + // Drain the buffer + r = write(fd, buffer->data, buffer->used); + + // Handle errors + if (r < 0) { + switch (errno) { + case EAGAIN: + case EIO: + break; + + default: + return -errno; + } + + // Successful write + } else { + memmove(buffer->data, buffer->data + r, buffer->used - r); + + buffer->used -= r; + } + + return 0; +} + +static int pakfire_jail_forward_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { + int r; + + // Read from standard input + if (ctx->pty.stdin.flags & PAKFIRE_JAIL_PTY_READY_TO_READ) { + r = pakfire_jail_fill_buffer(jail, ctx->pty.stdin.fd, &ctx->pty.stdin.buffer); + if (r) { + CTX_ERROR(jail->ctx, "Failed reading from standard input: %s\n", strerror(-r)); + return r; + } + + // We are done reading for now + ctx->pty.stdin.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_READ; + + // But we may have data to write + if (ctx->pty.stdin.buffer.used) + ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE; + } + + // Write to the master + if (ctx->pty.master.flags & PAKFIRE_JAIL_PTY_READY_TO_WRITE) { + r = pakfire_jail_drain_buffer(jail, ctx->pty.master.fd, &ctx->pty.stdin.buffer); + if (r) { + CTX_ERROR(jail->ctx, "Failed writing to the PTY: %s\n", strerror(-r)); + return r; + } + + // We are done writing for now + ctx->pty.master.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE; + } + + // Read from the master + if (ctx->pty.master.flags & PAKFIRE_JAIL_PTY_READY_TO_READ) { + r = pakfire_jail_fill_buffer(jail, ctx->pty.master.fd, &ctx->pty.stdout.buffer); + if (r) { + CTX_ERROR(jail->ctx, "Failed reading from the PTY: %s\n", strerror(-r)); + return r; + } + + // We are done reading for now + ctx->pty.master.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_READ; + + // But we may have data to write + if (ctx->pty.stdout.buffer.used) + ctx->pty.stdout.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE; + } + + // Write to standard output + if (ctx->pty.stdout.flags & PAKFIRE_JAIL_PTY_READY_TO_WRITE) { + r = pakfire_jail_drain_buffer(jail, ctx->pty.stdout.fd, &ctx->pty.stdout.buffer); + if (r) { + CTX_ERROR(jail->ctx, "Failed writing to standard output: %s\n", strerror(-r)); + return r; + } + + // We are done writing for now + ctx->pty.stdout.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE; + } + + return 0; +} + static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { int epollfd = -1; struct epoll_event events[EPOLL_MAX_EVENTS]; @@ -1000,9 +1124,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec int r = 0; // Fetch file descriptors from context - const int stdin = pakfire_jail_get_pipe_to_write(jail, &ctx->pipes.stdin); - const int stdout = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stdout); - const int stderr = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stderr); const int pidfd = ctx->pidfd; // Fetch the UNIX domain socket @@ -1023,11 +1144,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec const int fd; const int events; } fds[] = { - // Standard input/output - { stdin, EPOLLOUT }, - { stdout, EPOLLIN }, - { stderr, EPOLLIN }, - // Timer { timerfd, EPOLLIN }, @@ -1093,6 +1209,46 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec void* data = NULL; int priority; + // Handle PTY forwarding events + if (ctx->pty.master.fd == fd) { + if (e & (EPOLLIN|EPOLLHUP)) + ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_READ; + + if (e & (EPOLLOUT|EPOLLHUP)) + ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE; + + // Perform the work + r = pakfire_jail_forward_pty(jail, ctx); + if (r) { + CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r)); + goto ERROR; + } + + // Handle standard input + } else if (ctx->pty.stdin.fd == fd) { + if (e & (EPOLLIN|EPOLLHUP)) + ctx->pty.stdin.flags |= PAKFIRE_JAIL_PTY_READY_TO_READ; + + // Perform the work + r = pakfire_jail_forward_pty(jail, ctx); + if (r) { + CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r)); + goto ERROR; + } + + // Handle standard output + } else if (ctx->pty.stdout.fd == fd) { + if (e & (EPOLLOUT|EPOLLHUP)) + ctx->pty.stdout.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE; + + // Perform the work + r = pakfire_jail_forward_pty(jail, ctx); + if (r) { + CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r)); + goto ERROR; + } + } + // Check if there is any data to be read if (e & EPOLLIN) { // Handle any changes to the PIDFD @@ -1157,33 +1313,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec callback = pakfire_jail_log; #endif /* ENABLE_DEBUG */ - // Handle anything from the log pipes - } else if (fd == stdout) { - buffer = &ctx->buffers.stdout; - priority = LOG_INFO; - - // Send any output to the default logger if no callback is set - if (ctx->communicate.out) { - callback = ctx->communicate.out; - data = ctx->communicate.data; - } else { - callback = jail->callbacks.log; - data = jail->callbacks.log_data; - } - - } else if (fd == stderr) { - buffer = &ctx->buffers.stderr; - priority = LOG_ERR; - - // Send any output to the default logger if no callback is set - if (ctx->communicate.out) { - callback = ctx->communicate.out; - data = ctx->communicate.data; - } else { - callback = jail->callbacks.log; - data = jail->callbacks.log_data; - } - // Handle socket messages } else if (fd == socket_recv) { // Receive the passed FD @@ -1214,24 +1343,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec goto ERROR; } - if (e & EPOLLOUT) { - // Handle standard input - if (fd == stdin) { - r = pakfire_jail_stream_stdin(jail, ctx, fd); - if (r) { - switch (errno) { - // Ignore if we filled up the buffer - case EAGAIN: - break; - - default: - ERROR(jail->pakfire, "Could not write to stdin: %m\n"); - goto ERROR; - } - } - } - } - // Check if any file descriptors have been closed if (e & EPOLLHUP) { // Remove the file descriptor @@ -1876,16 +1987,58 @@ static int pakfire_jail_open_pty(struct pakfire_jail* jail, struct pakfire_jail_ CTX_DEBUG(jail->ctx, "Allocated console at %s (%d)\n", ctx->pty.console, ctx->pty.master.fd); -#if 0 + // Unlock the master device + r = unlockpt(ctx->pty.master.fd); + if (r) { + CTX_ERROR(jail->ctx, "Could not unlock the PTY: %s\n", strerror(errno)); + return -errno; + } + // Create a symlink - r = pakfire_symlink(jail->ctx, "/dev/console", ctx->pty.console); + r = pakfire_symlink(jail->ctx, ctx->pty.console, "/dev/console"); if (r) return r; -#endif return r; } +static int pakfire_jail_setup_terminal(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { + int fd; + int r; + + // Open a new terminal + fd = open("/dev/console", O_RDWR|O_NOCTTY); + if (fd < 0) { + CTX_ERROR(jail->ctx, "Failed to open a new terminal: %s\n", strerror(errno)); + return -errno; + } + + CTX_DEBUG(jail->ctx, "Opened a new terminal %d\n", fd); + + // Connect the new terminal to standard input + r = dup2(fd, STDIN_FILENO); + if (r < 0) { + CTX_ERROR(jail->ctx, "Failed to open standard input: %s\n", strerror(errno)); + return -errno; + } + + // Connect the new terminal to standard output + r = dup2(fd, STDOUT_FILENO); + if (r < 0) { + CTX_ERROR(jail->ctx, "Failed to open standard output: %s\n", strerror(errno)); + return -errno; + } + + // Connect the new terminal to standard error + r = dup2(fd, STDERR_FILENO); + if (r < 0) { + CTX_ERROR(jail->ctx, "Failed to open standard error: %s\n", strerror(errno)); + return -errno; + } + + return 0; +} + static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx, const char* argv[]) { int r; @@ -2016,14 +2169,12 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe } } -#if 0 // Create a new session r = setsid(); if (r < 0) { CTX_ERROR(jail->ctx, "Could not create a new session: %s\n", strerror(errno)); return r; } -#endif // Allocate a new PTY r = pakfire_jail_open_pty(jail, ctx); @@ -2039,6 +2190,11 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe return r; } + // Setup the terminal + r = pakfire_jail_setup_terminal(jail, ctx); + if (r) + return r; + // Close the master of the PTY close(ctx->pty.master.fd); ctx->pty.master.fd = -1; @@ -2053,41 +2209,6 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe close(ctx->pipes.log_DEBUG[0]); #endif /* ENABLE_DEBUG */ - // Connect standard input - if (ctx->pipes.stdin[0] >= 0) { - r = dup2(ctx->pipes.stdin[0], STDIN_FILENO); - if (r < 0) { - ERROR(jail->pakfire, "Could not connect fd %d to stdin: %m\n", - ctx->pipes.stdin[0]); - - return 1; - } - } - - // Connect standard output and error - if (ctx->pipes.stdout[1] >= 0 && ctx->pipes.stderr[1] >= 0) { - r = dup2(ctx->pipes.stdout[1], STDOUT_FILENO); - if (r < 0) { - ERROR(jail->pakfire, "Could not connect fd %d to stdout: %m\n", - ctx->pipes.stdout[1]); - - return 1; - } - - r = dup2(ctx->pipes.stderr[1], STDERR_FILENO); - if (r < 0) { - ERROR(jail->pakfire, "Could not connect fd %d to stderr: %m\n", - ctx->pipes.stderr[1]); - - return 1; - } - - // Close the pipe (as we have moved the original file descriptors) - pakfire_jail_close_pipe(jail, ctx->pipes.stdin); - pakfire_jail_close_pipe(jail, ctx->pipes.stdout); - pakfire_jail_close_pipe(jail, ctx->pipes.stderr); - } - // Reset open file limit (http://0pointer.net/blog/file-descriptor-limits.html) r = pakfire_rlimit_reset_nofile(jail->pakfire); if (r) @@ -2162,9 +2283,6 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[], .socket = { -1, -1 }, .pipes = { - .stdin = { -1, -1 }, - .stdout = { -1, -1 }, - .stderr = { -1, -1 }, .log_INFO = { -1, -1 }, .log_ERROR = { -1, -1 }, #ifdef ENABLE_DEBUG @@ -2218,26 +2336,6 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[], goto ERROR; } - // Create pipes to communicate with child process if we are not running interactively - if (!interactive) { - // stdin (only if callback is set) - if (ctx.communicate.in) { - r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdin, 0); - if (r) - goto ERROR; - } - - // stdout - r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdout, 0); - if (r) - goto ERROR; - - // stderr - r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stderr, 0); - if (r) - goto ERROR; - } - // Setup pipes for logging // INFO r = pakfire_jail_setup_pipe(jail, &ctx.pipes.log_INFO, O_CLOEXEC); @@ -2354,9 +2452,6 @@ ERROR: } // Close any file descriptors - pakfire_jail_close_pipe(jail, ctx.pipes.stdin); - pakfire_jail_close_pipe(jail, ctx.pipes.stdout); - pakfire_jail_close_pipe(jail, ctx.pipes.stderr); if (ctx.pidfd >= 0) close(ctx.pidfd); if (ctx.pty.master.fd >= 0) -- 2.39.2