Blame SOURCES/kvm-opts-don-t-silently-truncate-long-option-values.patch

016a62
From 6abc65aaa666bf41070fa772293982cb0d1ae835 Mon Sep 17 00:00:00 2001
016a62
From: Laszlo Ersek <lersek@redhat.com>
016a62
Date: Thu, 12 Sep 2019 13:05:00 +0100
016a62
Subject: [PATCH 03/22] opts: don't silently truncate long option values
016a62
MIME-Version: 1.0
016a62
Content-Type: text/plain; charset=UTF-8
016a62
Content-Transfer-Encoding: 8bit
016a62
016a62
RH-Author: Laszlo Ersek <lersek@redhat.com>
016a62
Message-id: <20190912130503.14094-4-lersek@redhat.com>
016a62
Patchwork-id: 90436
016a62
O-Subject: [RHEL-8.2.0 qemu-kvm PATCH 3/6] opts: don't silently truncate long option values
016a62
Bugzilla: 1749022
016a62
RH-Acked-by: Stefano Garzarella <sgarzare@redhat.com>
016a62
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
016a62
RH-Acked-by: Eduardo Habkost <ehabkost@redhat.com>
016a62
016a62
From: Daniel P. Berrangé <berrange@redhat.com>
016a62
016a62
The existing QemuOpts parsing code uses a fixed size 1024 byte buffer
016a62
for storing the option values. If a value exceeded this size it was
016a62
silently truncated and no error reported to the user. Long option values
016a62
is not a common scenario, but it is conceivable that they will happen.
016a62
eg if the user has a very deeply nested filesystem it would be possible
016a62
to come up with a disk path that was > 1024 bytes. Most of the time if
016a62
such data was silently truncated, the user would get an error about
016a62
opening a non-existant disk. If they're unlucky though, QEMU might use a
016a62
completely different disk image from another VM, which could be
016a62
considered a security issue. Another example program was in using the
016a62
-smbios command line arg with very large data blobs. In this case the
016a62
silent truncation will be providing semantically incorrect data to the
016a62
guest OS for SMBIOS tables.
016a62
016a62
If the operating system didn't limit the user's argv when spawning QEMU,
016a62
the code should honour whatever length arguments were given without
016a62
imposing its own length restrictions. This patch thus changes the code
016a62
to use a heap allocated buffer for storing the values during parsing,
016a62
lifting the arbitrary length restriction.
016a62
016a62
RHEL8 notes:
016a62
016a62
- Fix up upstream's obviously garbled UTF8 sequences in Dan's name (Author
016a62
  meta-datum, Signed-off-by tags).
016a62
016a62
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
016a62
Message-Id: <20180416111743.8473-4-berrange@redhat.com>
016a62
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
016a62
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
016a62
(cherry picked from commit 950c4e6c94b15cd0d8b63891dddd7a8dbf458e6a)
016a62
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
016a62
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
016a62
---
016a62
 hw/i386/multiboot.c   |  33 +++++++++------
016a62
 include/qemu/option.h |   2 +-
016a62
 util/qemu-option.c    | 111 +++++++++++++++++++++++++++-----------------------
016a62
 3 files changed, 81 insertions(+), 65 deletions(-)
