Pablo Greco e6a3ae
From 5fe3c58c3a57a04254b3083b070fdf99fba82c93 Mon Sep 17 00:00:00 2001
Pablo Greco e6a3ae
From: Laszlo Ersek <lersek@redhat.com>
Pablo Greco e6a3ae
Date: Thu, 12 Sep 2019 13:04:59 +0100
Pablo Greco e6a3ae
Subject: [PATCH 02/22] opts: don't silently truncate long parameter keys
Pablo Greco e6a3ae
MIME-Version: 1.0
Pablo Greco e6a3ae
Content-Type: text/plain; charset=UTF-8
Pablo Greco e6a3ae
Content-Transfer-Encoding: 8bit
Pablo Greco e6a3ae
Pablo Greco e6a3ae
RH-Author: Laszlo Ersek <lersek@redhat.com>
Pablo Greco e6a3ae
Message-id: <20190912130503.14094-3-lersek@redhat.com>
Pablo Greco e6a3ae
Patchwork-id: 90435
Pablo Greco e6a3ae
O-Subject: [RHEL-8.2.0 qemu-kvm PATCH 2/6] opts: don't silently truncate long parameter keys
Pablo Greco e6a3ae
Bugzilla: 1749022
Pablo Greco e6a3ae
RH-Acked-by: Stefano Garzarella <sgarzare@redhat.com>
Pablo Greco e6a3ae
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Pablo Greco e6a3ae
RH-Acked-by: Eduardo Habkost <ehabkost@redhat.com>
Pablo Greco e6a3ae
Pablo Greco e6a3ae
From: Daniel P. Berrangé <berrange@redhat.com>
Pablo Greco e6a3ae
Pablo Greco e6a3ae
The existing QemuOpts parsing code uses a fixed size 128 byte buffer
Pablo Greco e6a3ae
for storing the parameter keys. If a key exceeded this size it was
Pablo Greco e6a3ae
silently truncate and no error reported to the user. This behaviour was
Pablo Greco e6a3ae
reasonable & harmless because traditionally the key names are all
Pablo Greco e6a3ae
statically declared, and it was known that no code was declaring a key
Pablo Greco e6a3ae
longer than 127 bytes. This assumption, however, ceased to be valid once
Pablo Greco e6a3ae
the block layer added support for dot-separate compound keys. This
Pablo Greco e6a3ae
syntax allows for keys that can be arbitrarily long, limited only by the
Pablo Greco e6a3ae
number of block drivers you can stack up. With this usage, silently
Pablo Greco e6a3ae
truncating the key name can never lead to correct behaviour.
Pablo Greco e6a3ae
Pablo Greco e6a3ae
Hopefully such truncation would turn into an error, when the block code
Pablo Greco e6a3ae
then tried to extract options later, but there's no guarantee that will
Pablo Greco e6a3ae
happen. It is conceivable that an option specified by the user may be
Pablo Greco e6a3ae
truncated and then ignored. This could have serious consequences,
Pablo Greco e6a3ae
possibly even leading to security problems if the ignored option set a
Pablo Greco e6a3ae
security relevant parameter.
Pablo Greco e6a3ae
Pablo Greco e6a3ae
If the operating system didn't limit the user's argv when spawning QEMU,
Pablo Greco e6a3ae
the code should honour whatever length arguments were given without
Pablo Greco e6a3ae
imposing its own length restrictions. This patch thus changes the code
Pablo Greco e6a3ae
to use a heap allocated buffer for storing the keys during parsing,
Pablo Greco e6a3ae
lifting the arbitrary length restriction.
Pablo Greco e6a3ae
Pablo Greco e6a3ae
RHEL8 notes:
Pablo Greco e6a3ae
Pablo Greco e6a3ae
- Fix up upstream's obviously garbled UTF8 sequences in Dan's name (Author
Pablo Greco e6a3ae
  meta-datum, Signed-off-by tags).
Pablo Greco e6a3ae
Pablo Greco e6a3ae
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Pablo Greco e6a3ae
Message-Id: <20180416111743.8473-3-berrange@redhat.com>
Pablo Greco e6a3ae
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Pablo Greco e6a3ae
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Pablo Greco e6a3ae
(cherry picked from commit e652714f98f22e8882e88e3d563b025c5b00feec)
Pablo Greco e6a3ae
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Pablo Greco e6a3ae
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
Pablo Greco e6a3ae
---
Pablo Greco e6a3ae
 tests/test-qemu-opts.c | 18 ------------------
Pablo Greco e6a3ae
 util/qemu-option.c     | 44 ++++++++++++++++++++++----------------------
Pablo Greco e6a3ae
 2 files changed, 22 insertions(+), 40 deletions(-)
