[PATCH v2] libsemanage: semanage_pipe_data(): multiplex the compiler pipes
Stephen Smalley <[email protected]>
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <[email protected]> |
The parent writes the entire input to the compiler's stdin, then drains stdout to EOF, then drains stderr. If the child writes more than a pipe buffer to a stream the parent is not yet reading, both sides block. With hll/pp the input phase is safe (fp_to_buffer() drains stdin before any output), but sepol_module_package_to_cil() can emit stderr warnings while writing CIL to stdout, so a module that produces enough warnings fills the stderr pipe and semodule -i hanges. A streaming HLL that writes diagnostics while reading would deadlock in the input phase too. Replace the serial write/read with a poll() loop over all three pipes, feeding input while draining stdout and stderr as they become ready. read_from_pipe_to_data() is folded into an incremental append helper. Signed-off-by: Stephen Smalley <[email protected]> --- v2 fixing the formatting. libsemanage/src/direct_api.c | 191 +++++++++++++++++++++++------------ 1 file changed, 124 insertions(+), 67 deletions(-) diff --git a/libsemanage/src/direct_api.c b/libsemanage/src/direct_api.c index 6c84b23c..132e2a2a 100644 --- a/libsemanage/src/direct_api.c +++ b/libsemanage/src/direct_api.c @@ -27,6 +27,7 @@ #include <assert.h> #include <fcntl.h> #include <libgen.h> +#include <poll.h> #include <stdio.h> #include <stdio_ext.h> #include <stdlib.h> @@ -565,48 +566,135 @@ cleanup: return retval; } -static int read_from_pipe_to_data(semanage_handle_t *sh, size_t initial_len, - int fd, char **out_data_read, - size_t *out_read_len) +static int append_read(semanage_handle_t *sh, int fd, char **buf, size_t *len, + size_t *cap) { - size_t max_len = initial_len; - ssize_t read_len; - size_t data_read_len = 0; - char *data_read = NULL; + ssize_t r; - if (max_len <= 0) { - max_len = 1; + if (*len == *cap) { + ssize_t ncap; + char *tmp; + + if (__builtin_mul_overflow(*cap ? *cap : 4096, 2, &ncap)) { + ERR(sh, "Overflow"); + return -1; + } + tmp = realloc(*buf, ncap); + if (!tmp) { + ERR(sh, "Out of memory."); + return -1; + } + *buf = tmp; + *cap = ncap; } - data_read = malloc(max_len * sizeof(*data_read)); - if (data_read == NULL) { - ERR(sh, "Failed to malloc, out of memory."); + + r = read(fd, *buf + *len, *cap - *len); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + return 1; + ERR(sh, "Failed to read from pipe."); return -1; } + if (r == 0) + return 0; + *len += (size_t)r; + return 1; +} + +static int pump_pipes(semanage_handle_t *sh, int in_fd, int out_fd, int err_fd, + const char *in_data, size_t in_len, char **out_data, + size_t *out_len, char **err_data, size_t *err_len) +{ + size_t in_off = 0, out_cap = 0, err_cap = 0; + int rc = 0; + + *out_data = NULL; + *out_len = 0; + *err_data = NULL; + *err_len = 0; + + while (in_fd >= 0 || out_fd >= 0 || err_fd >= 0) { + struct pollfd pfd[3]; + + int n = 0, i_in = -1, i_out = -1, i_err = -1; + + if (in_fd >= 0) { + pfd[n].fd = in_fd; + pfd[n].events = POLLOUT; + i_in = n++; + } + if (out_fd >= 0) { + pfd[n].fd = out_fd; + pfd[n].events = POLLIN; + i_out = n++; + } + if (err_fd >= 0) { + pfd[n].fd = err_fd; + pfd[n].events = POLLIN; + i_err = n++; + } + + if (poll(pfd, n, -1) < 0) { + if (errno == EINTR) + continue; + ERR(sh, "poll() failed."); + rc = -1; + break; + } + + if (i_in >= 0 && (pfd[i_in].revents & (POLLOUT | POLLERR))) { + ssize_t w = + write(in_fd, in_data + in_off, in_len - in_off); + if (w < 0) { + if (errno != EINTR && errno != EAGAIN) { + ERR(sh, + "Failed to write to input pipe."); + rc = -1; + close(in_fd); + in_fd = -1; + } + } else { + in_off += (size_t)w; + if (in_off == in_len) { + close(in_fd); + in_fd = -1; + } + } + } - while ((read_len = read(fd, data_read + data_read_len, - max_len - data_read_len)) > 0) { - data_read_len += read_len; - if (data_read_len == max_len) { - char *tmp; - if (__builtin_mul_overflow(max_len, 2, &max_len)) { - ERR(sh, "Overflow"); - free(data_read); - return -1; + if (i_out >= 0 && + (pfd[i_out].revents & (POLLIN | POLLHUP | POLLERR))) { + int r = append_read(sh, out_fd, out_data, out_len, + &out_cap); + if (r <= 0) { + if (r < 0) + rc = -1; + close(out_fd); + out_fd = -1; } - tmp = realloc(data_read, max_len); - if (tmp == NULL) { - ERR(sh, "Failed to realloc, out of memory."); - free(data_read); - return -1; + } + + if (i_err >= 0 && + (pfd[i_err].revents & (POLLIN | POLLHUP | POLLERR))) { + int r = append_read(sh, err_fd, err_data, err_len, + &err_cap); + if (r <= 0) { + if (r < 0) + rc = -1; + close(err_fd); + err_fd = -1; } - data_read = tmp; } } - *out_read_len = data_read_len; - *out_data_read = data_read; + if (in_fd >= 0) + close(in_fd); + if (out_fd >= 0) + close(out_fd); + if (err_fd >= 0) + close(err_fd); - return 0; + return rc; } // Forward error messages to redirected stderr pipe @@ -649,7 +737,6 @@ static int semanage_pipe_data(semanage_handle_t *sh, const char *path, char *err_data_read = NULL; int retval; int status = 0; - size_t initial_len; size_t data_read_len = 0; size_t err_data_read_len = 0; struct sigaction old_signal; @@ -747,46 +834,16 @@ child_err: any_err = 1; } - retval = write_full(input_fd[PIPE_WRITE], in_data, in_data_len); - if (retval == -1) { - ERR(sh, "Failed to write data to input pipe."); - any_err = 1; - } - retval = close(input_fd[PIPE_WRITE]); + retval = pump_pipes(sh, input_fd[PIPE_WRITE], + output_fd[PIPE_READ], err_fd[PIPE_READ], + in_data, in_data_len, &data_read, + &data_read_len, &err_data_read, + &err_data_read_len); input_fd[PIPE_WRITE] = -1; - if (retval == -1) { - ERR(sh, "Unable to close write end of input pipe."); - any_err = 1; - } - - initial_len = 1 << 17; - retval = read_from_pipe_to_data(sh, initial_len, - output_fd[PIPE_READ], - &data_read, &data_read_len); - if (retval != 0) { - any_err = 1; - } - retval = close(output_fd[PIPE_READ]); output_fd[PIPE_READ] = -1; - if (retval == -1) { - ERR(sh, "Unable to close read end of output pipe."); - any_err = 1; - } - - initial_len = 1 << 9; - retval = read_from_pipe_to_data(sh, initial_len, - err_fd[PIPE_READ], - &err_data_read, - &err_data_read_len); - if (retval != 0) { - any_err = 1; - } - retval = close(err_fd[PIPE_READ]); err_fd[PIPE_READ] = -1; - if (retval == -1) { - ERR(sh, "Unable to close read end of error pipe."); + if (retval != 0) any_err = 1; - } errno = ENODATA; if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { -- 2.55.0