016a62
016a62
diff --git a/hw/i386/multiboot.c b/hw/i386/multiboot.c
016a62
index 5bc0a2c..7a2953e 100644
016a62
--- a/hw/i386/multiboot.c
016a62
+++ b/hw/i386/multiboot.c
016a62
@@ -291,12 +291,16 @@ int load_multiboot(FWCfgState *fw_cfg,
016a62
     cmdline_len = strlen(kernel_filename) + 1;
016a62
     cmdline_len += strlen(kernel_cmdline) + 1;
016a62
     if (initrd_filename) {
016a62
-        const char *r = initrd_filename;
016a62
+        const char *r = get_opt_value(initrd_filename, NULL);
016a62
         cmdline_len += strlen(r) + 1;
016a62
         mbs.mb_mods_avail = 1;
016a62
-        while (*(r = get_opt_value(NULL, 0, r))) {
016a62
-           mbs.mb_mods_avail++;
016a62
-           r++;
016a62
+        while (1) {
016a62
+            mbs.mb_mods_avail++;
016a62
+            r = get_opt_value(r, NULL);
016a62
+            if (!*r) {
016a62
+                break;
016a62
+            }
016a62
+            r++;
016a62
         }
016a62
     }
016a62
 
016a62
@@ -313,7 +317,8 @@ int load_multiboot(FWCfgState *fw_cfg,
016a62
 
016a62
     if (initrd_filename) {
016a62
         const char *next_initrd;
016a62
-        char not_last, tmpbuf[strlen(initrd_filename) + 1];
016a62
+        char not_last;
016a62
+        char *one_file = NULL;
016a62
 
016a62
         mbs.offset_mods = mbs.mb_buf_size;
016a62
 
016a62
@@ -322,24 +327,26 @@ int load_multiboot(FWCfgState *fw_cfg,
016a62
             int mb_mod_length;
016a62
             uint32_t offs = mbs.mb_buf_size;
016a62
 
016a62
-            next_initrd = get_opt_value(tmpbuf, sizeof(tmpbuf), initrd_filename);
016a62
+            next_initrd = get_opt_value(initrd_filename, &one_file);
016a62
             not_last = *next_initrd;
016a62
             /* if a space comes after the module filename, treat everything
016a62
                after that as parameters */
016a62
-            hwaddr c = mb_add_cmdline(&mbs, tmpbuf);
016a62
-            if ((next_space = strchr(tmpbuf, ' ')))
016a62
+            hwaddr c = mb_add_cmdline(&mbs, one_file);
016a62
+            next_space = strchr(one_file, ' ');
016a62
+            if (next_space) {
016a62
                 *next_space = '\0';
016a62
-            mb_debug("multiboot loading module: %s", tmpbuf);
016a62
-            mb_mod_length = get_image_size(tmpbuf);
016a62
+            }
016a62
+            mb_debug("multiboot loading module: %s", one_file);
016a62
+            mb_mod_length = get_image_size(one_file);
016a62
             if (mb_mod_length < 0) {
016a62
-                error_report("Failed to open file '%s'", tmpbuf);
016a62
+                error_report("Failed to open file '%s'", one_file);
016a62
                 exit(1);
016a62
             }
016a62
 
016a62
             mbs.mb_buf_size = TARGET_PAGE_ALIGN(mb_mod_length + mbs.mb_buf_size);
016a62
             mbs.mb_buf = g_realloc(mbs.mb_buf, mbs.mb_buf_size);
016a62
 
016a62
-            load_image(tmpbuf, (unsigned char *)mbs.mb_buf + offs);
016a62
+            load_image(one_file, (unsigned char *)mbs.mb_buf + offs);
016a62
             mb_add_mod(&mbs, mbs.mb_buf_phys + offs,
016a62
                        mbs.mb_buf_phys + offs + mb_mod_length, c);
016a62
 
016a62
@@ -347,6 +354,8 @@ int load_multiboot(FWCfgState *fw_cfg,
016a62
                      (char *)mbs.mb_buf + offs,
016a62
                      (char *)mbs.mb_buf + offs + mb_mod_length, c);
016a62
             initrd_filename = next_initrd+1;
016a62
+            g_free(one_file);
016a62
+            one_file = NULL;
016a62
         } while (not_last);
016a62
     }
016a62
 
016a62
diff --git a/include/qemu/option.h b/include/qemu/option.h
016a62
index 1cfe5cb..3dfb449 100644
016a62
--- a/include/qemu/option.h
016a62
+++ b/include/qemu/option.h
016a62
@@ -28,7 +28,7 @@
016a62
 
016a62
 #include "qemu/queue.h"
016a62
 
016a62
-const char *get_opt_value(char *buf, int buf_size, const char *p);
016a62
+const char *get_opt_value(const char *p, char **value);
016a62
 
016a62
 void parse_option_size(const char *name, const char *value,
016a62
                        uint64_t *ret, Error **errp);
016a62
diff --git a/util/qemu-option.c b/util/qemu-option.c
016a62
index b99568f..ba44a08 100644
016a62
--- a/util/qemu-option.c
016a62
+++ b/util/qemu-option.c
016a62
@@ -70,25 +70,37 @@ static const char *get_opt_name(const char *p, char **option, char delim)
016a62
  * delimiter is fixed to be comma which starts a new option. To specify an
016a62
  * option value that contains commas, double each comma.
016a62
  */
016a62
-const char *get_opt_value(char *buf, int buf_size, const char *p)
016a62
+const char *get_opt_value(const char *p, char **value)
016a62
 {
016a62
-    char *q;
016a62
+    size_t capacity = 0, length;
016a62
+    const char *offset;
016a62
+
016a62
+    *value = NULL;
016a62
+    while (1) {
016a62
+        offset = strchr(p, ',');
016a62
+        if (!offset) {
016a62
+            offset = p + strlen(p);
016a62
+        }
016a62
 
016a62
-    q = buf;
016a62
-    while (*p != '\0') {
016a62
-        if (*p == ',') {
016a62
-            if (*(p + 1) != ',')
016a62
-                break;
016a62
-            p++;
016a62
+        length = offset - p;
016a62
+        if (*offset != '\0' && *(offset + 1) == ',') {
016a62
+            length++;
016a62
+        }
016a62
+        if (value) {
016a62
+            *value = g_renew(char, *value, capacity + length + 1);
016a62
+            strncpy(*value + capacity, p, length);
016a62
+            (*value)[capacity + length] = '\0';
016a62
+        }
016a62
+        capacity += length;
016a62
+        if (*offset == '\0' ||
016a62
+            *(offset + 1) != ',') {
016a62
+            break;
016a62
         }
016a62
-        if (q && (q - buf) < buf_size - 1)
016a62
-            *q++ = *p;
016a62
-        p++;
016a62
+
016a62
+        p += (offset - p) + 2;
016a62
     }
016a62
-    if (q)
016a62
-        *q = '\0';
016a62
 
016a62
-    return p;
016a62
+    return offset;
016a62
 }
016a62
 
016a62
 static void parse_option_bool(const char *name, const char *value, bool *ret,
016a62
@@ -162,50 +174,43 @@ void parse_option_size(const char *name, const char *value,
016a62
 
016a62
 bool has_help_option(const char *param)
016a62
 {
016a62
-    size_t buflen = strlen(param) + 1;
016a62
-    char *buf = g_malloc(buflen);
016a62
     const char *p = param;
016a62
     bool result = false;
016a62
 
016a62
-    while (*p) {
016a62
-        p = get_opt_value(buf, buflen, p);
016a62
+    while (*p && !result) {
016a62
+        char *value;
016a62
+
016a62
+        p = get_opt_value(p, &value);
016a62
         if (*p) {
016a62
             p++;
016a62
         }
016a62
 
016a62
-        if (is_help_option(buf)) {
016a62
-            result = true;
016a62
-            goto out;
016a62
-        }
016a62
+        result = is_help_option(value);
016a62
+        g_free(value);
016a62
     }
016a62
 
016a62
-out:
016a62
-    g_free(buf);
016a62
     return result;
016a62
 }
016a62
 
016a62
-bool is_valid_option_list(const char *param)
016a62
+bool is_valid_option_list(const char *p)
016a62
 {
016a62
-    size_t buflen = strlen(param) + 1;
016a62
-    char *buf = g_malloc(buflen);
016a62
-    const char *p = param;
016a62
-    bool result = true;
016a62
+    char *value = NULL;
016a62
+    bool result = false;
016a62
 
016a62
     while (*p) {
016a62
-        p = get_opt_value(buf, buflen, p);
016a62
-        if (*p && !*++p) {
016a62
-            result = false;
016a62
+        p = get_opt_value(p, &value);
016a62
+        if ((*p && !*++p) ||
016a62
+            (!*value || *value == ',')) {
016a62
             goto out;
016a62
         }
016a62
 
016a62
-        if (!*buf || *buf == ',') {
016a62
-            result = false;
016a62
-            goto out;
016a62
-        }
016a62
+        g_free(value);
016a62
+        value = NULL;
016a62
     }
016a62
 
016a62
+    result = true;
016a62
 out:
016a62
-    g_free(buf);
016a62
+    g_free(value);
016a62
     return result;
016a62
 }
016a62
 
016a62
@@ -486,7 +491,7 @@ int qemu_opt_unset(QemuOpts *opts, const char *name)
016a62
     }
016a62
 }
016a62
 
016a62
-static void opt_set(QemuOpts *opts, const char *name, const char *value,
016a62
+static void opt_set(QemuOpts *opts, const char *name, char *value,
016a62
                     bool prepend, Error **errp)
016a62
 {
016a62
     QemuOpt *opt;
016a62
@@ -495,6 +500,7 @@ static void opt_set(QemuOpts *opts, const char *name, const char *value,
016a62
 
016a62
     desc = find_desc_by_name(opts->list->desc, name);
016a62
     if (!desc && !opts_accepts_any(opts)) {
016a62
+        g_free(value);
016a62
         error_setg(errp, QERR_INVALID_PARAMETER, name);
016a62
         return;
016a62
     }
016a62
@@ -508,8 +514,7 @@ static void opt_set(QemuOpts *opts, const char *name, const char *value,
016a62
         QTAILQ_INSERT_TAIL(&opts->head, opt, next);
016a62
     }
016a62
     opt->desc = desc;
016a62
-    opt->str = g_strdup(value);
016a62
-    assert(opt->str);
016a62
+    opt->str = value;
016a62
     qemu_opt_parse(opt, &local_err);
016a62
     if (local_err) {
016a62
         error_propagate(errp, local_err);
016a62
@@ -520,7 +525,7 @@ static void opt_set(QemuOpts *opts, const char *name, const char *value,
016a62
 void qemu_opt_set(QemuOpts *opts, const char *name, const char *value,
016a62
                   Error **errp)
016a62
 {
016a62
-    opt_set(opts, name, value, false, errp);
016a62
+    opt_set(opts, name, g_strdup(value), false, errp);
016a62
 }
016a62
 
016a62
 void qemu_opt_set_bool(QemuOpts *opts, const char *name, bool val,
016a62
@@ -754,7 +759,7 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
016a62
                           const char *firstname, bool prepend, Error **errp)
016a62
 {
016a62
     char *option = NULL;
016a62
-    char value[1024];
016a62
+    char *value = NULL;
016a62
     const char *p,*pe,*pc;
016a62
     Error *local_err = NULL;
016a62
 
016a62
@@ -766,15 +771,15 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
016a62
             if (p == params && firstname) {
016a62
                 /* implicitly named first option */
016a62
                 option = g_strdup(firstname);
016a62
-                p = get_opt_value(value, sizeof(value), p);
016a62
+                p = get_opt_value(p, &value);
016a62
             } else {
016a62
                 /* option without value, probably a flag */
016a62
                 p = get_opt_name(p, &option, ',');
016a62
                 if (strncmp(option, "no", 2) == 0) {
016a62
                     memmove(option, option+2, strlen(option+2)+1);
016a62
-                    pstrcpy(value, sizeof(value), "off");
016a62
+                    value = g_strdup("off");
016a62
                 } else {
016a62
-                    pstrcpy(value, sizeof(value), "on");
016a62
+                    value = g_strdup("on");
016a62
                 }
016a62
             }
016a62
         } else {
016a62
@@ -782,11 +787,12 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
016a62
             p = get_opt_name(p, &option, '=');
016a62
             assert(*p == '=');
016a62
             p++;
016a62
-            p = get_opt_value(value, sizeof(value), p);
016a62
+            p = get_opt_value(p, &value);
016a62
         }
016a62
         if (strcmp(option, "id") != 0) {
016a62
             /* store and parse */
016a62
             opt_set(opts, option, value, prepend, &local_err);
016a62
+            value = NULL;
016a62
             if (local_err) {
016a62
                 error_propagate(errp, local_err);
016a62
                 goto cleanup;
016a62
@@ -796,11 +802,13 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
016a62
             break;
016a62
         }
016a62
         g_free(option);
016a62
-        option = NULL;
016a62
+        g_free(value);
016a62
+        option = value = NULL;
016a62
     }
016a62
 
016a62
  cleanup:
016a62
     g_free(option);
016a62
+    g_free(value);
016a62
 }
016a62
 
016a62
 /**
016a62
@@ -819,7 +827,7 @@ static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
016a62
                             bool permit_abbrev, bool defaults, Error **errp)
016a62
 {
016a62
     const char *firstname;
016a62
-    char value[1024], *id = NULL;
016a62
+    char *id = NULL;
016a62
     const char *p;
016a62
     QemuOpts *opts;
016a62
     Error *local_err = NULL;
016a62
@@ -828,11 +836,9 @@ static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
016a62
     firstname = permit_abbrev ? list->implied_opt_name : NULL;
016a62
 
016a62
     if (strncmp(params, "id=", 3) == 0) {
016a62
-        get_opt_value(value, sizeof(value), params+3);
016a62
-        id = value;
016a62
+        get_opt_value(params + 3, &id;;
016a62
     } else if ((p = strstr(params, ",id=")) != NULL) {
016a62
-        get_opt_value(value, sizeof(value), p+4);
016a62
-        id = value;
016a62
+        get_opt_value(p + 4, &id;;
016a62
     }
016a62
 
016a62
     /*
016a62
@@ -844,6 +850,7 @@ static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
016a62
      */
016a62
     assert(!defaults || list->merge_lists);
016a62
     opts = qemu_opts_create(list, id, !defaults, &local_err);
016a62
+    g_free(id);
016a62
     if (opts == NULL) {
016a62
         error_propagate(errp, local_err);
016a62
         return NULL;
016a62
-- 
016a62
1.8.3.1
016a62