Pablo Greco e6a3ae
Pablo Greco e6a3ae
diff --git a/tests/test-qemu-opts.c b/tests/test-qemu-opts.c
Pablo Greco e6a3ae
index 77dd72b..7092e21 100644
Pablo Greco e6a3ae
--- a/tests/test-qemu-opts.c
Pablo Greco e6a3ae
+++ b/tests/test-qemu-opts.c
Pablo Greco e6a3ae
@@ -459,8 +459,6 @@ static void test_opts_parse(void)
Pablo Greco e6a3ae
 {
Pablo Greco e6a3ae
     Error *err = NULL;
Pablo Greco e6a3ae
     QemuOpts *opts;
Pablo Greco e6a3ae
-    char long_key[129];
Pablo Greco e6a3ae
-    char *params;
Pablo Greco e6a3ae
 
Pablo Greco e6a3ae
     /* Nothing */
Pablo Greco e6a3ae
     opts = qemu_opts_parse(&opts_list_03, "", false, &error_abort);
Pablo Greco e6a3ae
@@ -471,22 +469,6 @@ static void test_opts_parse(void)
Pablo Greco e6a3ae
     g_assert_cmpuint(opts_count(opts), ==, 1);
Pablo Greco e6a3ae
     g_assert_cmpstr(qemu_opt_get(opts, ""), ==, "val");
Pablo Greco e6a3ae
 
Pablo Greco e6a3ae
-    /* Long key */
Pablo Greco e6a3ae
-    memset(long_key, 'a', 127);
Pablo Greco e6a3ae
-    long_key[127] = 'z';
Pablo Greco e6a3ae
-    long_key[128] = 0;
Pablo Greco e6a3ae
-    params = g_strdup_printf("%s=v", long_key);
Pablo Greco e6a3ae
-    opts = qemu_opts_parse(&opts_list_03, params + 1, NULL, &error_abort);
Pablo Greco e6a3ae
-    g_assert_cmpuint(opts_count(opts), ==, 1);
Pablo Greco e6a3ae
-    g_assert_cmpstr(qemu_opt_get(opts, long_key + 1), ==, "v");
Pablo Greco e6a3ae
-
Pablo Greco e6a3ae
-    /* Overlong key gets truncated */
Pablo Greco e6a3ae
-    opts = qemu_opts_parse(&opts_list_03, params, NULL, &error_abort);
Pablo Greco e6a3ae
-    g_assert(opts_count(opts) == 1);
Pablo Greco e6a3ae
-    long_key[127] = 0;
Pablo Greco e6a3ae
-    g_assert_cmpstr(qemu_opt_get(opts, long_key), ==, "v");
Pablo Greco e6a3ae
-    g_free(params);
Pablo Greco e6a3ae
-
Pablo Greco e6a3ae
     /* Multiple keys, last one wins */
Pablo Greco e6a3ae
     opts = qemu_opts_parse(&opts_list_03, "a=1,b=2,,x,a=3",
Pablo Greco e6a3ae
                            false, &error_abort);
Pablo Greco e6a3ae
diff --git a/util/qemu-option.c b/util/qemu-option.c
Pablo Greco e6a3ae
index a8db173..b99568f 100644
Pablo Greco e6a3ae
--- a/util/qemu-option.c
Pablo Greco e6a3ae
+++ b/util/qemu-option.c
Pablo Greco e6a3ae
@@ -43,27 +43,23 @@
Pablo Greco e6a3ae
  * first byte of the option name)
Pablo Greco e6a3ae
  *
Pablo Greco e6a3ae
  * The option name is delimited by delim (usually , or =) or the string end
Pablo Greco e6a3ae
- * and is copied into buf. If the option name is longer than buf_size, it is
Pablo Greco e6a3ae
- * truncated. buf is always zero terminated.
Pablo Greco e6a3ae
+ * and is copied into option. The caller is responsible for free'ing option
Pablo Greco e6a3ae
+ * when no longer required.
Pablo Greco e6a3ae
  *
Pablo Greco e6a3ae
  * The return value is the position of the delimiter/zero byte after the option
Pablo Greco e6a3ae
  * name in p.
Pablo Greco e6a3ae
  */
