On 3/5/24 19:58, Daniel P. Berrangé wrote: > On Fri, Mar 01, 2024 at 07:28:53PM +0200, Andrey Drobyshev wrote: >> When executing guest commands in *nix environment, we repeat the same >> fork/exec pattern multiple times. Let's just separate it into a single >> helper which would also be able to feed input data into the launched >> process' stdin. This way we can avoid code duplication. >> >> To keep the history more bisectable, let's replace qmp commands >> implementations one by one. Also add G_GNUC_UNUSED attribute to the >> helper and remove it in the next commit. >> >> Originally-by: Yuri Pudgorodskiy <y...@virtuozzo.com> >> Signed-off-by: Andrey Drobyshev <andrey.drobys...@virtuozzo.com> >> --- >> qga/commands-posix.c | 140 +++++++++++++++++++++++++++++++++++++++++++ >> 1 file changed, 140 insertions(+) >> >> diff --git a/qga/commands-posix.c b/qga/commands-posix.c >> index 8207c4c47e..781498418f 100644 >> --- a/qga/commands-posix.c >> +++ b/qga/commands-posix.c >> @@ -76,6 +76,146 @@ static void ga_wait_child(pid_t pid, int *status, Error >> **errp) >> g_assert(rpid == pid); >> } >> >> +static void ga_pipe_read_str(int fd[2], char **str, size_t *len) >> +{ >> + ssize_t n; >> + char buf[1024]; >> + close(fd[1]); >> + fd[1] = -1; >> + while ((n = read(fd[0], buf, sizeof(buf))) != 0) { >> + if (n < 0) { >> + if (errno == EINTR) { >> + continue; >> + } else { >> + break; > > This is a fatal error condition.... > >> + } >> + } >> + *str = g_realloc(*str, *len + n); >> + memcpy(*str + *len, buf, n); >> + *len += n; >> + } >> + close(fd[0]); >> + fd[0] = -1; > > ....and yet as far as the caller is concerned we're not indicating > any sense of failure here. They're just being returned a partially > read data stream as if it were successful. IMHO this needs to be > reported properly. >
Agreed. We might make this helper return -errno in case of an erroneous read from pipe, check the value in the caller and do error_setg_errno() if smth went wrong. > > Nothing in this method has NUL terminated 'str', so we are > relying on the caller *always* honouring 'len', but..... > Agreed as well. Let's allocate +1 additional byte and store '\0' in there on every iteration, making sure our result is always null-terminated. That way we won't need to check 'len' in the caller. >> +} >> + >> +/* >> + * Helper to run command with input/output redirection, >> + * sending string to stdin and taking error message from >> + * stdout/err. >> + */ >> +G_GNUC_UNUSED >> +static int ga_run_command(const char *argv[], const char *in_str, >> + const char *action, Error **errp) >> +{ >> + pid_t pid; >> + int status; >> + int retcode = -1; >> + int infd[2] = { -1, -1 }; >> + int outfd[2] = { -1, -1 }; >> + char *str = NULL; >> + size_t len = 0; >> + >> + if ((in_str && !g_unix_open_pipe(infd, FD_CLOEXEC, NULL)) || >> + !g_unix_open_pipe(outfd, FD_CLOEXEC, NULL)) { >> + error_setg(errp, "cannot create pipe FDs"); >> + goto out; >> + } >> + >> + pid = fork(); >> + if (pid == 0) { >> + char *cherr = NULL; >> + >> + setsid(); >> + >> + if (in_str) { >> + /* Redirect stdin to infd. */ >> + close(infd[1]); >> + dup2(infd[0], 0); >> + close(infd[0]); >> + } else { >> + reopen_fd_to_null(0); >> + } >> + >> + /* Redirect stdout/stderr to outfd. */ >> + close(outfd[0]); >> + dup2(outfd[1], 1); >> + dup2(outfd[1], 2); >> + close(outfd[1]); >> + >> + execvp(argv[0], (char *const *)argv); >> + >> + /* Write the cause of failed exec to pipe for the parent to read >> it. */ >> + cherr = g_strdup_printf("failed to exec '%s'", argv[0]); >> + perror(cherr); >> + g_free(cherr); >> + _exit(EXIT_FAILURE); >> + } else if (pid < 0) { >> + error_setg_errno(errp, errno, "failed to create child process"); >> + goto out; >> + } >> + >> + if (in_str) { >> + close(infd[0]); >> + infd[0] = -1; >> + if (qemu_write_full(infd[1], in_str, strlen(in_str)) != >> + strlen(in_str)) { >> + error_setg_errno(errp, errno, "%s: cannot write to stdin pipe", >> + action); >> + goto out; >> + } >> + close(infd[1]); >> + infd[1] = -1; >> + } >> + >> + ga_pipe_read_str(outfd, &str, &len); >> + >> + ga_wait_child(pid, &status, errp); >> + if (*errp) { >> + goto out; >> + } >> + >> + if (!WIFEXITED(status)) { >> + if (len) { >> + error_setg(errp, "child process has terminated abnormally: %s", >> + str); > > ...this is reading 'str' without honouring 'len', so is likely > an array out of bounds read. > >> + } else { >> + error_setg(errp, "child process has terminated abnormally"); >> + } >> + goto out; >> + } >> + >> + retcode = WEXITSTATUS(status); >> + >> + if (WEXITSTATUS(status)) { >> + if (len) { >> + error_setg(errp, "child process has failed to %s: %s", >> + action, str); > > Again, array out of bounds is likely > >> + } else { >> + error_setg(errp, "child process has failed to %s: exit status >> %d", >> + action, WEXITSTATUS(status)); >> + } >> + goto out; >> + } >> + >> +out: >> + g_free(str); >> + >> + if (infd[0] != -1) { >> + close(infd[0]); >> + } >> + if (infd[1] != -1) { >> + close(infd[1]); >> + } >> + if (outfd[0] != -1) { >> + close(outfd[0]); >> + } >> + if (outfd[1] != -1) { >> + close(outfd[1]); >> + } >> + >> + return retcode; >> +} >> + >> void qmp_guest_shutdown(const char *mode, Error **errp) >> { >> const char *shutdown_flag; >> -- >> 2.39.3 >> >> > > With regards, > Daniel > -- > |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| > |: https://libvirt.org -o- https://fstop138.berrange.com :| > |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :| >