qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
To: "Daniel P. Berrangé" <berrange@redhat.com>
Cc: qemu-devel@nongnu.org, michael.roth@amd.com, kkostiuk@redhat.com,
	marcandre.lureau@redhat.com, philmd@linaro.org,
	den@virtuozzo.com
Subject: Re: [PATCH v2 2/7] qga: introduce ga_run_command() helper for guest cmd execution
Date: Fri, 15 Mar 2024 13:14:31 +0200	[thread overview]
Message-ID: <bebf17f7-d2d4-4da5-997f-d9cc530a3b26@virtuozzo.com> (raw)
In-Reply-To: <ZeddShrMTUFjMoNl@redhat.com>

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 <yur@virtuozzo.com>
>> Signed-off-by: Andrey Drobyshev <andrey.drobyshev@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 :|
> 



  reply	other threads:[~2024-03-15 11:15 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-03-01 17:28 [PATCH v2 0/7] qga/commands-posix: replace code duplicating commands with a helper Andrey Drobyshev
2024-03-01 17:28 ` [PATCH v2 1/7] qga: guest-get-fsinfo: add optional 'total-bytes-root' field Andrey Drobyshev
2024-03-01 17:28 ` [PATCH v2 2/7] qga: introduce ga_run_command() helper for guest cmd execution Andrey Drobyshev
2024-03-05 17:58   ` Daniel P. Berrangé
2024-03-15 11:14     ` Andrey Drobyshev [this message]
2024-03-01 17:28 ` [PATCH v2 3/7] qga/commands-posix: qmp_guest_shutdown: use ga_run_command helper Andrey Drobyshev
2024-03-01 17:28 ` [PATCH v2 4/7] qga/commands-posix: qmp_guest_set_time: " Andrey Drobyshev
2024-03-01 17:28 ` [PATCH v2 5/7] qga/commands-posix: execute_fsfreeze_hook: " Andrey Drobyshev
2024-03-01 17:28 ` [PATCH v2 6/7] qga/commands-posix: use ga_run_command helper when suspending via sysfs Andrey Drobyshev
2024-03-05 18:34   ` Daniel P. Berrangé
2024-03-15 12:08     ` Andrey Drobyshev
2024-03-01 17:28 ` [PATCH v2 7/7] qga/commands-posix: qmp_guest_set_user_password: use ga_run_command helper Andrey Drobyshev
2024-03-05 18:38   ` Daniel P. Berrangé
2024-03-15 11:06     ` Andrey Drobyshev
2024-03-04 12:00 ` [PATCH v2 0/7] qga/commands-posix: replace code duplicating commands with a helper Konstantin Kostiuk
2024-03-04 13:18   ` Dehan Meng

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=bebf17f7-d2d4-4da5-997f-d9cc530a3b26@virtuozzo.com \
    --to=andrey.drobyshev@virtuozzo.com \
    --cc=berrange@redhat.com \
    --cc=den@virtuozzo.com \
    --cc=kkostiuk@redhat.com \
    --cc=marcandre.lureau@redhat.com \
    --cc=michael.roth@amd.com \
    --cc=philmd@linaro.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).