Pablo Greco e6a3ae
-static const char *get_opt_name(char *buf, int buf_size, const char *p,
Pablo Greco e6a3ae
-                                char delim)
Pablo Greco e6a3ae
+static const char *get_opt_name(const char *p, char **option, char delim)
Pablo Greco e6a3ae
 {
Pablo Greco e6a3ae
-    char *q;
Pablo Greco e6a3ae
+    char *offset = strchr(p, delim);
Pablo Greco e6a3ae
 
Pablo Greco e6a3ae
-    q = buf;
Pablo Greco e6a3ae
-    while (*p != '\0' && *p != delim) {
Pablo Greco e6a3ae
-        if (q && (q - buf) < buf_size - 1)
Pablo Greco e6a3ae
-            *q++ = *p;
Pablo Greco e6a3ae
-        p++;
Pablo Greco e6a3ae
+    if (offset) {
Pablo Greco e6a3ae
+        *option = g_strndup(p, offset - p);
Pablo Greco e6a3ae
+        return offset;
Pablo Greco e6a3ae
+    } else {
Pablo Greco e6a3ae
+        *option = g_strdup(p);
Pablo Greco e6a3ae
+        return p + strlen(p);
Pablo Greco e6a3ae
     }
Pablo Greco e6a3ae
-    if (q)
Pablo Greco e6a3ae
-        *q = '\0';
Pablo Greco e6a3ae
-
Pablo Greco e6a3ae
-    return p;
Pablo Greco e6a3ae
 }
Pablo Greco e6a3ae
 
Pablo Greco e6a3ae
 /*
Pablo Greco e6a3ae
@@ -757,7 +753,8 @@ void qemu_opts_print(QemuOpts *opts, const char *separator)
Pablo Greco e6a3ae
 static void opts_do_parse(QemuOpts *opts, const char *params,
Pablo Greco e6a3ae
                           const char *firstname, bool prepend, Error **errp)
Pablo Greco e6a3ae
 {
Pablo Greco e6a3ae
-    char option[128], value[1024];
Pablo Greco e6a3ae
+    char *option = NULL;
Pablo Greco e6a3ae
+    char value[1024];
Pablo Greco e6a3ae
     const char *p,*pe,*pc;
Pablo Greco e6a3ae
     Error *local_err = NULL;
Pablo Greco e6a3ae
 
Pablo Greco e6a3ae
@@ -768,11 +765,11 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
Pablo Greco e6a3ae
             /* found "foo,more" */
Pablo Greco e6a3ae
             if (p == params && firstname) {
Pablo Greco e6a3ae
                 /* implicitly named first option */
Pablo Greco e6a3ae
-                pstrcpy(option, sizeof(option), firstname);
Pablo Greco e6a3ae
+                option = g_strdup(firstname);
Pablo Greco e6a3ae
                 p = get_opt_value(value, sizeof(value), p);
Pablo Greco e6a3ae
             } else {
Pablo Greco e6a3ae
                 /* option without value, probably a flag */
Pablo Greco e6a3ae
-                p = get_opt_name(option, sizeof(option), p, ',');
Pablo Greco e6a3ae
+                p = get_opt_name(p, &option, ',');
Pablo Greco e6a3ae
                 if (strncmp(option, "no", 2) == 0) {
Pablo Greco e6a3ae
                     memmove(option, option+2, strlen(option+2)+1);
Pablo Greco e6a3ae
                     pstrcpy(value, sizeof(value), "off");
Pablo Greco e6a3ae
@@ -782,10 +779,8 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
Pablo Greco e6a3ae
             }
Pablo Greco e6a3ae
         } else {
Pablo Greco e6a3ae
             /* found "foo=bar,more" */
Pablo Greco e6a3ae
-            p = get_opt_name(option, sizeof(option), p, '=');
Pablo Greco e6a3ae
-            if (*p != '=') {
Pablo Greco e6a3ae
-                break;
Pablo Greco e6a3ae
-            }
Pablo Greco e6a3ae
+            p = get_opt_name(p, &option, '=');
Pablo Greco e6a3ae
+            assert(*p == '=');
Pablo Greco e6a3ae
             p++;
Pablo Greco e6a3ae
             p = get_opt_value(value, sizeof(value), p);
Pablo Greco e6a3ae
         }
Pablo Greco e6a3ae
@@ -794,13 +789,18 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
Pablo Greco e6a3ae
             opt_set(opts, option, value, prepend, &local_err);
Pablo Greco e6a3ae
             if (local_err) {
Pablo Greco e6a3ae
                 error_propagate(errp, local_err);
Pablo Greco e6a3ae
-                return;
Pablo Greco e6a3ae
+                goto cleanup;
Pablo Greco e6a3ae
             }
Pablo Greco e6a3ae
         }
Pablo Greco e6a3ae
         if (*p != ',') {
Pablo Greco e6a3ae
             break;
Pablo Greco e6a3ae
         }
Pablo Greco e6a3ae
+        g_free(option);
Pablo Greco e6a3ae
+        option = NULL;
Pablo Greco e6a3ae
     }
Pablo Greco e6a3ae
+
Pablo Greco e6a3ae
+ cleanup:
Pablo Greco e6a3ae
+    g_free(option);
Pablo Greco e6a3ae
 }
Pablo Greco e6a3ae
 
Pablo Greco e6a3ae
 /**
Pablo Greco e6a3ae
-- 
Pablo Greco e6a3ae
1.8.3.1
Pablo Greco e6a3ae